/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : HostList.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.ssh.hosts;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.Vector;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

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

import ciss.phase_viewer.common.ConstParameters;
import ciss.phase_viewer.common.TaggedString;
import ciss.phase_viewer.common.Utils;
import ciss.phase_viewer.jdom.MyElement;

public class HostList extends LinkedList {

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

    private final String FS = ConstParameters.FS;
    // private String hostsFile = System.getProperty("user.home") + FS +
    // ".phase-viewer" + FS + "hosts";
    private String hostsFile = System.getProperty("user.home") + FS
            + ".phase-viewer" + FS + "hosts.xml";
    private PasswordManager pass;

    public static HostList getHostList() {
        if (list == null) {
            list = new HostList();
        }
        return list;
    }

    public static String[] getHostNames() {
        HostList li = getHostList();
        String[] hosts = new String[li.size()];
        for (int i = 0; i < li.size(); i++)
            hosts[i] = li.getHostInfo(i).getName();

        return hosts;
    }

    private HostList() {
        if (!(new File(hostsFile)).exists()) {
            createHostsFile();
        }
        try {
            pass = new PasswordManager();
        } catch (Exception exc) {
            logger.error(exc);
            logger.warn("can't manage password ...");
        }

        boolean foundLocalhost = false;
        Document document = ciss.phase_viewer.jdom.XMLUtils
                .getDocumentFromFile(new File(hostsFile));
        Element rootElement = document.getRootElement();
        java.util.List list = rootElement.getChildren();

        for (int i = 0; i < list.size(); i++) {
            Element ele = (Element) list.get(i);
            String nam = ele.getName();
            HostInfo inf = new HostInfo(nam);
            java.util.List children = ele.getChildren();
            for (int j = 0; j < children.size(); j++) {
                Element child = (Element) children.get(j);
                String key = child.getName();
                String val = MyElement.decode(child.getTextTrim());
                inf.setProperty(key, val);
            }
            if (nam.trim().equals("localhost")) {
                this.addFirst(inf);
            } else {
                this.addLast(inf);
            }
        }

        try {
            readPasswordFile();
        } catch (IOException ioe) {
            logger.debug("couldn't read password files");
        }
    }

    private void createHostsFile() {
        Document document = new Document();
        Element rootElement = new Element("hosts");
        Element localHost = new Element("localhost");
        Element address = new Element("address").setText(MyElement
                .encode("127.0.0.1"));
        Element userName = new Element("username").setText(MyElement
                .encode(System.getProperty("user.name")));
        Element bindir = new Element("bindir").setText(MyElement.encode(System
                .getProperty("pviewer.home")
                + System.getProperty("file.separator") + "bin"));
        Element mpiDir = null;
        if (Utils.isWindows()) {
            String vendor = System.getProperty("pviewer.vendor");
            if (vendor != null && vendor.equalsIgnoreCase("asms"))
                mpiDir = new Element("mpidir").setText(MyElement.encode(System
                        .getProperty("pviewer.home")
                        + System.getProperty("file.separator") + "mpi"));
        }
        Element basedir = new Element("basedir").setText(MyElement
                .encode(System.getProperty("user.home")
                        + System.getProperty("file.separator")
                        + "phase-viewer-projects"));
        Element isdefault = new Element("default").setText(MyElement
                .encode("true"));
        localHost.addContent(address);
        localHost.addContent(userName);
        localHost.addContent(bindir);
        localHost.addContent(basedir);
        localHost.addContent(isdefault);
        if (mpiDir != null)
            localHost.addContent(mpiDir);
        rootElement.addContent(localHost);
        document.setRootElement(rootElement);
        ciss.phase_viewer.jdom.XMLUtils.saveDocumentTo(document, hostsFile);
    }

    /**
     * デフォルトで使用すべきホストを取得. 無い場合ローカルホストを返す
     * 
     * @return デフォルトのホスト
     */
    public HostInfo getDefaultHost() {
        for (int i = 0; i < size(); i++) {
            String foo = getHostInfo(i).getProperty("default");
            if (foo != null && foo.equalsIgnoreCase("true")) {
                return getHostInfo(i);
            }
        }
        return getHostInfo("localhost");
    }

    public HostInfo getHostInfo(int i) {
        HostInfo info;
        if (i == -1) {
            return null;
        }
        try {
            info = (HostInfo) get(i);
        } catch (Exception e) {
            logger.warn("failed to get HostInfo at: " + i);
            return null;
        }
        return info;
    }

    public HostInfo getHostInfo(String hostName) {
        for (int i = 0; i < size(); i++) {
            HostInfo hinfo = getHostInfo(i);
            if (hinfo.getName().equals(hostName)) {
                return hinfo;
            }
        }
        return null;
    }

    /**
     * アドレスからホスト情報を取得
     * 
     * @param address
     *            ホストのアドレス
     * @return 対応するホスト情報
     */
    public HostInfo getHostInfoFromAddress(String address) {
        for (int i = 0; i < size(); i++) {
            HostInfo hinfo = getHostInfo(i);
            TaggedString[] ts = hinfo.getHostProperties();
            if (ts == null || ts.length == 0) {
                continue;
            }
            for (int j = 0; j < ts.length; j++) {
                if (ts[j].getTag().equals("address")
                        && ts[j].getValue().equals(address)) {
                    return hinfo;
                }
            }
        }
        return null;
    }

    protected void debug() {
        for (int i = 0; i < size(); i++) {
            logger.debug("for HostInfo " + i);
            this.getHostInfo(i).debug();
        }
    }

    public void save() {
        Document document = ciss.phase_viewer.jdom.XMLUtils
                .getDocumentFromFile(new File(hostsFile));
        if (document == null) {
            createHostsFile();
            document = ciss.phase_viewer.jdom.XMLUtils
                    .getDocumentFromFile(new File(hostsFile));
        }

        Element rootElement = document.getRootElement();
        java.util.List list = rootElement.getChildren();
        for (int i = 0; i < list.size(); i++) {
            Element el = (Element) list.get(i);
            boolean valid = false;
            for (int j = 0; j < size(); j++) {
                if (getHostInfo(j).getName().equals(el.getName())) {
                    valid = true;
                }
            }
            if (!valid)
                rootElement.removeContent(el);
        }

        for (int i = 0; i < size(); i++) {
            HostInfo info = getHostInfo(i);
            String name = info.getName();
            Element elemHost = rootElement.getChild(name);
            if (elemHost == null) {
                elemHost = new Element(name);
                rootElement.addContent(elemHost);
            }
            logger.debug("host: " + elemHost.getName());
            TaggedString[] props = info.getHostProperties();
            for (int j = 0; j < props.length; j++) {
                String key = props[j].getTag();
                String val = props[j].getValue();
                Element child = elemHost.getChild(key);
                if (child == null) {
                    child = new Element(key);
                    elemHost.addContent(child);
                }
                child.setText(MyElement.encode(val));
                logger.debug("hostinfo: " + key + ", " + val);
            }
        }

        ciss.phase_viewer.jdom.XMLUtils.saveDocumentTo(document, hostsFile);

        Vector passVector = new Vector();
        Vector rempassVector = new Vector();

        for (int i = 0; i < size(); i++) {
            HostInfo info = getHostInfo(i);
            String ssavepass = info.getProperty("savepass");
            boolean savepass = false;

            if (ssavepass != null && ssavepass.equalsIgnoreCase("true")) {
                savepass = true;
            }

            if (savepass) {
                String name = info.getName();
                String passString = info.getPasswordString();
                passVector.addElement(new TaggedString(name, passString));
            } else {
                String name = info.getName();
                rempassVector.addElement(name);
            }

        }

        if (passVector.size() >= 1) {
            try {
                writePasswordFile(passVector);
            } catch (IOException ioe) {
                logger.error("failed to save encrypted passwords to disk");
            }
        }

        if (rempassVector.size() >= 1) {
            String passwordDir = System.getProperty("user.home") + FS
                    + ".phase-viewer" + FS + "passwords";
            for (int ii = 0; ii < rempassVector.size(); ii++) {
                (new File(passwordDir + FS
                        + (String) rempassVector.elementAt(ii))).delete();
            }
        }

    }

    protected void writePasswordFile(Vector passVector) throws IOException {
        try {
            pass = new PasswordManager();
        } catch (Exception exc) {
            logger.error("failed to create password manager");
            return;
        }

        String passwordDir = System.getProperty("user.home") + FS
                + ".phase-viewer" + FS + "passwords";
        (new File(passwordDir)).mkdir();

        for (int i = 0; i < passVector.size(); i++) {
            TaggedString data = (TaggedString) passVector.get(i);
            String name = data.getTag().trim();
            String passwd = data.getValue().trim();
            byte[] passb = null;
            try {
                passb = pass.getEncryptedPassword(passwd);
            } catch (Exception exc) {
                logger.error("failed to encrypt password");
                continue;
            }

            if (passb != null) {
                BufferedOutputStream bostream = new BufferedOutputStream(
                        new FileOutputStream(passwordDir + FS + name));
                bostream.write(passb, 0, passb.length);
                bostream.flush();
            }
        }

    }

    protected void readPasswordFile() throws IOException {
        File passwdDir = new File(System.getProperty("user.home") + FS
                + ".phase-viewer" + FS + "passwords");
        if (!passwdDir.exists()) {
            return;
        }
        File[] passwdFiles = passwdDir.listFiles();
        try {
            pass = new PasswordManager();
        } catch (Exception exc) {
            logger.error(exc);
            logger.warn("can't manage password ...");
            return;
        }
        for (int i = 0; i < passwdFiles.length; i++) {
            String name = passwdFiles[i].getName();
            BufferedInputStream bistream = new BufferedInputStream(
                    new FileInputStream(passwdDir.getAbsolutePath() + FS + name));
            byte[] pas = new byte[bistream.available()];
            bistream.read(pas);
            String planepass = "";
            try {
                planepass = pass.getPlanePassword(pas);
                logger.debug("plain password: " + planepass);
            } catch (Exception exc) {
                logger.debug("couldn't get plain password...");
                continue;
            }
            for (int j = 0; j < size(); j++) {
                HostInfo info = getHostInfo(j);
                if (info.getName().trim().equals(name)) {
                    info.setPasswordString(planepass);
                    info.setSavePassword(true);
                }
            }
        }
    }

}

class PasswordManager {
    private SecretKeySpec key;
    private Cipher cipher;

    protected PasswordManager() throws NoSuchAlgorithmException,
            NoSuchPaddingException {
        String keyword = "ad=v|/v3af89t" + System.getProperty("user.name")
                + "fasbgh0<rr8892";
        byte[] initkey = { (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
                (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08 };
        byte[] tmpkey = keyword.getBytes();
        for (int i = 0; i < tmpkey.length && i < initkey.length; i++) {
            initkey[i] = tmpkey[i];
        }

        key = new SecretKeySpec(initkey, "DES");
        cipher = Cipher.getInstance("DES");
    }

    protected byte[] getEncryptedPassword(String password)
            throws InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException {
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] secret = cipher.doFinal(password.getBytes());
        return secret;
    }

    protected String getPlanePassword(byte[] secret)
            throws InvalidKeyException, IllegalBlockSizeException,
            BadPaddingException {
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] password = cipher.doFinal(secret);
        return new String(password);
    }
}
