/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2005/12/19, 14:52
!  AUTHOR(S): KOGA, Junichiro
!  File : ScriptUtils.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.scripting;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Set;
import java.util.Vector;

import org.apache.log4j.Logger;

import bsh.EvalError;
import bsh.Interpreter;
import ciss.phase_viewer.common.ConstParameters;
import ciss.phase_viewer.common.ExternalProgramExecuter;
import ciss.phase_viewer.common.Utils;
import ciss.phase_viewer.inputinterface.DataManager;
import ciss.phase_viewer.inputinterface.InputInterface;

/**
 * スクリプト操作に便利と思しきstaticメソッドはここへ.
 * 
 * @author
 */
public class ScriptUtils {
    private static Logger logger = Logger
            .getLogger(ScriptUtils.class.getName());

    private static String BEGIN_DESCRIPTION = "BEGIN_DESCRIPTION";

    private static String END_DESCRIPTION = "END_DESCRIPTION";

    private static String BEGIN_INPUT = "BEGIN_INPUT";

    private static String END_INPUT = "END_INPUT";

    private static String BEGIN_NECESSARY_FILES = "BEGIN_NECESSARY_FILES";

    private static String END_NECESSARY_FILES = "END_NECESSARY_FILES";

    private static String BEGIN_BCOMMENT = "/*";

    private static String END_BCOMMENT = "*/";

    /** static methods only... */
    private ScriptUtils() {
    }

    /**
     * 引数で与えた文字よりInputInterfaceオブジェクトを作成する. 仕組み上, ディスク上のファイル
     * をparseするようになっているので一旦temporaryなファイルに書き出してからInputInterface オブジェクトを作成する.
     * スクリプトの入力は, ブロックコメント中に下記のような仕組みで記述されていることを前提としている. PHASE入力ファイルを例とすると,
     * BEGIN_INPUT control{ condition=initial max_iteration = 1000 } accuracy {
     * ... } END_INPUT
     * 
     * @param file
     *            ここで与えたファイルよりInputInterface文字列を抽出し, オブジェクトを作成する.
     */
    public static InputInterface getInputInterfaceFrom(String file) {
        BufferedReader reader = null;
        boolean inInput = false;
        String inputString = "";
        try {
            reader = new BufferedReader(new FileReader(file));
            String string = "";
            while ((string = reader.readLine()) != null) {
                if (string.trim().equals(BEGIN_INPUT)) {
                    inInput = true;
                    continue;
                } else if (string.trim().equals(END_INPUT)) {
                    break;
                }
                if (inInput) {
                    inputString += string
                            + System.getProperty("line.separator");
                }
            }
        } catch (IOException ioe) {
            logger.error(ioe);
            return null;
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException ioe2) {
                logger.error(ioe2);
                return null;
            }
        }
        File tmpFile = getTmpFile();
        logger.debug("tmpfile name: " + tmpFile.getAbsolutePath());
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(tmpFile));
            writer.write(inputString, 0, inputString.length() - 1);
        } catch (IOException ioe) {
            logger.error(ioe);
            return null;
        } finally {
            try {
                writer.close();
            } catch (IOException ioe2) {
                logger.error(ioe2);
                return null;
            }
        }

        DataManager dm = new DataManager(tmpFile.getAbsolutePath());
        dm.parse();
        InputInterface inputInterface = dm.getInputInterface();
        tmpFile.delete();
        if (inputInterface != null) {
            inputInterface.writeNullValue(true);
        }
        return inputInterface;
    }

    private static HashMap getNecessaryFiles(String file) {
        Vector fromFile = new Vector();
        Vector toFile = new Vector();

        BufferedReader reader = Utils.getReader(file);
        if (reader == null)
            return null;

        String delim = "\\-\\>";
        boolean inCopyNecessary = false;
        String line = "";
        try {
            while ((line = reader.readLine()) != null) {
                if (line.trim().startsWith(BEGIN_NECESSARY_FILES)) {
                    inCopyNecessary = true;
                    continue;
                } else if (line.trim().startsWith(END_NECESSARY_FILES)) {
                    break;
                }
                if (inCopyNecessary) {
                    String[] ar = line.split(delim);
                    if (ar == null || ar.length < 2)
                        continue;
                    fromFile.add(ar[0]);
                    toFile.add(ar[1]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            reader.close();
        } catch (IOException ioe) {
        }

        if (fromFile.size() == 0)
            return null;

        String[] fromStr = new String[fromFile.size()];
        String[] toStr = new String[toFile.size()];
        fromFile.copyInto(fromStr);
        toFile.copyInto(toStr);

        HashMap ret = new HashMap();
        ret.put("from", fromStr);
        ret.put("to", toStr);
        return ret;
    }

    private static String FS = ConstParameters.FS;

    private static String scriptDir = System.getProperty("user.dir") + FS
            + ".phase-viewer" + FS + "scripts";

    /**
     * 引数のスクリプトが必要とするファイルのコピーを行う.
     * 
     * @param file
     *            対象となるスクリプト.
     * @param targetDir
     *            プロジェクトのディレクトリー.
     * @return
     */
    public static boolean copyNecessaryFiles(String file, String targetDir) {
        File scriptDirFile = new File(scriptDir);
        if (!scriptDirFile.exists() || !scriptDirFile.isDirectory()) {
            logger.error("invalid script dir: " + scriptDir);
            return false;
        }
        HashMap nefile = getNecessaryFiles(file);
        if (nefile == null)
            return false;

        String[] fromFile = (String[]) nefile.get("from");
        String[] toFile = (String[]) nefile.get("to");

        boolean ret = true;
        for (int i = 0; i < fromFile.length; i++) {
            String from = scriptDir + FS + fromFile[i];
            String to = targetDir + FS + toFile[i];
            logger.debug("from, to: " + from + ", " + to);
            try {
                ExternalProgramExecuter.copyBinary(from, to);
            } catch (IOException ioe) {
                ret = false;
            }
        }
        return ret;
    }

    /**
     * スクリプトを削除する際, 必要だったファイルも削除する, そんなメソッド.
     * 
     * @param file
     *            対象となるスクリプトファイル
     * @param targetDir
     *            プロジェクトのターゲットディレクトリー
     * @return 失敗した場合false.
     */
    public static boolean removeNecessaryFiles(String file, String targetDir) {
        HashMap nefile = getNecessaryFiles(file);
        if (nefile == null)
            return false;

        String[] to = (String[]) nefile.get("to");

        boolean ret = false;
        for (int i = 0; i < to.length; i++) {
            ret = new File(targetDir + FS + to[i]).delete();
        }

        return ret;
    }

    /**
     * 当該スクリプトファイルの説明を記述した文字列を取得する. 説明は, ブロックコメント中に下記のように記述されていることを想定している.
     * BEGIN_DESCRIPTION 説明文... END_DESCRIPTION
     * 
     * @param file
     *            説明文を抽出したいファイル.
     */
    public static String getDescriptionFrom(String file) {
        if (new File(file).isDirectory()) {
            return "";
        }
        BufferedReader reader = null;
        boolean inDescription = false;
        String descriptionString = "";
        try {
            reader = new BufferedReader(new FileReader(file));
            String string = "";
            while ((string = reader.readLine()) != null) {
                if (string.trim().equals(BEGIN_DESCRIPTION)) {
                    inDescription = true;
                    continue;
                } else if (string.trim().equals(END_DESCRIPTION)) {
                    break;
                }
                if (inDescription) {
                    descriptionString += string
                            + System.getProperty("line.separator");
                }
            }
        } catch (IOException ioe) {
            logger.error(ioe);
            return "";
        } finally {
            try {
                reader.close();
            } catch (IOException ioe2) {
                logger.error(ioe2);
                return "";
            }
        }
        return descriptionString;
    }

    /**
     * 各種ヘッダー情報のないスクリプトテキストを返す. 要は, ブロックコメントをstripする.
     * 本格的なJavaTokenizerを目指すつもりは全然ないので注意.
     * 
     * @param file
     *            スクリプトテキストを抽出したいファイルのファイル名
     * @return スクリプトのテキスト
     */
    public static String getScriptText(String file) {
        BufferedReader reader = null;
        String ret = "";
        try {
            String string = "";
            reader = new BufferedReader(new FileReader(file));
            boolean inBlock = false;
            while ((string = reader.readLine()) != null) {
                if (string.trim().indexOf(BEGIN_BCOMMENT) != -1) {
                    inBlock = true;
                }
                if (!inBlock) {
                    ret += string + System.getProperty("line.separator");
                }
                if (string.trim().indexOf(END_BCOMMENT) != -1) {
                    inBlock = false;
                }
            }
        } catch (IOException ioe) {
            logger.error(ioe);
        } finally {
            try {
                reader.close();
            } catch (IOException ioe1) {
                logger.error(ioe1);
            }
        }
        return ret;
    }

    public static File getTmpFile() {
        String now = String.valueOf(Calendar.getInstance().getTimeInMillis());
        String tmpDir = System.getProperty("java.io.tmpdir");
        String tmpFile = tmpDir + System.getProperty("file.separator") + "tmp"
                + now;
        return new File(tmpFile);
    }

    private static String getNecessaryFilesStr(String file) {
        BufferedReader rd = Utils.getReader(file);
        String ret = "";
        try {
            boolean inNec = false;
            String line = "";
            while ((line = rd.readLine()) != null) {
                if (line.startsWith(BEGIN_NECESSARY_FILES))
                    inNec = true;
                if (inNec)
                    ret += line + "\n";
                if (line.startsWith(END_NECESSARY_FILES))
                    break;
            }
        } catch (IOException ioe) {
        }

        try {
            rd.close();
        } catch (IOException ioe) {
        }

        return ret;
    }

    public static boolean saveScript(String file,
            InputInterface inputInterface, String description) {
        BufferedWriter writer = null;
        File tmp = ciss.phase_viewer.common.Utils.getTmpFile();
        String nefi = getNecessaryFilesStr(file);
        String script = getScriptText(file);
        inputInterface.writeValueDelimitedByColon(true);
        try {
            ciss.phase_viewer.common.ExternalProgramExecuter.copyBinary(file,
                    tmp.getAbsolutePath());
            new File(file).delete();

            writer = new BufferedWriter(new FileWriter(file));
            /* write description */
            writer.write(BEGIN_BCOMMENT + "\n", 0, BEGIN_BCOMMENT.length() + 1);
            writer.write(BEGIN_DESCRIPTION + "\n", 0,
                    BEGIN_DESCRIPTION.length() + 1);
            writer.write(description);
            writer.write(END_DESCRIPTION + "\n", 0,
                    END_DESCRIPTION.length() + 1);

            /* write input */
            writer.write(BEGIN_INPUT + "\n", 0, BEGIN_INPUT.length() + 1);
            String input = inputInterface.toString();
            writer.write(input, 0, input.length());
            writer.write(END_INPUT + "\n", 0, END_INPUT.length() + 1);

            /* write file names of 'necessary files' */
            writer.write(nefi, 0, nefi.length());

            writer.write(END_BCOMMENT + "\n", 0, END_BCOMMENT.length() + 1);

            /* write the script itself */
            writer.write(script, 0, script.length());
        } catch (Exception exc) {
            try {
                logger.error("write failed; restoring backup...");
                ciss.phase_viewer.common.ExternalProgramExecuter.copyBinary(
                        tmp.getAbsolutePath(), file);
            } catch (IOException ioe) {
                logger.error("failed to restore backup file.");
                ioe.printStackTrace();
            }
        } finally {
            tmp.delete();
            try {
                writer.close();
            } catch (IOException ioe2) {
                logger.error(ioe2);
            }
        }
        return true;
    }

    /**
     * 引数のBeanShellスクリプトを実行する. この場合, 配列の全てのスクリプトファイルが同じInterpreterで実行される.
     * 
     * @param scriptPath
     *            スクリプトのパス; 配列の全スクリプトが同一のインタープリター上で実行される.
     * @param properties
     *            インタープリターに渡したいプロパティーを収めたHashMap
     * @return スクリプトを走らせた結果得られるObject; 多くの場合無意味ともいえる.
     */
    public static Object runScript(String[] scriptPath, HashMap properties) {
        Object obj = null;
        Interpreter interpreter = new Interpreter();
        try {
            Set keySet = properties.keySet();
            Object[] keys = keySet.toArray();
            if (keys != null) {
                for (int i = 0; i < keys.length; i++)
                    interpreter
                            .set(keys[i].toString(), properties.get(keys[i]));
            }
            obj = interpreter
                    .eval("importCommands(\"ciss.phase_viewer.scripting.commands\")");
            for (int i = 0; i < scriptPath.length; i++)
                interpreter.source(scriptPath[i]);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (EvalError e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 引数のBeanShellスクリプトを実行する.
     * 
     * @param scriptPath
     *            実行したいスクリプト.
     * @param properties
     *            Interpreterに予めセットしておきたいオブジェクトのハッシュマップ
     * @return スクリプトを走らせた結果得られるObject.
     */
    public static Object runScript(String scriptPath, HashMap properties) {
        return runScript(new String[] { scriptPath }, properties);
    }

}
