/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/06/02, 14:04
!  AUTHOR(S): KOGA, Junichiro
!  File : ChaseFileManager.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.file;

import java.io.File;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;

import ciss.phase_viewer.jdom.XMLUtils;
import ciss.phase_viewer.jobcontrol.JobController;
import ciss.phase_viewer.jobcontrol.JobControllerElement;
import ciss.phase_viewer.projectbrowser.ProjectInfo;
import ciss.phase_viewer.ssh.hosts.HostInfo;
import ciss.phase_viewer.ssh.hosts.HostList;

/**
 * ChaseFileを保持, ハードディスクとあれこれしたり, など.
 * 
 * @author
 */
public abstract class ChaseFileManager implements JobControllerElement {
    private Logger logger = Logger.getLogger(ChaseFileManager.class.getName());

    protected File fileSpecFile;
    protected URL specFile;
    protected Element fileInf;
    protected HostInfo hostInfo;

    protected Document fileInfoDocument;
    protected ProjectInfo projectInfo;

    public static String BASEDIR = "basedir";
    public static String BLOCK = "block";

    protected Element[] file_templates;
    protected Vector file_vector = new Vector();
    protected LinkedHashMap chaseFileInfoSpec = new LinkedHashMap();

    protected String outputDirectory = ""; // 出力ファイルのディレクトリー; PHASEは常に'.',
                                           // Flecsなんかは入力依存.

    /**
     * コンストラクター
     * 
     * @param hostInfo
     *            ホスト情報
     * @param specFile
     *            ファイルの属性などを定義するXMLファイルへのURL
     * @param fileSpecFile
     *            (不要な場合も有り得るけど)ファイルのパスなどを解決する
     *            ために必要なファイルへのパス(PHASEならfile_names.dataファイル). いらないならnullでよい.
     */
    public ChaseFileManager(HostInfo hostInfo, URL specFile, File fileSpecFile) {
        this.hostInfo = hostInfo;
        this.specFile = specFile;
        this.fileSpecFile = fileSpecFile;
        if (fileSpecFile != null)
            logger.debug("specFile: " + specFile.toString() + " fileSpecFile: "
                    + fileSpecFile.getAbsolutePath());
        parseSpecFile();
    }

    public void setFileSpecFile(File fileSpecFile) {
        this.fileSpecFile = fileSpecFile;
    }

    /**
     * コンストラクター; ホスト情報のデフォルトはlocalhost.
     * 
     * @param specFile
     *            ファイルの属性などを定義するXMLファイルへのURL
     * @param fileSpecFile
     *            (不要な場合も有り得るけど)ファイルのパスなどを解決する
     *            ために必要なファイルへのパス(PHASEならfile_names.dataファイル).
     */
    public ChaseFileManager(URL specFile, File fileSpecFile) {
        this(HostList.getHostList().getHostInfo("localhost"), specFile,
                fileSpecFile);
    }

    private void parseSpecFile() {
        Document document = XMLUtils.getDocumentFromURL(specFile);
        Element fileinf = null;
        if (document == null) {
            logger.fatal("FATAL ERROR invalid fileinfo file");
            return;
        }
        Element rootElement = document.getRootElement();
        if (rootElement == null) {
            logger.fatal("FATAL ERROR invalid fileinfo file");
            return;
        }
        java.util.List fileinfos = rootElement.getChildren("fileinfo");
        if (fileinfos == null || fileinfos.size() == 0) {
            logger.fatal("no fileinfos in: " + specFile.toString());
            return;
        }
        for (int i = 0; i < fileinfos.size(); i++) {
            Element finfo = (Element) fileinfos.get(i);
            Attribute att = finfo.getAttribute("fileinfoname");
            if (att == null) {
                logger.fatal("invalid fileinfo.");
                return;
            }
            if (att.getValue().equals(getName())) {
                fileinf = finfo;
            }
        }
        if (fileinf == null) {
            logger.fatal("couldn't find an appropriate file info.");
            return;
        }

        Element fispec = fileinf.getChild("fileinfospec");
        java.util.List fispecList = fispec.getChildren();
        for (int i = 0; i < fispecList.size(); i++) {
            Element ele = (Element) fispecList.get(i);
            ChaseFileInfoSpec cfispec = new ChaseFileInfoSpec(ele);
            chaseFileInfoSpec.put(cfispec.getName(), cfispec);
        }

        java.util.List li = fileinf.getChildren("file");
        file_templates = new Element[li.size()];
        for (int i = 0; i < li.size(); i++) {
            file_templates[i] = (Element) li.get(i);
        }
    }

    public ChaseFileInfoSpec[] getFileInfoSpecs() {
        Collection col = chaseFileInfoSpec.values();
        ChaseFileInfoSpec[] specs = new ChaseFileInfoSpec[col.size()];
        col.toArray(specs);
        return specs;
    }

    public ChaseFileInfoSpec getFileInfoSpec(Object key) {
        Object obj = chaseFileInfoSpec.get(key);
        if (obj instanceof ChaseFileInfoSpec) {
            return (ChaseFileInfoSpec) obj;
        }
        return null;
    }

    /**
     * このファイルマネージャーに関連付けられたProjectInfoをセット;
     * ProjectInfoのsetChaseFileManagerメソッドからしか呼ばれないようにする!!
     * 
     * @param projectInfo
     *            上記の通り
     */
    public void setProjectInfo(ProjectInfo projectInfo) {
        this.projectInfo = projectInfo;
        logger.debug("at setProjectInfo");
        fileInfoDocument = JobController.getJobControlDocument(projectInfo,
                this);
        logger.debug("got fileInfoDocument...");
        if (fileInfoDocument != projectInfo.getProjectPropertyDocument()) {
            // logger.error("something is wrong....");
            projectInfo.setProjectPropertyDocument(fileInfoDocument);
        } else {
            logger.debug("fileTransferDocument == projectInfo.getProjectPropertyDocument()");
        }
        save();
    }

    /**
     * このファイルマネージャーに関連付けられたProjectInfoをゲット
     * 
     * @return 上記の通り
     */
    public ProjectInfo getProjectInfo() {
        return this.projectInfo;
    }

    /**
     * 登録されているファイルから, 識別子とファイル名を頼りに探す. ただしファイル名として空白ないしnullを渡すと識別子のみを頼りにする.
     * 
     * @param ident
     *            探しているファイルの識別子
     * @param fname
     *            探しているファイルのファイル名
     * @return 探しているファイル.
     */
    private ChaseFile findFile(String ident, String fname) {
        boolean useFname = true;
        if (fname == null || fname.trim().length() == 0) {
            useFname = false;
        }
        for (int i = 0; i < file_vector.size(); i++) {
            ChaseFile cfi = (ChaseFile) file_vector.get(i);
            if (!useFname) {
                if (ident.matches(cfi.getIdentifier().getValue())) {
                    return cfi;
                }
            } else {
                if (ident.matches(cfi.getIdentifier().getValue())
                        && fname.equals(cfi.getFileName())) {
                    return cfi;
                }
            }
        }
        return null;
    }

    /**
     * ファイルを足す.
     * 
     * @param element
     *            ファイル情報が記述してあるElement
     * @return 足されたファイル.
     */
    public ChaseFile addFile(Element element) {
        ChaseFile cfi = new ChaseFile(element);
        fileInf.addContent(element);
        file_vector.addElement(cfi);
        return cfi;
    }

    /**
     * ファイルを足す.
     * 
     * @param ident
     *            足すファイルの識別子
     * @param key
     *            足すファイルの情報の識別子; null可
     * @param value
     *            足すファイルの情報; null可
     */
    public ChaseFile addFileByKey(String ident, String key, String value) {
        for (int i = 0; i < file_templates.length; i++) {
            String ide = file_templates[i].getChildTextTrim("identifier");
            if (ident.matches(ide)) {
                logger.debug("at addFileByKey. ident: " + ident + " key: "
                        + key + " value: " + value);
                Element elem = (Element) file_templates[i].clone();
                if (key != null && value != null)
                    elem.getChild(key).setText(value);
                fileInf.addContent(elem);
                ChaseFile cfi = new ChaseFile(elem, chaseFileInfoSpec);
                file_vector.addElement(cfi);
                return cfi;
            }
        }
        return null;
    }

    /**
     * ファイルを足す.
     * 
     * @param relative_path
     *            相対パス. project directoryからの相対パスを指定する.
     * @param ident
     *            識別子
     * @param filename
     *            ファイル名.
     * @return 足したファイル.
     */
    public ChaseFile addFile(String relative_path, String ident, String filename) {
        for (int i = 0; i < file_templates.length; i++) {
            String ide = file_templates[i].getChildTextTrim("identifier");
            if (ident.matches(ide)) {
                Element elem = (Element) file_templates[i].clone();
                fileInf.addContent(elem);
                ChaseFile cfi = new ChaseFile(elem, chaseFileInfoSpec);
                cfi.update("relative_path", relative_path);
                cfi.update("filename", filename);
                file_vector.addElement(cfi);
                return cfi;
            }
        }
        return null;
    }

    /**
     * 指定の場所にファイルを足す
     * 
     * @param ind
     *            ファイルを足す場所; ただし現在登録されているファイルの数より大きい場合末尾に足す.
     * @param relative_path
     *            相対パス; project directoryからの相対パスを指定.
     * @param ident
     *            識別子
     * @param filename
     *            ファイル名
     * @return
     */
    public ChaseFile addFileAt(int ind, String relative_path, String ident,
            String filename) {
        for (int i = 0; i < file_templates.length; i++) {
            String ide = file_templates[i].getChildTextTrim("identifier");
            if (ident.matches(ide)) {
                Element elem = (Element) file_templates[i].clone();
                fileInf.addContent(ind, elem);
                ChaseFile cfi = new ChaseFile(elem, chaseFileInfoSpec);
                cfi.update("relative_path", relative_path);
                cfi.update("filename", filename);
                if (ind < file_vector.size() && ind >= 0)
                    file_vector.add(ind, cfi);
                else
                    file_vector.add(cfi);
                return cfi;
            }
        }
        return null;
    }

    /**
     * すでにあるファイルを更新する. ファイル名も渡すので擬ポテンシャルファイルのように 複数有り得るファイルの更新も可. 無い場合がんばって足す.
     * 
     * @param ident
     *            更新したいファイルの識別子
     * @param fname
     *            更新したいファイルのファイル名
     * @param key
     *            更新したい情報の識別子
     * @param val
     *            更新したい値.
     */
    public void updateFile(String ident, String fname, String key, String val) {
        ChaseFile cfi = findFile(ident, fname);
        if (cfi == null) {
            addFileByKey(ident, key, val);
            return;
        }
        cfi.update(key, val);
    }

    /**
     * すでにあるファイルを更新する. ファイル名も渡すので擬ポテンシャルファイルのように 複数有り得るファイルの更新も可.
     * 値が真偽値の場合こちらを利用する.
     * 
     * @param ident
     *            更新したいファイルの識別子
     * @param fname
     *            更新したいファイルのファイル名
     * @param key
     *            更新したい情報の識別子
     * @param val
     *            更新したい値.
     */
    public void updateFile(String ident, String fname, String key, boolean b) {
        String val = Boolean.toString(b);
        updateFile(ident, fname, key, val);
    }

    /**
     * すでにあるファイルを更新する. ただし, 擬ポテンシャルのように複数有り得るファイル は一番最初にヒットしたものにしか適用されない.
     * 
     * @param ident
     *            更新したいファイルの識別子.
     * @param key
     *            更新したい情報の識別子.
     * @param val
     *            更新したい値.
     */
    public void updateFile(String ident, String key, String val) {
        updateFile(ident, null, key, val);
    }

    /**
     * すでにあるファイルを更新する. ただし, 擬ポテンシャルのように複数有り得るファイル は一番最初にヒットしたものにしか適用されない.
     * 値が真偽値の場合はこちら.
     * 
     * @param ident
     *            更新したいファイルの識別子.
     * @param key
     *            更新したい情報の識別子.
     * @param b
     *            更新したい値.
     */
    public void updateFile(String ident, String key, boolean b) {
        String val = Boolean.toString(b);
        updateFile(ident, key, val);
    }

    /**
     * ファイルを削除.
     * 
     * @param remfile
     *            削除したいファイル.
     */
    public void removeFile(Object file) {
        for (int i = 0; i < file_vector.size(); i++) {
            Object obj = file_vector.get(i);
            if (obj == file) {
                logger.debug("found file to remove...");
                file_vector.remove(i);
                if (file instanceof ChaseFile) {
                    ((ChaseFile) file).getElement().detach();
                }
                break;
            }
        }
    }

    /**
     * ファイルを取得. 識別子によっては複数有り得るので配列を返す. 識別子に合致するファイルが無い場合nullを返す.
     * 
     * @param ident
     *            欲しいファイルの識別子.
     * @return 識別子に合致するファイル
     */
    public ChaseFile[] getFiles(String ident) {
        Vector tmp = new Vector();
        for (int i = 0; i < file_vector.size(); i++) {
            ChaseFile cf = (ChaseFile) file_vector.get(i);
            if (ident.matches(cf.getIdentifier().getValue())) {
                tmp.addElement(cf);
            }
        }
        if (tmp.size() == 0) {
            addFileByKey(ident, null, null);
            for (int i = 0; i < file_vector.size(); i++) {
                ChaseFile cf = (ChaseFile) file_vector.get(i);
                if (ident.matches(cf.getIdentifier().getValue())) {
                    tmp.addElement(cf);
                }
            }
            if (tmp.size() == 0)
                return null;
        }
        ChaseFile[] ret = new ChaseFile[tmp.size()];
        tmp.copyInto(ret);

        return ret;
    }

    /**
     * ファイルを取得. 一番最初にヒットしたファイルを返す. 識別子に合致するファイルが無い場合, 可能ならばデフォルトを作成,
     * できない場合nullを返す.
     * 
     * @param ident
     *            欲しいファイルの識別子.
     * @return 識別子に合致するファイル
     */
    public ChaseFile getFile(String ident) {
        for (int i = 0; i < file_vector.size(); i++) {
            ChaseFile cf = (ChaseFile) file_vector.get(i);
            if (ident.matches(cf.getIdentifier().getValue())) {
                return cf;
            }
        }
        ChaseFile ret = addFileByKey(ident, null, null);
        save();
        return ret;
    }

    /**
     * 登録されている全ファイルを取得.
     * 
     * @return 全ファイルの配列
     */
    public ChaseFile[] getFiles() {
        ChaseFile[] ret = new ChaseFile[file_vector.size()];
        file_vector.copyInto(ret);
        return ret;
    }

    /**
     * このオブジェクトが持っているホスト情報を取得.
     * 
     * @return ホスト情報
     */
    public HostInfo getHostInfo() {
        return this.hostInfo;
    }

    /**
     * このオブジェクトが持っているホスト情報をセット.
     * 
     * @param ホスト情報
     *            .
     */
    public void setHostInfo(HostInfo hostInfo) {
        this.hostInfo = hostInfo;
    }

    public void save() {
        saveSub();
        saveFileInfo();
    }

    /**
     * ここでの情報を, ディスクへ保存. PHASEの場合, 差し当たり"file_names.data"かな.
     */
    protected abstract void saveSub();

    /**
     * 初期化ルーチンの最後に呼ばれる.
     */
    protected abstract void postInit();

    /**
     * FileInfo Elementを読み終わったあとに呼ばれる.
     */
    protected abstract void postRead();

    /**
     * phase-viewer.xml("JobControlFile"と呼んでいる)をディスクに保存.
     */
    protected void saveFileInfo() {
        if (fileInfoDocument == null) {
            logger.error("fileInfoDocument is NNNNNUUUULLLLL!!!!");
        }
        logger.debug("projectdir at this point: "
                + projectInfo.getProjectDirectory());
        JobController.saveJobControlFile(projectInfo.getProjectDirectory(),
                fileInfoDocument);
    }

    /**
     * このFileManagerの名前を返す. たとえば, 設定ファイルから必要な部分を取り出したりするのに使う.
     * 
     * @return 名前
     */
    public abstract String getName();

    public void initJobControlDocument(Document doc) {
        Element root = doc.getRootElement().getChild(JobController.JOBCONTROL);
        Element fileInfo = new Element("fileinfo");
        for (int i = 0; i < file_templates.length; i++) {
            String fname = file_templates[i].getChildTextTrim("filename");
            String defname = file_templates[i].getChildTextTrim("defaultname");
            if (fname != null && fname.trim().length() != 0 || defname != null
                    && defname.trim().length() != 0) {
                fileInfo.addContent((Element) file_templates[i].clone());
            }
        }
        root.addContent(fileInfo);
        postInit();
    }

    /**
     * 'output'用のディレクトリーのパスを取得.
     */
    public String getOutputDir() {
        return outputDirectory;
    }

    /**
     * documentから転送情報などを読み込む... 更新すべき情報とそうでない情報が ごちゃごちゃになってしまっている.
     */
    public boolean readJobControlDocument(Document doc) {
        Element root = doc.getRootElement();
        if (root == null) {
            logger.debug("no root element...");
            return false;
        }
        Element jobControlElement = root.getChild(JobController.JOBCONTROL);
        if (jobControlElement == null) {
            logger.debug("no jobcontrol element...");
            return false;
        }
        Element elem = jobControlElement.getChild("fileinfo");
        if (elem == null) {
            logger.debug("no fileinfo...");
            return false;
        }

        fileInf = elem;
        java.util.List files = elem.getChildren("file");
        if (files == null || files.size() == 0) {
            return false;
        }

        for (int i = 0; i < files.size(); i++) {
            Element file = (Element) files.get(i);
            logger.debug("ident: " + file.getChildTextTrim("identifier"));
            file_vector.addElement(new ChaseFile(file, chaseFileInfoSpec));
        }

        postRead();
        return true;
    }

}
