/*
 * Copyright (c) 2007, Going Dot Com Inc. All rights reserved.
 */

package jp.co.going.xbrl.taxonomy;

import java.io.File;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;

import jp.co.going.xbrl.common.XbException;
import jp.co.going.xbrl.common.XbConstants;
import jp.co.going.xbrl.common.parse.XbPAOImpl;

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

/**
 * タクソノミーをＤＢに格納するクラス
 * 
 * @author Going Dot Com Inc.
 */
abstract public class XbTaxonomyImpl implements XbTaxonomy {

	// システム日付
	private Date system_date;
	// コネクション
    private Connection conn;
    // DAO
    private XbTaxonomyGroupDAO dbaTaxonomyGroupDAO;
    private XbTaxonomyDAO      dbaTaxonomyDAO;
    private XbTaxonomyItemDAO  dbaTaxonomyItemDAO;
    private XbLinkbaseRefDAO   dbaLinkbaseRefDAO;
    private XbItemTypeDAO    dbaItemtypeDAO;
    private XbNextIdDAO    taxGroupIdDAO;
    private XbNextIdDAO    taxIdDAO;
    private XbNextIdDAO    taxItemIdDAO;
    private XbNextIdDAO    linkRefIdDAO;
    private XbItemWorkDAO     itemWorkDAO;
    private XbItemLabelDAO     itemLabelDAO;
    private XbItemTopDAO       itemTopDAO;
    private XbItemOrderDAO     itemOrderDAO;

    private String spec;

    protected ArrayList<String> schemaList = new ArrayList<String>();

    /**
     * コンストラクタ
     * @param conn SQLコネクション
     * @throws XbException
     */
	protected XbTaxonomyImpl(Connection conn, String spec) throws XbException {
        this.conn = conn;
        this.spec = spec;
		// タクソノミーグループアクセスクラス
        dbaTaxonomyGroupDAO = XbTaxonomyGroupDAO.getInstance(conn);
		// タクソノミーアクセスクラス
        dbaTaxonomyDAO = XbTaxonomyDAO.getInstance(conn);
		// タクソノミー科目アクセスクラス
        dbaTaxonomyItemDAO = XbTaxonomyItemDAO.getInstance(conn);
		// リンクベースアクセスクラス
        dbaLinkbaseRefDAO = XbLinkbaseRefDAO.getInstance(conn);
        // アイテムタイプセスクラス
        dbaItemtypeDAO = XbItemTypeDAO.getInstance(conn);
		// グループID取得
        taxGroupIdDAO = XbTaxGroupNextIdDAO.getInstance(conn);
        // タクソノミーID取得
        taxIdDAO = XbTaxNextIdDAO.getInstance(conn);
        // アイテムID取得
        taxItemIdDAO = XbTaxItemNextIdDAO.getInstance(conn);
        // リンクベースID取得
        linkRefIdDAO = XbLinkRefNextIdDAO.getInstance(conn);
        // 親子関係登録
        if (spec.equals(XbConstants.SPECIFICATION_NO_20)) {
            itemWorkDAO =  XbItemWork20DAO.getInstance(conn);
        } else if (spec.equals(XbConstants.SPECIFICATION_NO_21)) {
            itemWorkDAO =  XbItemWork21DAO.getInstance(conn);
        }
        // 表示名登録
        if (spec.equals(XbConstants.SPECIFICATION_NO_20)) {
            itemLabelDAO = XbItemLabel20DAO.getInstance(conn);
        } else if (spec.equals(XbConstants.SPECIFICATION_NO_21)) {
            itemLabelDAO = XbItemLabel21DAO.getInstance(conn);
        }
        // TOP要素登録
        itemTopDAO = XbItemTopDAO.getInstance(conn);
        // 並び順登録
        itemOrderDAO = XbItemOrderDAO.getInstance(conn);
        // Dateクラスインスタンス生成
        system_date = new Date();
    }

    /**
     * タクソノミーグループ情報を設定する
     * @param       String                  帳票名
     * @param       String                  備考
     * @throws      XbException
     * @return      XbTaxonomyGroupObj      タクソノミーグループオブジェクト
     */
    private XbTaxonomyGroupObj setTaxonomyGroup(String grpName, String note) throws XbException {

        XbTaxonomyGroupObj taxGroupObj = new XbTaxonomyGroupObj();

        // タクソノミーグループID
        taxGroupObj.setTaxonomyGroupId(taxGroupIdDAO.getNextValue());
        // タクソノミーグループ名
        taxGroupObj.setTaxonomyGroupName(grpName);
        // 仕様番号
        taxGroupObj.setSpecificationNo(this.spec);
        // 備考
        taxGroupObj.setNote(note);
        // 日付
        taxGroupObj.setCreationDate(system_date);
        // Insert実行
        dbaTaxonomyGroupDAO.insert(taxGroupObj);

        return taxGroupObj;
    }


    /**
     * 取得したXMLファイルをparseしてDOMオブジェクトを作成する
     * @param fileName ファイル名
     * @return DOMオブジェクト
     * @throws XbException
     */
	private Document getDocument(String fileName) throws XbException {
        // パーサの生成
        XbPAOImpl parser = new XbPAOImpl();
        // XMLファイルのパース
        parser.parse(fileName);
        // DOCUMENTノードの取得
        return  parser.getDocument();
    }

    /**
     * 各DBに登録する
     * @param       XbTaxonomyGroupObj          タクソノミーグループオブジェクト
     * @param       XbTaxonomyPramObjList       タクソノミーファイルオブジェクトリスト
     * @throws      SQLException
     * @throws      ClassNotFoundException
     * @throws      Exception
     * @see         なし
     */
    private void registTaxonomyObj(Document doc, XbTaxonomyGroupObj taxGroupObj, String pathName, String fileName, int flg) throws XbException {

        String path = "";
        // 型派生を設定する
        this.setDerivation(doc);
        // タクソノミーを設定する
        XbTaxonomyObj taxObj = this.setTaxonomy(doc, taxGroupObj, fileName, flg);
        // LinkBaseRefを設定する
        XbLinkBaseObj linkPram = this.setLinkbaseref(doc, pathName, taxObj);
        // LinkBase情報を設定する
        XbLinkBase linkBase = new XbLinkBase(this.conn);
        linkBase.setLinkBase(linkPram);
        // タクソノミー科目設定
        setTaxonomyItem(doc, taxObj);
        // インポートファイル
        NodeList lst = doc.getElementsByTagNameNS("*", XbConstants.TAG_IMPORT);
        // 再帰処理を行う
        ArrayList<String> impList = getImportList(lst);
        for ( int i = 0; i < impList.size();i ++) {
            // ファイルからDOMオブジェクト作成
            String file = pathName + "/" + impList.get(i);
            Document document = getDocument(file);
            path = file.substring(0, file.lastIndexOf("/"));
            this.registTaxonomyObj(document, taxGroupObj, path, impList.get(i), 0);
        }
        // インクルードファイル
        NodeList list = doc.getElementsByTagNameNS("*", XbConstants.TAG_INCLUDE);
        // 再帰処理を行う
        ArrayList<String> imcList = getImportList(list);
        for ( int i = 0; i < imcList.size();i ++) {
            // ファイルからDOMオブジェクト作成
            String file = pathName + "/" + imcList.get(i);
            Document document = getDocument(file);
            path = file.substring(0, file.lastIndexOf("/"));
            this.registTaxonomyObj(document, taxGroupObj, path, imcList.get(i), 0);
        }
    }

    /**
     * タクソノミー情報を設定する
     * @param       Document                DOCUMENTノード
     * @param       XbTaxonomyPramObj               タクソノミーオブジェクトリスト
     * @param       XbTaxonomyGroupObj      タクソノミーグループオブジェクト
     * @throws      Exception
     * @see         なし
     * @return      XbTaxonomyObj           タクソノミーオブジェクト
     */
    private XbTaxonomyObj setTaxonomy(Document doc, XbTaxonomyGroupObj taxGroupobj, String fileName, int flg) throws XbException {

        XbTaxonomyObj taxObj = new XbTaxonomyObj();

        // タクソノミーID
        taxObj.setTaxonomyId(taxIdDAO.getNextValue());
        // タクソノミーグループID
        taxObj.setTaxonomyGroupId(taxGroupobj.getTaxonomyGroupId());
        // タクソノミー名
        taxObj.setTaxonomyName(taxGroupobj.getTaxonomyGroupName());
        // タクソノミーファイル名
        taxObj.setTaxonomyFile(fileName.substring(fileName.lastIndexOf("/") + 1));
        // 名前空間を設定する
        this.setNamespase(doc, taxObj);
        // 終端判定フラグ
        taxObj.setTerminatedFlag(flg);
        // 日付
        taxObj.setCreationDate(system_date);
        // Insert実行
        dbaTaxonomyDAO.insert(taxObj);

        return taxObj;
    }

    /**
     * 名前空間を設定する
     * @param       Document            DOCUMENTノード
     * @param       XbTaxonomyObj       オブジェクト
     * @throws      Exception
     * @see         なし
     * @return      なし
     */
    private void setNamespase(Document document, XbTaxonomyObj taxObj) {

        String str_url = "";

        NodeList lst = document.getElementsByTagNameNS("*", XbConstants.TAG_SCHEMA);

        // --------------------------------------------------------------------
        // targetNamespace取得処理
        // --------------------------------------------------------------------
        for (int i = 0;i < lst.getLength();i ++) {
            Node child = lst.item(i);
            NamedNodeMap attrs = child.getAttributes();

            for (int j = 0;j < attrs.getLength();j ++) {
                String node  = attrs.item(j).getNodeName();
                String value = attrs.getNamedItem(node).getNodeValue();

                if(XbConstants.TARGET_NAMESPACE.equals(node)){
                    // targetNamespaceで指定されているURLを保持する
                    str_url = value;
                }
                else if (str_url.equals(value)) {
                    String strwk = null;
                    String sp[] = node.split(":");
                    if (sp.length >= 2) {
                        // 名前空間
                        strwk = sp[1];
                    }
                    // 名前空間
                    taxObj.setNamespace(strwk);
                    // 名前空間URL
                    taxObj.setNamespaceUrl(value);
                }
            }
        }
    }

    /**
     * LinkBaseを設定する
     * @param       Document            DOCUMENTノード
     * @param       XbTaxonomyObj       オブジェクト
     * @throws      Exception
     * @see         なし
     * @return      ArrayList           XbLinkbaserefObjオブジェクトリスト
     */
    private XbLinkBaseObj setLinkbaseref(Document document, String pathName, XbTaxonomyObj taxObj) throws XbException {
        // Linkbaserefタグを取得
        NodeList lst_link = document.getElementsByTagNameNS("*", XbConstants.TAG_LINKBASE);
        // リンクベース取込オブジェクト作成
        XbLinkBaseObj linkParam = new XbLinkBaseObj();
        // タクソノミーIDを設定
        linkParam.setTaxonomyId(String.valueOf(taxObj.getTaxonomyId()));

        // --------------------------------------------------------------------
        // LinkBaseRef取得処理
        // --------------------------------------------------------------------
        for (int i = 0;i < lst_link.getLength();i ++) {
            Node child = lst_link.item(i);
            NamedNodeMap attrs = child.getAttributes();
            XbLinkbaserefObj linkobj = new XbLinkbaserefObj();

            // LinkBaseref ID
            linkobj.setLinkbaserefId(linkRefIdDAO.getNextValue());
            // タクソノミーID
            linkobj.setTaxonomyId(taxObj.getTaxonomyId());
            // タクソノミーグループID
            linkobj.setTaxonomyGroupId(taxObj.getTaxonomyGroupId());

            for (int j = 0;j < attrs.getLength();j ++) {
                String node  = attrs.item(j).getNodeName();
                String value = attrs.item(j).getNodeValue();

                if (XbConstants.ATTRIBUTE_XLINK_ROLE.equals(node)) {
                    linkobj.setLinkbaseRole(value);
                    // カテゴリー設定
                    String category = value.substring(value.lastIndexOf("/") + 1);
                    linkobj.setCategory(category);
                }
                if (XbConstants.ATTRIBUTE_XLINK_HREF.equals(node)) {
                    linkobj.setLinkbaseHref(value);
                }
            }
            // 日付
            linkobj.setCreationDate(system_date);
            // Insert実行
            dbaLinkbaseRefDAO.insert(linkobj);
            // リンクベースファイル設定
            setLinkBaseFile(linkParam, pathName, linkobj);
        }
        return linkParam;
    }

    /**
     * タクソノミー科目を設定する
     * @param       Document            DOCUMENTノード
     * @param       XbTaxonomyObj       オブジェクト
     * @throws      Exception
     * @see         なし
     * @return      なし
     */
    private void setTaxonomyItem(Document document, XbTaxonomyObj taxobj) throws XbException {
        // Elementタグを取得
        NodeList lst = document.getElementsByTagNameNS("*", XbConstants.TAG_ELEMENT);

        // --------------------------------------------------------------------
        // 項型テーブル更新処理
        // --------------------------------------------------------------------
        for (int i = 0;i < lst.getLength();i ++) {
            Node child = lst.item(i);
            XbTaxonomyItemObj taxItemObj = new XbTaxonomyItemObj();
            NamedNodeMap attrs = child.getAttributes();

            // 属性値取得
            for (int j = 0;j < attrs.getLength();j ++) {
                String node  = attrs.item(j).getNodeName();
                String value = attrs.getNamedItem(node).getNodeValue();

                if (XbConstants.ATTRIBUTE_ID.equals(node)) {
                    // id (element tag attrubute)
                    taxItemObj.setItemId(value);
                }
                if (XbConstants.ATTRIBUTE_NAME.equals(node)) {
                    // name (element tag attrubute)
                    taxItemObj.setItemName(value);
                }
                if (XbConstants.ATTRIBUTE_TYPE.equals(node)) {
                    // type (element tag attrubute)
                    taxItemObj.setItemType(this.getSplitAtem(value));
                }
                if (XbConstants.ATTRIBUTE_SUBSTITUTION_GROUP.equals(node)) {
                    // substitutiongroup (element tag attrubute)
                    taxItemObj.setItemSubstitutiongroup(this.getSplitAtem(value));
                }
                if (XbConstants.ATTRIBUTE_BALANCE.equals(node)) {
                    // 貸方(credit)/借方(Debit)
                    taxItemObj.setItemBalance(value);
                }
                if (XbConstants.ATTRIBUTE_NILLABLE.equals(node)) {
                    if (value.equals("true")) {
                        // NULL値許可フラグ(0:false, 1:true)
                        taxItemObj.setItemNillable(1);
                    }else{
                        // NULL値許可フラグ(0:false, 1:true)
                        taxItemObj.setItemNillable(0);
                    }
                }
                if (XbConstants.ATTRIBUTE_ABSTRACT.equals(node)) {
                    if (value.equals("true")) {
                        // 属性フラグ(0:false, 1:true)
                        taxItemObj.setAbstractFlg(1);
                    } else {
                        // 属性フラグ(0:false, 1:true)
                        taxItemObj.setAbstractFlg(0);
                    }
                }
                if (XbConstants.ATTRIBUTE_PERIOD_TYPE.equals(node)) {
                    // 会計期間属性
                    taxItemObj.setItemPeriodType(value);
                }
            }

            // substitutionGroup内容が "link:part" 以外の場合、
            // タクソノミー科目レコードを作成する
            if (!XbConstants.SUBSTITUTION_GROUP_VALUE.equals(taxItemObj.getItemSubstitutiongroup()) && taxItemObj.getItemId() != null) {
                // タクソノミーアイテムID
                taxItemObj.setTaxonomyItemId(taxItemIdDAO.getNextValue());
                // タクソノミーID
                taxItemObj.setTaxonomyId(taxobj.getTaxonomyId());
                // タクソノミーグループID
                taxItemObj.setTaxonomyGroupId(taxobj.getTaxonomyGroupId());
                // ドキュメント(参考資料)
                taxItemObj.setDocumentation(this.getAttributeName(child));
                // 日付
                taxItemObj.setCreationDate(system_date);
                // Insert実行
                dbaTaxonomyItemDAO.insert(taxItemObj);
            }
        }
    }

    /**
     * documentationの"ja"属性のValue値を取得する
     * @param       Node        ノード
     * @throws      なし
     * @see         なし
     * @return      String      Value値
     */
    private String getAttributeName(Node root) {
        Node childnode = root.getFirstChild();
        if (childnode == null) {
            return null;
        }

        // documentationタグを検索する
        ArrayList<Node> ls  = getNode(childnode, XbConstants.TAG_DOCUMENTATION);
        for (int i=0;i < ls.size();i ++) {
            Node nowork = ls.get(i);
            // 属性値取得
            NamedNodeMap attrs = nowork.getAttributes();
            if (attrs.getLength() != 0) {
                for (int j = 0;j < attrs.getLength();j ++) {
                    // xml:lang属性の"ja"のValue値を取得する
                    if (attrs.getNamedItem(attrs.item(j).getNodeName()).getNodeValue().equals("ja")) {
                        String str = getNodeVal(nowork);
                        return str;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 指定したタグ名を持つノードリストを取得する
     * @param       Node            ノード
     * @param       String          タグ名
     * @throws      なし
     * @see         なし
     * @return      ArrayList       ノードリスト
     */
    private ArrayList<Node> getNode(Node node, String nodeName) {
        ArrayList<Node> lst = new ArrayList<Node>();
        if ((node == null) || (nodeName == null) ) {
            return null;
        }

        NodeList ls = node.getChildNodes();
        Node child = null;
        for (int i = 0;i < ls.getLength();i ++) {
            child = ls.item(i);
            if (nodeName.equals(child.getNodeName())) {
                lst.add(child);
            }
        }
        return lst;
    }

    /**
     * 指定したノードのValueを取得する
     * @param       Node        ノード
     * @throws      なし
     * @see         なし
     * @return      String      Value値
     */
    private String getNodeVal(Node node) {
        if (node == null) {
            return null;
        }

        NodeList ls = node.getChildNodes();
        Node child = null;
        for (int i = 0;i < ls.getLength();i ++) {
            child = ls.item(i);
            if (child.getNodeType() == Node.TEXT_NODE) {
                return child.getNodeValue();
            }
        }
        return null;
    }

    /**
     * ":"の第二文字列を返却する
     * @param       String      対照文字列
     * @throws      なし
     * @see         なし
     * @return      String      Value値
     */
    private String getSplitAtem(String str) {
        if (str == null) {
            return null;
        }

        String sp[] = str.split(":");
        if (sp.length >= 2) {
            // 第二文字列
            return sp[1];
        }
        return str;
    }

    /**
     * 勘定科目の順番や表示名を登録する
     * @param grpId タクソノミーグループID
     * @throws XbException
     */
    private void setItemInsert(long grpId) throws XbException {
        // ワークテーブルに親子関係を登録
        itemWorkDAO.insert(grpId);
        // ラベル情報を登録
        itemLabelDAO.setItemLabel(grpId);
        // ＴＯＰ要素を登録
        ArrayList<XbItemTopObj> itemTop = itemTopDAO.setItemTop(grpId);
        // 並び順を登録する
        itemOrderDAO.setItemOrder(itemTop);
    }

    /**
     * 派生したアイテムを登録する
     * @param doc ドキュメント
     * @throws XbException
     */
    private void setDerivation(Document doc) throws XbException {
        // 制限タグ取得
        NodeList restLst = doc.getElementsByTagNameNS("*", XbConstants.TAG_COMPLEX_TYPE);
        for (int i = 0;i < restLst.getLength();i ++) {
            Node node = restLst.item(i);
            NamedNodeMap attrs = node.getAttributes();
            // name属性の値を取得
            if (attrs.getLength() != 0) {
                String item = attrs.getNamedItem("name").getNodeValue();
                Node child = node.getFirstChild();
                // item追加
                this.addItemType(child, item);
            }
        }
    }

    /**
     * アイテムの型を登録する
     * @param item 追加するアイテム名
     * @throws XbException
     */
    private void addItemType(Node node, String item) throws XbException {
        while (node != null) {
            // simpleContent
            if (node.getNodeType() == Node.ELEMENT_NODE &&
                    XbConstants.TAG_SIMPLECONTENT.equals(this.getSplitAtem(node.getNodeName()))) {
                Node child = node.getFirstChild();
                this.addItemType(child, item);
            }
            // restriction or extension
            if (node.getNodeType() == Node.ELEMENT_NODE &&
                    (XbConstants.TAG_RESTRICTION.equals(this.getSplitAtem(node.getNodeName())) ||
                            XbConstants.TAG_EXTENSION.equals(this.getSplitAtem(node.getNodeName())))) {
                // 属性値取得
                NamedNodeMap attrs = node.getAttributes();
                // 派生元の型を取得
                String itemName = this.getSplitAtem(attrs.getNamedItem("base").getNodeValue());
                // insert
                dbaItemtypeDAO.insert(item, itemName, this.spec, system_date);
            }
            node = node.getNextSibling();
        }
    }

    /**
     *  importされているスキーマを取得するか判定する
     * @param value importスキーマ名
     * @param version バージョン
     * @return true : スキーマ取得 false : 取得しない
	 */
    protected boolean isAddFlg(String value, String version) {
        String instance = "";
        String linkbase = "";

        if (version.equals(XbConstants.SPECIFICATION_NO_20)) {
            instance = XbConstants.INSTANCE_NAME20;
            linkbase = XbConstants.LINKBASE_NAME20;
        } else if (version.equals(XbConstants.SPECIFICATION_NO_21)) {
            instance = XbConstants.INSTANCE_NAME21;
            linkbase = XbConstants.LINKBASE_NAME21;
        }

        if (value.equals(instance) || value.equals(linkbase)) {
            return false;
        }

        for (int i = 0;i < this.schemaList.size();i ++) {
            if (value.equals(schemaList.get(i))) {
                return false;
            }
        }

        return true;
    }

    /**
     * タクソノミーの取込みを行う
     * @param       grpName         帳票名
     * @param       note            備考
     * @param       fileName        末端タクソノミーファイル
     * @throws      XbException
     */
	public final void registerTaxonomy(String grpName, String note, String fileName) throws XbException {
		// タクソノミーグループを設定する
		XbTaxonomyGroupObj taxGroupObj = this.setTaxonomyGroup(grpName, note);
        // pathとfileを取得する
        File name = new File(fileName);
        String pathName = name.getParent();
        String fName = name.getName();
        // ファイルを読み込んでDOMを作成する
        Document doc = this.getDocument(fileName);
        // タクソノミーオブジェクトを各DBに設定する
		this.registTaxonomyObj(doc, taxGroupObj, pathName, fName, 1);
		// アイテム情報を登録する
        this.setItemInsert(taxGroupObj.getTaxonomyGroupId());
    }

	abstract protected void setLinkBaseFile(XbLinkBaseObj linkObj, String pathName, XbLinkbaserefObj linkobj) throws XbException;

    abstract protected ArrayList<String> getImportList(NodeList importList);
}