/*
 * 作成日: 2005/02/04
 */
package jp.co.ntt.lms.xmo.Lo.lom;

import java.io.File;
import java.util.Hashtable;
import java.util.Vector;

import jp.co.ntt.lms.lo.lom.learnableinfo.NodeController;
import jp.co.ntt.lms.xmo.util.DebugLog;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * <p>ScormのマニフェストファイルからLOM情報を取り込みます。</p>
 * <p>LOM取り込み設定ファイルを使い、ScormのマニフェストファイルからLOM情報を取り込みます。
 * </p>
 * <p>LOM取り込み設定ファイルは、<code>/conf/lo/scorm.ini</code>の<code>LOM_ENTRY_FILE</code>にてパスとファイル名を設定しています。
 * </p>LOM取り込み設定ファイル自身XML形式で保存されており、LOM取り込みを可変で行えるような構成になっています。
 * <p>
 * </p>
 * @author t-sugawara
 */
public class EntryDataFactory {
    /** ノードコントローラ */
    private NodeController ctrl = null;
    /** メタデータのノード */
    private Node metaDataNode = null;
    /** LOM取り込み設定ノード */
    private Node lomEntryNode = null;
    /** LOM情報ノード */
    private Node lomNode = null;
    /** マニフェストファイル */
    private File manifestDirectory = null;
    /** LOM登録データ */
    private Vector entryData = null;
    /** 取り込み済みテーブル */
    private Hashtable entryTable = null;
    /** 取り込み済みテーブル */
    private Hashtable inLineEntryTable = null;
    /** マニフェストにLOMが直接記述されているか判定 */
    private boolean isInLine = false;
    
    /** 取り込み属性：LOM情報取り込みノード */
    private static final String ENTRY = "entry";
    /** 取り込み属性値：有効 */
    private static final String ENTRY_ENABLE = "enable";
    /** 取り込み属性：LOM表示項目名 */
    private static final String ENTRY_NAME = "name";
    /** 取り込み属性：LOM表示時に選択項目 */
    private static final String CHOICES = "choices";
    /** LOM表示項目名のインデックス */
    private static final int NAME_INDEX = 0;
    /** LOM表示時に選択項目のインデックス */
    private static final int CHOICES_INDEX = 1;
    
    /**
     * コンストラクタ
     */
    public EntryDataFactory() {
    }
    
    /**
     * コンストラクタ
     * @param manifestFile マニフェストファイル
     * @param lomEntryFile LOM取り込み設定ファイル
     */
    public EntryDataFactory(File manifestFile, File lomEntryFile) {
        DebugLog.write(getClass(),
				"Constructor manifestFile:"+manifestFile.getPath(), DebugLog.ROW);
        DebugLog.write(getClass(),
				"Constructor lomEntryFile:"+lomEntryFile.getPath(), DebugLog.ROW);
        try {
            this.ctrl = new NodeController();
            this.getMetaDataNode(manifestFile);
            this.getLOMEntryNode(lomEntryFile);
            this.manifestDirectory = manifestFile.getParentFile();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        DebugLog.write(getClass(), "Constructor End:", DebugLog.ROW);
    }

    /**
     * <p>マニフェストファイルからメタデータノードを取得します。</p>
     * @param manifestFile マニフェストファイル
     * @throws Exception
     */
    private void getMetaDataNode(File manifestFile) throws Exception {
        DebugLog.write(getClass(),
				"getMetaDataNode() manifestFile:"+manifestFile.getPath(), DebugLog.ROW);
        if (manifestFile == null) {
            throw new Exception();
        }
        
        Node topNode = ctrl.parse(manifestFile);
        this.metaDataNode = ctrl.getNode(topNode, "/:manifest/:metadata");

        DebugLog.write(getClass(), "getMetaDataNode() End:", DebugLog.ROW);
    }
    
    /**
     * <p>LOM登録判定ファイルからLOM登録判定用ノードを作成します。</p>
     * @param lomEntryFile
     * @throws Exception
     */
    private void getLOMEntryNode(File lomEntryFile) throws Exception {
        DebugLog.write(getClass(),
				"getLOMEntryNode() manifestFile:"+lomEntryFile.getPath(), DebugLog.ROW);
        if (lomEntryFile == null) {
            throw new Exception();
        }
        
        Node topNode = ctrl.parse(lomEntryFile);
        lomEntryNode = topNode;
        this.lomEntryNode = ctrl.getNode(topNode, "//lom");
        DebugLog.write(getClass(), "getLOMEntryNode() End:", DebugLog.ROW);
    }
    
    /**
     * <p>DB登録情報を作成します。</p>
     * <p>LOM情報取り込みの際に使用されるデータを作成します。</p>
     * <p>作成するデータは、マニフェストファイルを取得し、内部の全文書情報に記述されているLOM情報を取得します。</p>
     * <p>全文書内に外部ファイルへの参照が行われている場合は、参照先も取得するLOM情報の検索対象となります。</p>
     * 
     * @return EntryDataUnitのVector
     * @throws Exception
     */
//    public Vector makeEntryData() throws Exception {
//        DebugLog.write(getClass(), "makeEntryData() start", DebugLog.ROW);
//        entryData = new Vector();
//        entryTable = new Hashtable();
//        getLOMNode();
//        DebugLog.write(getClass(), "makeEntryData() Node:"
//                		+ lomEntryNode.getLocalName(), DebugLog.ROW);
//        makeLOMEntryData(lomEntryNode.getChildNodes());
//        DebugLog.write(getClass(), "makeEntryData() end", DebugLog.ROW);
//        return entryData;
//    }
    public Vector makeEntryData() throws Exception {
        DebugLog.write(getClass(), "makeEntryData() start", DebugLog.ROW);
        entryData = new Vector();
        getLOMNode();
        if (lomNode != null) {
//          エントリーテーブルを作成
            entryTable = new Hashtable();
            inLineEntryTable = new Hashtable();
            entryData = new Vector();
            makeEntryTable(lomEntryNode);
//            System.out.println("lomEntry size:" + entryTable.size());
//            System.out.println("lomEntry:" + entryTable);
//    		再帰的にノードを参照しエントリーテーブルとマッチしたノードの値を取得する。
            makeLOMEntryData(lomNode.getChildNodes());
        }
        return entryData;
    }

    /**
     * <p>データ取得の判定を行った、パスをキーとした項目名のハッシュテーブルを返します。</p>
     * @return パスをキーとした項目名のハッシュテーブル
     */
    public Hashtable getEntryTable() {
        return entryTable;
    }

    /**
     * LOMノードを取得します。
     * @throws Exception
     */
    private void getLOMNode() throws Exception {
        DebugLog.write(getClass(), "getLOMNode() start", DebugLog.ROW);
        lomNode = null;

        if (metaDataNode == null) {
            return;
        }
        NodeList n = metaDataNode.getChildNodes();
        if (n == null) {
            return;
        }
        for(int i = 0; i < n.getLength(); ++i) {
            Node checkNode = n.item(i);
//            System.out.println("Node Name:" + checkNode.getNodeName());
//            System.out.println("Node LocalName:" + checkNode.getLocalName());
            if (checkNode.getLocalName() != null
                    && checkNode.getLocalName().equals("lom")) {
                isInLine = true;
                lomNode = checkNode;
            }
        }
        
        Node locationNode = ctrl.getNode(metaDataNode, ".//adlcp:location");
        if (locationNode == null) {
            return;
        }
        File locationFile = new File(locationNode.getFirstChild().getNodeValue());
        if (!(locationFile.isAbsolute())) {
            locationFile = new File(this.manifestDirectory.getPath() + File.separator + locationNode.getFirstChild().getNodeValue());
            if (!(locationFile.isAbsolute())) {
                return;
            }
            lomNode = ctrl.parse(locationFile);
        }

//        System.out.println("Node:" + lomNode);
        DebugLog.write(getClass(), "getLOMNode() end", DebugLog.ROW);
    }

//    /**
//     * <p>LOMノード、登録判定用ノードからLOM登録データを作成します。</p>
//     * @param checkNodeList
//     * @throws Exception
//     */
//    private void makeLOMEntryData(NodeList checkNodeList) throws Exception{
//        DebugLog.write(getClass(), "makeLOMEntryData() start" , DebugLog.ROW);
//        for (int i = 0; i < checkNodeList.getLength(); ++i) {
//            Node n = checkNodeList.item(i);
////            System.out.println(this.getClass().getName() + "#getLOMData() NodeName:" + n.getNodeName());
//            NodeList childNodes = n.getChildNodes();
//            DebugLog.write(getClass(), "makeLOMEntryData() Node n:" + n.getLocalName(), DebugLog.ROW);
//            if (childNodes.getLength() != 0) {
//                makeLOMEntryData(childNodes);
//            }
//            //末葉のノードである場合、または、再帰で戻ってきた場合データ取得対象かを判別する。
//            String attriguteValue = getNodeAttributeValue(n, ENTRY);
//            if (attriguteValue != null 
//                    && attriguteValue.equals(ENTRY_ENABLE) == true) {
//                //同じXPathのNodeは既に取得済みなので、一度だけ取得する。
//                if (entryTable.get(createXPath(n) + n.getNodeName()) == null) {
//                    DebugLog.write(getClass(), "makeLOMEntryData() enable Node n:" + n.getLocalName(), DebugLog.ROW);
//	                //データを取得するノードのXPathを作成する。
//	                String xpath = createXPath(n);
//	                //データ取得対象ならデータを格納
//	                setEntryData(xpath,
//	                        		n.getNodeName(),
//	                        		getNodeAttributeValue(n, ENTRY_NAME),
//	                        		getNodeAttributeValue(n, CHOICES));
//	                //取得済みテーブルとしてハッシュテーブルにセットする。
////	                System.out.println("path:" + createXPath(n) + n.getNodeName() + "   " + getNodeAttributeValue(n, ENTRY_NAME));
//	                entryTable.put(createXPath(n) + "/" + n.getNodeName(),
//	                        getNodeAttributeValue(n, ENTRY_NAME));
//                }
//            }
//        }
//        DebugLog.write(getClass(), "getLOMNode() start", DebugLog.ROW);
//    }
    
    /**
     * <p>属性値の取得を行います。</p>
     * @param targetNode 取得対象のノード
     * @param attributeName 取得する属性名
     * @return 取得した属性値（取得できなかった場合はNullを返却）
     */
    private static final String getNodeAttributeValue(Node targetNode, String attributeName) {
        DebugLog.write(EntryDataFactory.class,
                		"getNodeAttributeValue() start Node:" + targetNode.getLocalName() 
                		+ " /attributeName" + attributeName, DebugLog.ROW);
        //属性が存在しない、もしくは属性値が無い場合はNullを返却
        if (targetNode.getAttributes() == null
                || targetNode.getAttributes().getNamedItem(attributeName) == null
                || targetNode.getAttributes().getNamedItem(attributeName).getNodeValue() == null) {
            return null;
        }
        DebugLog.write(EntryDataFactory.class,
                		"getNodeAttributeValue() end Attributes:" + targetNode.getAttributes().getNamedItem(attributeName).getNodeValue() , DebugLog.ROW);
        return targetNode.getAttributes().getNamedItem(attributeName).getNodeValue();
    }
    


    /**
     * <p>ノードのXPathを作成します。</p>
     * @param targetNode XPathを作成するノード
     * @return 作成したXPath
     */
    private static final String createXPath(Node targetNode) {
        DebugLog.write(EntryDataFactory.class, "createXPath() start Node:" + targetNode.getLocalName() , DebugLog.ROW);
        StringBuffer buff = new StringBuffer();
        if (targetNode == null) {
            return null;
        }

//        targetNode = targetNode.getParentNode();
        while(true) {
            if (targetNode.getNodeName().equals("#document")) {
                buff.insert(0, "/");
                break;
            }
            String nodeName = targetNode.getLocalName();
            if (nodeName == null) {
                nodeName = targetNode.getNodeName();
            }
            buff.insert(0, "/:" + nodeName);
            targetNode = targetNode.getParentNode();
        }
        
//        System.out.println("path:" + buff.toString());
        DebugLog.write(EntryDataFactory.class,
                		"createXPath() end path:" + buff.toString() , DebugLog.ROW);
        return buff.toString();
    }

//    /**
//     * <p>パス情報とノード名を元にノードの内容をLOM情報として取り込みます。</p>
//     * @param targetPath
//     * @param nodeName
//     * @param entryName
//     * @param choices
//     * @throws Exception
//     */
//    private void setEntryData(String targetPath, String nodeName, String entryName, String choices) throws Exception{
//        DebugLog.write(getClass(), "setEntryData() start" , DebugLog.ROW);
//        DebugLog.write(getClass(),
//                		"setEntryData() param:" + targetPath + "," + nodeName + "," + entryName + "," + choices,
//                		DebugLog.ROW);
//        System.out.println("setEntryData() start");
//        System.out.println("setEntryData() param:" + targetPath + "," + nodeName + "," + entryName + "," + choices);
//        Node entryNode = ctrl.getNode(lomNode, targetPath);
//        if (entryNode == null) {
//            return;
//        }
//        NodeList childNodes = entryNode.getChildNodes();
//        if (childNodes == null) {
//            return;
//        }
//        System.out.println("setEntryData() 1 childNodes:" + childNodes.getLength());
//
//        //TODO あとで余裕があれば別メソッドに切り出す
//        for (int i = 0; i < childNodes.getLength(); ++i) {
//            Node childNode = childNodes.item(i);
//            System.out.println("name/localNeme:" + childNode.getNodeName() + "/" + childNode.getLocalName());
////            if (childNode.getLocalName() != null
////                    && childNode.getLocalName().equals(nodeName)) {
//            if (childNode.getNodeName().equals("lom:" + nodeName)) {
//                NodeList values = childNode.getChildNodes();
//                String valueString = childNode.getFirstChild().getNodeValue();
//                if (valueString.trim().length() == 0) {
//                    for (int j = 0; j < values.getLength(); ++j) {
//                        Node value = values.item(j);
////                        if (value.getNodeName().equals("string")
////                                || value.getNodeName().equals("langstring")
////                                || value.getNodeName().equals("datetime")
////                                || value.getNodeName().equals("duration")) {
//                        if (value.getNodeName().equals("lom:string")
//                                || value.getNodeName().equals("lom:langstring")
//                                || value.getNodeName().equals("lom:datetime")
//                                || value.getNodeName().equals("lom:duration")) {
//                            valueString = value.getFirstChild().getNodeValue();
//                        }
//                        else if (value.getNodeName().equals("lom:value")) {
//                            NodeList valueChildNodes = value.getChildNodes();
//                            for (int k = 0; k < valueChildNodes.getLength(); ++k) {
//                                Node valueChildNode = valueChildNodes.item(k);
//                                if (valueChildNode != null
//                                        && valueChildNode.getNodeName().equals("langstring")) {
//                                    valueString = valueChildNode.getFirstChild().getNodeValue();
//                                }
//                            }
//                            if (valueString.trim().length() == 0) {
//                                valueString = value.getFirstChild().getNodeValue();
//                            }
//                        }
//                    }
//                }
//                boolean isChoices = false;
//                if (choices != null) {
//                    isChoices = choices.equals(ENTRY_ENABLE);
//                }
//                //XXX ログ
////		        System.out.println("setEntryData() XPath=" + targetPath.toLowerCase() + "/:" + nodeName.toLowerCase());
//		        System.out.println("setEntryData() XPath=" + targetPath.toLowerCase() + "/lom:" + nodeName.toLowerCase());
//                //XXX ログ
//		        System.out.println("Name/NodeValue:" + entryName + "/" +valueString);
//                DebugLog.write(getClass(), "setEntryData()\n XPath=" + targetPath.toLowerCase() + "/" + nodeName.toLowerCase() , DebugLog.ROW);
//                DebugLog.write(getClass(), "Name/NodeValue:" + entryName + "/" +valueString , DebugLog.ROW);
//		        //TODO 属性用にEntryDataUnitのフィールドを追加する(DBに登録する際に、パス内の文字は小文字に変換)。
//		        entryData.add(new EntryDataUnit(targetPath.toLowerCase() + "/lom:" + nodeName.toLowerCase(),
//		                		entryName, 
//		                		valueString,
//		                		isChoices));
//            }
//        }
//        DebugLog.write(getClass(), "setEntryData() end" , DebugLog.ROW);
//    }

    /**
     * LOMデータ取り込み判定用テーブルを作成します。
     * @param node 再帰的に判定されている、LOMデータ取り込み判定用ノード
     */
    private void makeEntryTable(Node node) {
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); ++i) {
//            System.out.println("set:" + entryTable.toString());
            Node childNode = nodeList.item(i);
            String attributeValue = getNodeAttributeValue(childNode, ENTRY);
            if (attributeValue != null
                    && attributeValue.equals(ENTRY_ENABLE)) {
                Vector elements = new Vector();
                elements.add(getNodeAttributeValue(childNode, ENTRY_NAME));
                elements.add(getNodeAttributeValue(childNode, CHOICES));
//                System.out.println("Elements:" + elements.toString());
                String path = createXPath(childNode);
                entryTable.put(path, elements);
                if (isInLine) {
                    String inLinePath = path;
                    inLinePath = inLinePath.substring(1);
                    inLinePath = "//:manifest/:metadata" + inLinePath;
//                    System.out.println("path:" + inLinePath);
                    inLineEntryTable.put(inLinePath, path);
                }
            }
            makeEntryTable(childNode);
        }
    }
    
    /**
     * <p>LOMノード、登録判定用ノードからLOM登録データを作成します。</p>
     * @param lomNodeList
     * @throws Exception
     */
    private void makeLOMEntryData(NodeList lomNodeList) throws Exception {
        for (int i = 0; i < lomNodeList.getLength(); ++i) {
            Node childNode = lomNodeList.item(i);
            String path = createXPath(childNode);
            Vector entryAttribute = null;
//            System.out.println("path:" + path);
            if (isInLine) {
                path = (String) inLineEntryTable.get(path);
            }
            if (path != null){
                entryAttribute = (Vector) entryTable.get(path);
            }
//            System.out.println("path:" + entryAttribute);
            if (entryAttribute != null) {
//                System.out.println("getData:" + path + ":" + entryAttribute.get(0) + ":" + entryAttribute.get(1));
                //データ取得
                NodeList grandchildNodes = childNode.getChildNodes();
                for (int j = 0; j < grandchildNodes.getLength(); ++j) {
                    Node grandchild = grandchildNodes.item(j);
                    String valueString = grandchild.getNodeValue();
                    String nodeName = grandchild.getLocalName();
//                    System.out.println("NodeNameLocal:" + nodeName + ":NodeName:" + grandchild.getNodeName() + ":index:" + j);
                    if (valueString != null && valueString.trim().length() > 0) {
                        valueString = valueString.trim();
                        if (valueString.length() == 0) {
                            valueString = null;
                        }
                    }
                    if (nodeName != null) {
                        if(nodeName.equals("string")
                                || nodeName.equals("langstring")
                                || nodeName.equals("datetime")
                                || nodeName.equals("duration")) {
                            valueString = grandchild.getFirstChild().getNodeValue();
                        }
                        else if (nodeName.equals("value")) {
                            NodeList valueChildNodes = grandchild.getChildNodes();
                            valueString = grandchild.getFirstChild().getNodeValue();
//                            System.out.println("makeLOMEntryData:2.1.10" + valueString);
                            if (valueString == null || valueString.trim().length() == 0) {
                                for (int k = 0; k < valueChildNodes.getLength(); ++k) {
                                    Node valueChildNode = valueChildNodes.item(k);
//                                    System.out.println("makeLOMEntryData:2.1.11 nodeName:" + valueChildNode.getLocalName());
//                                    System.out.println("makeLOMEntryData:2.1.11 nodeName:" + valueChildNode.getNodeName());
                                    String valueNodeName = valueChildNode.getLocalName();
                                    if (valueNodeName != null && valueNodeName.equals("langstring")) {
                                        valueString = valueChildNode.getFirstChild().getNodeValue();
                                    }
                                }
                            }
                            if (valueString != null) {
                                valueString = valueString.toLowerCase();
                            }
                        }
                        else {
                            nodeName = grandchild.getNodeName();
//                            System.out.println("NodeName:" + nodeName + ":Length:" + grandchildNodes.getLength());
                            if (nodeName != null) {
                                if(nodeName.equals("string")
                                        || nodeName.equals("langstring")
                                        || nodeName.equals("datetime")
                                        || nodeName.equals("duration")) {
                                    valueString = grandchild.getFirstChild().getNodeValue();
                                }
                                else if (nodeName.equals("value")) {
                                    NodeList valueChildNodes = grandchild.getChildNodes();
                                    valueString = grandchild.getFirstChild().getNodeValue();
//                                    System.out.println("makeLOMEntryData:2.1.10" + valueString);
                                    if (valueString == null || valueString.trim().length() == 0) {
                                        for (int k = 0; k < valueChildNodes.getLength(); ++k) {
                                            Node valueChildNode = valueChildNodes.item(k);
                                            String valueNodeName = valueChildNode.getLocalName();
                                            if (valueNodeName != null && valueNodeName.equals("langstring")) {
//                                                System.out.println("makeLOMEntryData:2.1.11" + valueChildNode.getFirstChild().getNodeValue());
                                                valueString = valueChildNode.getFirstChild().getNodeValue();
                                            }
                                        }
                                    }
                                    if (valueString != null) {
                                        valueString = valueString.toLowerCase();
                                    }
                                }
//                                System.out.println("makeLOMEntryData:2.1.12");
                            }
                        }
                    }

                    if (valueString != null) {
                        valueString = valueString.trim();
                        if (valueString.length() > 0) {
                            boolean isChoices = false;
                            if (entryAttribute.get(1) != null
                                    && entryAttribute.get(1).equals(ENTRY_ENABLE)) {
                                isChoices = true;
                            }
//                            System.out.println("data:" + path + ":" + entryAttribute.get(0) + ":" + valueString + ":" + isChoices);
                            entryData.add(new EntryDataUnit(path.toLowerCase(),
            		                						(String) entryAttribute.get(0),
            		                						valueString,
            		                						isChoices));
                        }
                    }
                }
            }
            makeLOMEntryData(childNode.getChildNodes());
        }
    }
}
