/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : DataManager.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */
package ciss.phase_viewer.inputinterface;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import ciss.phase_viewer.inputinterface.filestate.FileStateChangeEvent;
import ciss.phase_viewer.inputinterface.filestate.FileStateListener;
import ciss.phase_viewer.inputinterface.filestate.FileStateObserver;
import ciss.phase_viewer.inputinterface.inputinterfacetable.InputInterfaceTable;
import ciss.phase_viewer.inputinterface.inputinterfacetable.InputInterfaceTableSpecManager;

public class DataManager implements Serializable, FileStateListener {

    private static final long serialVersionUID = 1L;

    private InputInterface input;

    private InputInterfaceBlock rootBlock;

    private String fileName;

    // regular expressions for parsing
    public static String RBRACE = "\\}";

    public static String ENTRY = "[\\w[*][(][)][=][+][-][.][,][<][>][\\s][\\\\][:][/][\"]]+"
            + "[^#]";

    public static String LBRACE = "[\\w[\\\\][-][.][\\s]]+\\{";

    public static String LBRACE_NO_IDENT = "\\{";

    public static String SPACE = "\\s";

    public static String[] COMMENT = { "[!]", "/{2,}" };

    public static String BIKKURIHASH = "!#";

    public static String HASH = "#";

    public static String AST = "*";

    // token kinds
    public static String blockStart = "blockStart";

    public static String blockEnd = "blockEnd";

    public static String entry = "entry";

    public static String comment = "comment";

    // table-related
    public static String tableStart = "tableStart";

    public static String tableEntry = "tableEntry";

    public static String tableEnd = "tableEnd";

    // identifiers starting with HASH
    public static String TAG = "tag";

    public static String DEFAULT = "default";

    public static String UNITS = "units";

    // special identifiers
    public static String[] CELL_VECTORS = { "a_vector", "b_vector", "c_vector" };

    private boolean inTable = false;

    private String[] tableIdentifiers;

    private Vector tableData = new Vector();

    private EntryTokenizer tokenizer = new EntryTokenizer();

    private Logger logger;

    // private boolean caseSensitive = false;
    private boolean caseSensitive = true; // experimental

    private InputInterfaceTableSpecManager manager;

    private String fullName = "";

    private URL url;

    public DataManager(String fileName) {
        this.fileName = fileName;

        input = new InputInterface();
        rootBlock = new InputInterfaceBlock();
        rootBlock.setName("filename comes here");
        logger = Logger.getLogger(this.getClass());
        this.url = DataManager.class
                .getResource("/ciss/phase_viewer/inputinterface/inputinterfacetable/DefaultInputInterfaceTableSpec.xml");
        manager = new InputInterfaceTableSpecManager(url, input);
    }

    private FileStateObserver obs;

    public DataManager(String fileName, URL url) {
        this.fileName = fileName;
        this.url = url;

        input = new InputInterface();
        manager = new InputInterfaceTableSpecManager(url, input);

        rootBlock = new InputInterfaceBlock();
        rootBlock.setName("filename comes here");
        logger = Logger.getLogger(this.getClass());
        registerToFileStateObserver();
    }

    public DataManager(String fileName, URL url, boolean initObserver) {
        this.fileName = fileName;
        this.url = url;

        input = new InputInterface();
        manager = new InputInterfaceTableSpecManager(url, input);

        rootBlock = new InputInterfaceBlock();
        rootBlock.setName("filename comes here");
        logger = Logger.getLogger(this.getClass());
        if (!initObserver)
            return;
        registerToFileStateObserver();
    }

    /**
     * ファイル状態を監視するオブジェクトからこのオブジェクトを登録解除する.
     * 
     */
    public void unregisterFromFileStateObserver() {
        if (obs == null)
            return;
        obs.removeFileStateListener(this);
    }

    /**
     * ファイル状態を監視するオブジェクトにこのオブジェクトを登録する. ただしすでに登録済みな場合なにもしない.
     */
    public void registerToFileStateObserver() {
        if (obs != null)
            return;
        logger.debug("registering to FileStateObserver");
        File dirdir = new File(this.fileName).getParentFile();
        if (dirdir != null && dirdir.exists() && dirdir.isDirectory()) {
            obs = new FileStateObserver(new File(fileName).getParent(),
                    new String[] { new File(fileName).getName() }, 10L);
            obs.addFileStateListener(this);
        }
    }

    public DataManager(String fileName, boolean caseSensitive) {
        this.fileName = fileName;
        this.caseSensitive = caseSensitive;

        input = new InputInterface();
        rootBlock = new InputInterfaceBlock();
        rootBlock.setName("filename comes here");
        logger = Logger.getLogger(this.getClass());
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public void setWSisDelimiter(boolean wid) {
        tokenizer.setWSisDelimiter(wid);
    }

    private int blcount = 0;

    private int numBlocks = 0;

    public boolean parse() {
        String line;

        if (manager != null
                && input.getInputInterfaceTableSpecManager() == null) {
            input.setInputInterfaceTableSpecManager(manager);
        }

        input.setFileName(fileName);
        if (!new File(fileName).exists())
            return false;

        input.initialize();
        rootBlock = input.getRootList();
        input.setUpdateUndoStack(false);
        BufferedReader br = null;
        boolean valtoks = true;
        Vector temp = new Vector();

        try {
            br = new BufferedReader(new FileReader(fileName));
            while ((line = br.readLine()) != null) {
                temp.add(line);
            }
        } catch (Exception exc) {
            if (valtoks) {
                logger.error("failed to parse: " + fileName);
                exc.printStackTrace();
                input.clearUndoRedo();
                input.setUpdateUndoStack(true);
            }
            return false;
        } finally {
            try {
                br.close();
            } catch (IOException ioe) {
                logger.error("failed to close file: " + fileName);
            }
        }

        for (int it = 0; it < temp.size(); it++) {
            line = (String) temp.get(it);
            Vector tokens = tokenize(line);
            // if (tokens.size() == 0 ) {
            // logger.debug("no valid tokens.");
            // valtoks=false;
            // return false;
            // }
            for (int i = 0; i < tokens.size(); i++) {
                String[] token = (String[]) tokens.get(i);
                String tokenKind = token[1];
                logger.debug("token: " + token[0]);
                logger.debug("tokenKind: " + token[1]);
                if (tokenKind.equals(blockStart)) {
                    startBlock(token[0]);
                } else if (tokenKind.equals(blockEnd)) {
                    if (inTable) {
                        registerTableData();
                        inTable = false;
                    }
                    endBlock();
                } else {
                    registerEntry(token[0], token[1]);
                }
            }
        }

        if (numBlocks == 0)
            return false;

        if (valtoks && blcount == 0) {
            logger.debug("calling InputInterface.replaceAst()");
            input.replaceAst();
            input.clearUndoRedo();
            input.intialized();
            input.setUpdateUndoStack(true);
        }

        return true;
    }

    /**
     * InputInterfaceへの参照を取得.
     * 
     * @return InputInterfaceへの参照.
     */
    public InputInterface getInputInterface() {
        return this.input;
    }

    private void startBlock(String token) {
        logger.debug("starting block: " + token);
        String blockName = token.replaceAll(LBRACE_NO_IDENT, "").trim();
        if (fullName.trim().length() == 0) {
            fullName = blockName;
        } else {
            fullName += "." + blockName;
        }
        InputInterfaceBlock block = new InputInterfaceBlock(blockName,
                new String(fullName));
        input.addBlock(block);
        blcount++;
        numBlocks++;
    }

    private void endBlock() {
        // InputInterfaceBlockEnd blockEnd = new InputInterfaceBlockEnd();
        // input.addBlockEnd(blockEnd);
        int ind = fullName.lastIndexOf(".");
        if (ind < 0) {
            ind = 0;
        }
        fullName = fullName.substring(0, ind);
        input.selectParentBlock();
        blcount--;
    }

    private void registerTableData() {
        InputInterfaceTable tableEntry = new InputInterfaceTable(input
                .getCurrentBlock().getName(), tableIdentifiers, tableData);
        input.replaceEntry((InputInterfaceEntry) tableEntry, null, false);
        logger.debug("registered table data");
        logger.debug("name of table: " + input.getCurrentBlock().getName());
    }

    private void registerEntry(String token, String tokenKind) {
        tokenizer.setTokens(token, tokenKind);
        Vector results = tokenizer.getResults();
        if (results == null)
            return;
        for (int i = 0; i < results.size(); i++) {
            String[] res = (String[]) results.get(i);
            if (tokenKind.equals(tableStart)) {
                tableIdentifiers = res;
            } else if (tokenKind.equals(tableEntry)) {
                tableData.addElement(res);
            } else if (tokenKind.equals(entry)) {
                InputInterfacePrimitiveEntry entry = new InputInterfacePrimitiveEntry(
                        Utils.stripDQ(res[0]), Utils.stripDQ(res[1]),
                        Utils.stripDQ(res[2]));
                entry.setDoubleQuoted(Utils.isDoubleQuoted(res[1]));
                entry.setFullName(input.getCurrentBlock().getFullName() + "."
                        + Utils.stripDQ(res[0]));
                input.replaceEntry((InputInterfaceEntry) entry, null, false);
            } else if (tokenKind.equals(DEFAULT)) {
                InputInterfaceDefaults defs = new InputInterfaceDefaults(input
                        .getCurrentBlock().getName(), res);
                defs.setFullName(input.getCurrentBlock().getFullName());
                input.replaceEntry((InputInterfaceEntry) defs, null, false);
            } else if (tokenKind.equals(UNITS)) {
                InputInterfaceUnits units = new InputInterfaceUnits(input
                        .getCurrentBlock().getName(), res);
                units.setFullName(input.getCurrentBlock().getFullName());
                input.replaceEntry((InputInterfaceEntry) units, null, false);
            }
        }
    }

    /**
     * 行をトークン化する. Vectorに情報を放り込んでいく. 格納される情報は大きさ2のString配列で, 0番目の要素がトークンそのもの,
     * 1番目の要素がトークンタイプとなる.
     * 
     * @param line
     *            トークン化したい文字列.
     */
    private Vector tokenize(String line) {
        Vector tokens = new Vector();
        if (!caseSensitive) {
            line = line.toLowerCase().trim();
        } else {
            line = line.trim();
        }

        // !#は#にreplace
        String line_new = line.replaceAll(BIKKURIHASH, HASH);
        if (!line_new.equals(line)) {
            logger.debug("replaced !# to #");
            line = line_new;
        }

        // コメント処理
        for (int j = 0; j < COMMENT.length; j++) {
            String[] comments = line.split(COMMENT[j]);
            // for (int i = 1; i < comments.length; i++) {
            // String[] token = new String[2];
            // token[0] = comments[i];
            // token[1] = comment;
            // tokens.addElement(token);
            // logger.debug("found comment: " + token[0]);
            // }
            if (comments.length >= 1) {
                line = comments[0];
            } else {
                return tokens;
            }
        }

        // LBRACE
        Pattern pattern = Pattern.compile(LBRACE);
        Matcher matcher = pattern.matcher(line);
        while (matcher.find()) {
            String[] token = new String[2];
            token[0] = matcher.group();
            token[1] = blockStart;
            tokens.addElement(token);
            logger.debug("found lbrace: " + token[0]);
        }

        // #を含む行; 特殊な処理が必要.
        if (line.indexOf(HASH) >= 0) {
            String[] token = parseLinesWithHash(line);
            tokens.addElement(token);
            return tokens;
        }

        pattern = Pattern.compile(ENTRY);
        matcher = pattern.matcher(line);
        String entryName = new String();
        String notify = new String();

        if (inTable) {
            entryName = tableEntry;
            notify = "found table entry: ";
        } else {
            entryName = entry;
            notify = "found primitive entry: ";
        }

        while (matcher.find() && blcount != 0) {
            String[] token = new String[2];
            String matchString = matcher.group();
            if (!matchString.trim().endsWith("{")) {
                token[0] = matchString.replaceAll("}", "");
                token[1] = entryName;
                tokens.addElement(token);
                logger.debug(notify + matchString);
            }
        }

        // RBRACE
        pattern = Pattern.compile(RBRACE);
        matcher = pattern.matcher(line);
        while (matcher.find()) {
            String[] token = new String[2];
            token[0] = matcher.group();
            token[1] = blockEnd;
            tokens.addElement(token);
            logger.debug("found rbrace: " + token[0]);
        }

        return tokens;
    }

    private String[] parseLinesWithHash(String line) {
        String[] token = new String[2];
        int hashStart = line.indexOf(HASH);
        String parseString = line.substring(hashStart + 1, line.length());
        if (parseString.startsWith(TAG)) {
            token[0] = line;
            token[1] = tableStart;
            inTable = true;
            tableData = new Vector();
        } else if (parseString.startsWith(DEFAULT)) {
            token[0] = line;
            token[1] = DEFAULT;
        } else if (parseString.startsWith(UNITS)) {
            token[0] = line;
            token[1] = UNITS;
        } else {
            token[0] = line;
            token[1] = comment;
        }

        return token;
    }

    public static void main(String[] args) {
        /**
         * テスト用メイン. コード例. \code DataManager datamanager = new
         * DataManager(fileName); datamanager.parse(); InputInterface
         * inputinterface = datamanager.getInputInterface();
         * inputinterface.selectRoot(); inputinterface.selectBlock("Control"); .
         * . . \endcode
         */
        String fileName;
        DataManager datamanager = null;
        if (args.length != 0) {
            datamanager = new DataManager(args[0]);
            datamanager.parse();
        } else {
            System.err.println("must specify file name!");
            System.exit(0);
        }

        InputInterface input = datamanager.getInputInterface();
        input.scanList();
        if (args.length >= 2) {
            input.saveTo(new File(args[1]));
        }
    }

    private boolean iDidIt = false;

    public void fileStateChanged(FileStateChangeEvent fe) {
        logger.debug("at fileStateChanged");
        if (input.block)
            return;
        if (!new File(fileName).exists())
            return;
        if (!input.iSavedIt && url != null) {
            input.selectRoot();
            parse();
            input.setState(false);
        } else {
            input.iSavedIt = false;
        }
    }

}

class EntryTokenizer implements Serializable {

    private static Logger logger = Logger.getLogger(EntryTokenizer.class
            .getName());

    private boolean table = false;

    private String token = new String();

    private int numCols;

    private boolean wid = true;

    private String tokenKind = DataManager.entry;

    public EntryTokenizer(String token, String tokenKind) {
        this.token = token;
        this.tokenKind = tokenKind;
    }

    public EntryTokenizer() {
    }

    public void setTokens(String token, String tokenKind) {
        this.token = token;
        this.tokenKind = tokenKind;
    }

    public void setWSisDelimiter(boolean wid) {
        this.wid = wid;
    }

    public Vector getResults() {
        Vector returnVector = new Vector();
        if (tokenKind.equals(DataManager.tableStart)
                || tokenKind.equals(DataManager.tableEntry)) {
            returnVector = tokenizeTable();
        } else if (tokenKind.equals(DataManager.entry)) {
            returnVector = tokenizePrimitive();
        } else if (tokenKind.equals(DataManager.DEFAULT)) {
            returnVector = tokenizeDefaults();
        } else if (tokenKind.equals(DataManager.UNITS)) {
            returnVector = tokenizeUnits();
        } else {
            returnVector = null;
        }
        return returnVector;
    }

    private Vector tokenizeTable() {
        Vector tokens = new Vector();

        if (tokenKind.equals(DataManager.tableStart)) {
            int hashStart = token.indexOf(DataManager.HASH);
            token = token.substring(hashStart + 4, token.length()).trim();
            Vector vec = tokenizeSpaceOrDoubleQuote(token);
            String[] st = new String[vec.size()];
            for (int i = 0; i < vec.size(); i++) {
                st[i] = (String) vec.get(i);
            }
            tokens.addElement(st);
            numCols = st.length;
        } else if (tokenKind.equals(DataManager.tableEntry)) {
            String[] ret = new String[numCols];
            Vector vec = tokenizeSpaceOrDoubleQuote(token);
            String[] st = new String[vec.size()];
            for (int i = 0; i < vec.size(); i++) {
                st[i] = (String) vec.get(i);
            }
            int count = numCols;
            if (st.length < numCols) {
                count = st.length;
            }
            for (int i = 0; i < count; i++) {
                ret[i] = st[i].toString();
            }
            for (int i = count; i < numCols; i++) {
                ret[i] = DataManager.AST;
            }
            tokens.addElement(ret);
        }

        return tokens;

    }

    private Vector tokenizePrimitive() {
        Vector tokens = new Vector();
        String[] first = token.split(",");
        for (int i = 0; i < first.length; i++) {
            String[] tokenString = new String[3];
            String[] second = first[i].trim().split("=");
            boolean isCellVec = false;
            if (second.length >= 2) {
                tokenString[0] = second[0];
                for (int cell = 0; cell < DataManager.CELL_VECTORS.length; cell++) {
                    if (second[0].trim().equals(DataManager.CELL_VECTORS[cell])) {
                        isCellVec = true;
                    }
                }
                Vector vector = new Vector();
                if (isCellVec) {
                    vector.addElement(second[1]);
                } else if (!wid) {
                    vector.addElement(second[1]);
                } else {
                    vector = tokenizeSpaceOrDoubleQuote(second[1]);
                }

                for (int ii = 0; ii < vector.size(); ii++) {
                    logger.debug("tokenized: " + vector.get(ii));
                }
                if (vector.size() == 1) {
                    tokenString[1] = (String) vector.get(0);
                    tokenString[2] = "";
                } else if (vector.size() > 1) {
                    tokenString[1] = (String) vector.get(0);
                    tokenString[2] = (String) vector.get(1);
                } else {
                    tokenString[1] = "";
                    tokenString[2] = "";
                }
            } else if (second.length == 1) {
                tokenString[0] = second[0];
                tokenString[1] = "";
                tokenString[2] = "";
            } else {
                tokenString[0] = "";
                tokenString[1] = "";
                tokenString[2] = "";
            }

            tokens.addElement(tokenString);
        }

        return tokens;
    }

    private Vector tokenizeUnits() {
        Vector tokens = new Vector();
        int hashStart = token.indexOf(DataManager.HASH);
        token = token.substring(hashStart + 6, token.length()).trim();
        String[] st = token.split("\\s+");
        tokens.addElement(st);
        return tokens;
    }

    private Vector tokenizeDefaults() {
        Vector tokens = new Vector();
        int hashStart = token.indexOf(DataManager.HASH);
        token = token.substring(hashStart + 9, token.length()).trim();
        String[] st = token.split(",");
        String[] st2 = new String[st.length];
        for (int i = 0; i < st.length; i++) {
            st2[i] = st[i].trim();
        }
        tokens.addElement(st2);
        return tokens;
    }

    static Vector tokenizeSpaceOrDoubleQuote(String string) {
        string = string.trim();
        String SPACE = "[\\s]+";
        char QUOTE = '\"';
        // String IN_QUOTE = "\""+"[^\"]+" +"\"";
        String IN_QUOTE = "\"" + ".+" + "\"";
        boolean inQuote = false;
        boolean inToken = true;
        String tokenString = "";
        Vector tokenVector = new Vector();

        if (Utils.isDoubleQuoted(string)) {
            tokenVector.addElement(new String(string));
            logger.debug("entry is double quoted.");
            return tokenVector;
        }

        String separator = SPACE;

        Vector quoteVector = new Vector();
        Vector normalVector = new Vector();

        Pattern pattern = Pattern.compile(IN_QUOTE);
        Matcher matcher = pattern.matcher(string);
        String quoteStart = "0";
        String normalStart = "0";
        String quoteEnd = "0";
        String normalEnd = "0";
        for (int i = 0; i < string.length(); i++) {
            if (string.charAt(i) == QUOTE) {
                if (!inQuote) {
                    quoteStart = String.valueOf(i);
                    normalEnd = String.valueOf(i);
                    if (Integer.parseInt(normalEnd)
                            - Integer.parseInt(normalStart) > 0) {
                        String[] normal = { normalStart, normalEnd };
                        normalVector.addElement(normal);
                    }
                } else {
                    quoteEnd = String.valueOf(i + 1);
                    normalStart = String.valueOf(i + 1);
                    String[] quote = { quoteStart, quoteEnd };
                    quoteVector.addElement(quote);
                }
                inQuote = !inQuote;
            }
        }
        normalEnd = String.valueOf(string.length());
        String[] normal = { normalStart, normalEnd };
        normalVector.addElement(normal);
        Vector map = new Vector();
        Vector substringVector = new Vector();
        Vector isQuote = new Vector();

        for (int i = 0; i < normalVector.size(); i++) {
            String[] indeces = (String[]) normalVector.get(i);
            String substring = string.substring(Integer.parseInt(indeces[0]),
                    Integer.parseInt(indeces[1])).trim();
            if (substring.length() > 0) {
                substringVector.addElement(substring);
                map.addElement(indeces[0]);
                isQuote.addElement(new Boolean(false));
            }

        }
        for (int i = 0; i < quoteVector.size(); i++) {
            String[] indeces = (String[]) quoteVector.get(i);
            String substring = string.substring(Integer.parseInt(indeces[0]),
                    Integer.parseInt(indeces[1])).trim();
            if (substring.length() > 0) {
                substringVector.addElement(substring);
                map.addElement(indeces[0]);
                isQuote.addElement(new Boolean(true));
            }
        }

        String[] retString = new String[substringVector.size()];
        String[] mapString = new String[map.size()];
        Boolean[] isQuoteBoolean = new Boolean[isQuote.size()];

        for (int i = 0; i < retString.length; i++) {
            retString[i] = (String) substringVector.get(i);
            mapString[i] = (String) map.get(i);
            isQuoteBoolean[i] = (Boolean) isQuote.get(i);
        }

        for (int i = 0; i < mapString.length - 1; i++) {
            for (int j = i + 1; j < mapString.length; j++) {
                int ii = Integer.parseInt(mapString[i]);
                int jj = Integer.parseInt(mapString[j]);
                if (jj < ii) {
                    String tmpi = mapString[i];
                    String tmpj = mapString[j];
                    mapString[i] = tmpj;
                    mapString[j] = tmpi;
                    tmpi = retString[i];
                    tmpj = retString[j];
                    retString[i] = tmpj;
                    retString[j] = tmpi;
                    Boolean bi = isQuoteBoolean[i];
                    Boolean bj = isQuoteBoolean[j];
                    isQuoteBoolean[i] = bj;
                    isQuoteBoolean[j] = bi;
                }
            }
        }

        for (int i = 0; i < retString.length; i++) {
            if (isQuoteBoolean[i].booleanValue()) {
                tokenVector.addElement(retString[i]);
            } else {
                String[] split = retString[i].split("\\s+");
                for (int j = 0; j < split.length; j++) {
                    tokenVector.addElement(split[j]);
                }
            }

        }

        return tokenVector;
    }

}
