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

import java.io.File;
import java.io.IOException;
import java.util.Vector;

import ciss.phase_viewer.common.ExternalProgramExecuter;
import ciss.phase_viewer.common.Utils;
import ciss.phase_viewer.file.ChaseFile;
import ciss.phase_viewer.ssh.SessionCreator;
import ciss.phase_viewer.ssh.filechooser.RemoteFile;
import ciss.phase_viewer.ssh.hosts.HostInfo;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;

public class Sftp {
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
            .getLogger(Sftp.class.getName());

    private HostInfo host;

    private ChannelSftp c;

    private Session session;

    private Channel channel;

    private Vector listeners = new Vector();

    public Sftp(HostInfo host) {
        this.host = host;
    }

    public void addSftpListener(SftpListener sl) {
        listeners.add(sl);
    }

    public void connect() {
        try {
            logger.info("sftp connection to host: " + host.getName());
            // session = SessionCreator.getSession(host, 5000);
            session = SessionCreator.getSession(host);
            session.setTimeout(0);
            channel = session.openChannel("sftp");
            channel.connect();
        } catch (JSchException e) {
            logger.error("failed connection");
            e.printStackTrace();
            return;
        }
        c = (ChannelSftp) channel;
        // logger.debug("current directory: " + c.pwd());
    }

    public void disconnect() {
        if (session != null && session.isConnected()) {
            session.disconnect();
        }
    }

    public Session getSession() {
        return this.session;
    }

    public Channel getChannel() {
        return this.channel;
    }

    public void lcd(String lcdpath) {
        if (c == null) {
            logger.info("not connected.");
            return;
        }
        try {
            c.lcd(lcdpath);
            logger.debug("changed local directory to: " + lcdpath);
        } catch (SftpException se) {
            logger.error("failed to change local directory");
        }
    }

    public void lcd(File file) {
        if (c == null) {
            logger.info("not connected.");
            return;
        }
        String lcdpath = file.getAbsolutePath();
        try {
            c.lcd(lcdpath);
            logger.debug("changed local directory to: " + lcdpath);
        } catch (SftpException se) {
            logger.error("failed to change local directory");
        }
    }

    public void cd(String cdpath) {
        if (c == null) {
            logger.info("not connected");
            return;
        }

        try {
            c.cd(cdpath);
            logger.debug("changed remote directory to: " + cdpath);
        } catch (SftpException se) {
            logger.error("failed to change remote directory");
        }
    }

    public void download(RemoteFile[] files, RemoteFile dir, boolean recursive) {
        if (c == null) {
            logger.info("not connected.");
            return;
        }
        dir.mkdirs();
        (new Thread(new Download(files, dir, c, listeners, recursive))).start();
    }

    public void download(RemoteFile[] files, RemoteFile dir) {
        if (c == null) {
            logger.info("not connected.");
            return;
        }
        dir.mkdirs();
        (new Thread(new Download(files, dir, c, listeners))).start();
    }

    /**
     * 結構複雑な仕組みなので要注意
     * 
     * @param files
     *            アップロードしたいファイルの配列
     * @param dir
     *            リモートのベースとなるディレクトリー
     * @param localDir
     *            ローカルのベースとなるディレクトリー; リモートもローカルもないと相対パスでの解決に困る.
     * @param fork
     *            別スレッドでuploadしたい場合true
     */
    public void upload(RemoteFile[] files, RemoteFile dir, String localDir,
            boolean fork) {
        if (c == null) {
            logger.info("not connected.");
            return;
        }
        dir.mkdirs();
        if (fork)
            (new Thread(new Upload(files, dir, localDir, c, listeners)))
                    .start();
        else {
            Upload up = new Upload(files, dir, localDir, c, listeners);
            up.upload();
        }
        // logger.debug("wd: " + c.pwd());
    }

    /**
     * 結構複雑な仕組みなので要注意
     * 
     * @param files
     *            アップロードしたいファイルの配列
     * @param dir
     *            リモートのベースとなるディレクトリー
     * @param localDir
     *            ローカルのベースとなるディレクトリー; リモートもローカルもないと相対パスでの解決に困る.
     */
    public void upload(RemoteFile[] files, RemoteFile dir, String localDir) {
        upload(files, dir, localDir, true);
    }

    public void ls(File dir) {
        if (c == null) {
            logger.info("not connected.");
            return;
        }
        String path = ".";
        try {
            Vector lsvec = c.ls(path);
            if (lsvec != null) {
                for (int i = 0; i < lsvec.size(); i++) {
                    logger.debug((String) lsvec.get(i));
                }
            }
        } catch (SftpException se) {
            logger.error("failed to perform the \"ls\" command");
        }
    }

    public ChannelSftp getChannelSftp() {
        return this.c;
    }

    public void test() {
        if (c != null) {
            RemoteFile remote = new RemoteFile("", c);
            logger.debug("rem. file: " + remote);
            RemoteFile parent = (RemoteFile) remote.getParentFile();
            logger.debug("parent of rem: " + parent);
            remote.exists();
            parent.exists();
            RemoteFile[] files = (RemoteFile[]) remote.listFiles();
            if (files != null) {
                logger.debug("list of files");
                for (int i = 0; i < files.length; i++) {
                    logger.debug(files[i]);
                }
            }
        }

    }

    /**
     * 指定のディレクトリーの下にファイルが存在するか否か調べる.
     * 
     * @param fname
     *            調べたいファイル名
     * @param dir
     *            調べたいディレクトリー
     * @param ignoreCase
     *            大文字小文字を無視する場合true. OSから取得すればよいと思うかもしれないが,
     *            remoteディレクトリーを調べることもあるので明示的に指定する必要がある.
     * @return fnameがdirの下に存在する場合true. otherwise false.
     */
    private boolean checkDupli(String fname, RemoteFile dir, boolean ignoreCase) {
        File[] list = dir.listFiles();
        if (list == null || list.length == 0) {
            return false;
        }
        for (int i = 0; i < list.length; i++) {
            if (ignoreCase && list[i].getName().equalsIgnoreCase(fname)) {
                return true;
            } else if (list[i].getName().equals(fname)) {
                return true;
            }
        }
        return false;
    }

    /**
     * ディレクトリー下のユニークな名前を取得. ファイル名の最後に"_数字"というファイルを作成する.
     * 
     * @param origFname
     *            元のファイル名
     * @param dir
     *            指定のディレクトリー
     * @param ignoreCase
     *            大文字小文字を無視する場合true.
     * @return ユニークなファイル名
     */
    private String getUniqueFileName(String origFname, RemoteFile dir,
            boolean ignoreCase) {
        File[] list = dir.listFiles();
        if (list == null || list.length == 0) {
            logger.debug("returning orig. name " + origFname);
            return origFname;
        }
        if (checkDupli(origFname, dir, ignoreCase)) {
            String[] split_by_period = origFname.split("\\.");
            String testString = "";
            if (split_by_period.length > 1) {
                for (int i = 0; i < split_by_period.length - 1; i++) {
                    testString += split_by_period[i];
                }
            } else {
                testString = split_by_period[0];
            }

            String[] arr = testString.split("_");
            if (arr.length <= 1 || !Utils.isNumber(arr[arr.length - 1])) {
                String newName = testString + "_1";
                if (split_by_period.length > 1) {
                    newName += "."
                            + split_by_period[split_by_period.length - 1];
                }
                return getUniqueFileName(newName, dir, ignoreCase);
            }

            int num = Integer.parseInt(arr[arr.length - 1]);
            String ret = "";
            for (int i = 0; i < arr.length - 1; i++) {
                ret += arr[i] + "_";
            }
            ret += String.valueOf(num + 1);
            if (split_by_period.length > 1) {
                ret += "." + split_by_period[split_by_period.length - 1];
            }
            return getUniqueFileName(ret, dir, ignoreCase);
        }
        logger.debug("returning " + origFname);
        return origFname;
    }

    class Upload implements Runnable {

        private RemoteFile[] files;

        private RemoteFile dir;

        private String localDir;

        private ChannelSftp c;

        private Vector listeners;

        protected Upload(RemoteFile[] files, RemoteFile dir, String localDir,
                ChannelSftp c, Vector listeners) {
            this.files = files;
            this.dir = dir;
            this.localDir = localDir;
            this.c = c;
            this.listeners = listeners;
        }

        public void run() {
            upload(files, dir);
        }

        public void upload() {
            upload(files, dir);
        }

        private void upload(RemoteFile[] files, RemoteFile dir) {
            if (files != null && dir != null) {
                for (int i = 0; i < files.length; i++) {
                    String upfile = files[i].getAbsolutePath();
                    String tmpfile = upfile;

                    if (files[i].isDirectory()) {
                        if (files[i].getName().trim().equals(".")
                                || files[i].getName().trim().equals("..")) {
                            continue;
                        }
                        String dirdir = dir.getAbsolutePath();
                        RemoteFile newDir = new RemoteFile(dirdir,
                                files[i].getName() + "/", c);
                        newDir.mkdir();
                        logger.debug("created newdir: " + newDir);
                        upload((RemoteFile[]) files[i].listFiles(), newDir);
                        continue;
                    }
                    String toDir = dir.getAbsolutePath();
                    if (localDir != null) {
                        String tmpLocalDir = localDir.replaceAll("\\\\", "/")
                                .trim();
                        String tmpTargetFile = upfile.replaceAll("\\\\", "/")
                                .trim();
                        String[] tmparray = tmpTargetFile.split(tmpLocalDir);
                        logger.debug("tmpLocalDir, tmpTargetFile: "
                                + tmpLocalDir + ", " + tmpTargetFile);
                        if (tmparray == null || tmparray.length == 0) {
                            continue;
                        }
                        String tmpTargetRelative = tmpTargetFile.substring(
                                tmpLocalDir.length(), tmpTargetFile.length());
                        if (!tmpTargetRelative.startsWith("./")) {
                            tmpTargetRelative = "./" + tmpTargetRelative;
                        }
                        logger.debug("tmpTargetRelative: " + tmpTargetRelative);
                        toDir += new File(tmpTargetRelative).getParent()
                                .replaceAll("\\\\", "/");
                        logger.debug("toDir: " + toDir);
                    }
                    try {
                        RemoteFile tmpToDir = new RemoteFile(toDir, c);
                        RemoteFile test = new RemoteFile(toDir + "/"
                                + new File(upfile).getName(), c);
                        logger.debug("does file " + test.getAbsolutePath()
                                + " exist? " + test.exists());

                        ChaseFile cf = files[i].getChaseFile();
                        int mode = ChannelSftp.OVERWRITE;
                        boolean ignoreCase = false;
                        boolean rename = false;
                        boolean dup = checkDupli(new File(upfile).getName(),
                                tmpToDir, ignoreCase);

                        if (cf != null
                                && cf.getDuplicateFile().equals(
                                        ChaseFile.DO_NOTHING) && dup) {
                            continue;
                        }

                        if (cf != null
                                && cf.getDuplicateFile().equals(
                                        ChaseFile.APPEND)) {
                            mode = ChannelSftp.APPEND;
                        } else if (cf != null
                                && cf.getDuplicateFile().equals(
                                        ChaseFile.RENAME) && dup) {
                            rename = true;
                            mode = ChannelSftp.OVERWRITE;
                        }

                        String tmp = "~~~____";
                        if (rename && dup) {
                            // tmpfile += tmp;
                            tmpfile = System.getProperty("java.io.tmpdir")
                                    + System.getProperty("file.separator")
                                    + new File(upfile).getName() + tmp;
                            try {
                                ExternalProgramExecuter.copy(upfile, tmpfile);
                            } catch (IOException ioe) {
                                logger.error("failed pre-renme operation");
                                rename = false;
                            }
                        }

                        tmpToDir.mkdirs();
                        logger.info("uploading file: " + upfile + " to "
                                + tmpToDir.getAbsolutePath());
                        // MyProgressMonitor monitor=new
                        // MyProgressMonitor("uploading "+new
                        // File(upfile).getName());
                        SilentMyProgressMonitor monitor = new SilentMyProgressMonitor(
                                "uploading " + new File(upfile).getName());
                        c.put(tmpfile, toDir, (SftpProgressMonitor) monitor,
                                mode);
                        if (rename && dup) {
                            new File(tmpfile).delete();
                            String newName = getUniqueFileName(
                                    new File(upfile).getName(), tmpToDir,
                                    ignoreCase);
                            if (!toDir.endsWith("/")) {
                                toDir += "/";
                            }
                            logger.debug("toDir: " + toDir);
                            logger.debug("renaming "
                                    + new File(tmpfile).getName() + " to "
                                    + newName);
                            try {
                                c.rename(toDir + new File(tmpfile).getName(),
                                        toDir + newName);
                            } catch (SftpException sexc) {
                                logger.error("failed post-rename operation.");
                                try {
                                    c.rm(toDir + new File(tmpfile).getName());
                                } catch (Exception exc) {
                                    new File(tmpfile).delete();
                                }
                                sexc.printStackTrace();
                            }
                        }
                        logger.info("... done");
                    } catch (SftpException e) {
                        logger.error("failed to upload: " + upfile);
                        e.printStackTrace();
                    }
                }
            }

            if (listeners != null) {
                for (int i = 0; i < listeners.size(); i++) {
                    ((SftpListener) listeners.elementAt(i))
                            .uploadFinished(new SftpEvent(this));
                }
            }
            // parent.reload(SftpClient.REMOTE_PANE);
        }

    }

    class Download implements Runnable {
        private RemoteFile[] files;

        private RemoteFile dir;

        private ChannelSftp c;

        private Vector listeners;

        private boolean recursive = true;

        protected Download(RemoteFile[] files, RemoteFile dir, ChannelSftp c,
                Vector listeners) {
            this.files = files;
            this.dir = dir;
            this.c = c;
            this.listeners = listeners;
        }

        protected Download(RemoteFile[] files, RemoteFile dir, ChannelSftp c,
                Vector listeners, boolean recursive) {
            this.files = files;
            this.dir = dir;
            this.c = c;
            this.listeners = listeners;
            this.recursive = recursive;
        }

        public void run() {
            download(files, dir);
        }

        private void download(RemoteFile[] files, RemoteFile dir) {
            if (files != null && dir != null) {
                for (int i = 0; i < files.length; i++) {
                    String downfile = files[i].getAbsolutePath();
                    if (files[i].isDirectory()) {

                        if (recursive) {
                            continue;
                        }

                        if (files[i].getName().trim().equals(".")
                                || files[i].getName().trim().equals("..")) {
                            continue;
                        }
                        RemoteFile newDir = new RemoteFile(
                                dir.getAbsolutePath(), files[i].getName());
                        newDir.mkdir();
                        download((RemoteFile[]) files[i].listFiles(), newDir);
                        continue;
                    }
                    String toDir = dir.getAbsolutePath();
                    try {
                        int mode = ChannelSftp.OVERWRITE;
                        ChaseFile cf = files[i].getChaseFile();
                        boolean ignoreCase = System.getProperty("os.name")
                                .startsWith("windows")
                                || System.getProperty("os.name").startsWith(
                                        "mac");
                        boolean dup = checkDupli(new File(downfile).getName(),
                                dir, ignoreCase);
                        logger.debug("duplicate files: " + dup);
                        boolean rename = false;
                        boolean rename_local = false;
                        String tmpfile = System.getProperty("java.io.tmpdir")
                                + System.getProperty("file.separator")
                                + new File(downfile).getName();
                        if (cf != null) {
                            if (dup
                                    && cf.getDuplicateFile().equals(
                                            ChaseFile.DO_NOTHING)) {
                                continue;
                            } else if (dup
                                    && (cf.getDuplicateFile().equals(
                                            ChaseFile.RENAME) || cf
                                            .getDuplicateFile().equals(
                                                    ChaseFile.RENAME_LOCAL))) {
                                rename = true;
                                try {
                                    String ff = toDir
                                            + System.getProperty("file.separator")
                                            + new File(downfile).getName();
                                    if (new File(ff).exists())
                                        ExternalProgramExecuter.copyBinary(ff,
                                                tmpfile);
                                } catch (IOException ioe) {
                                    logger.error("failed pre-rename manipulation");
                                    ioe.printStackTrace();
                                    rename = false;
                                }
                            }

                            if (cf.getDuplicateFile().equals(ChaseFile.APPEND)) {
                                mode = ChannelSftp.APPEND;
                            }
                        }
                        logger.info("downloading file: " + downfile + " to "
                                + dir.getAbsolutePath());
                        // MyProgressMonitor monitor=new
                        // MyProgressMonitor("downloading "+new
                        // File(downfile).getName());

                        SilentMyProgressMonitor monitor = new SilentMyProgressMonitor(
                                "downloading " + new File(downfile).getName());
                        c.get(downfile, toDir, (SftpProgressMonitor) monitor,
                                mode);

                        if (rename) {
                            String newName = getUniqueFileName(new File(
                                    downfile).getName(), dir, ignoreCase);
                            try {
                                String fromName = toDir
                                        + System.getProperty("file.separator")
                                        + new File(downfile).getName();
                                String toName = toDir
                                        + System.getProperty("file.separator")
                                        + newName;
                                logger.debug("fromName, toName: " + fromName
                                        + ", " + toName);
                                if (cf.getDuplicateFile().equals(
                                        ChaseFile.RENAME)) {
                                    ExternalProgramExecuter
                                            .copyBinary(
                                                    toDir
                                                            + System.getProperty("file.separator")
                                                            + new File(downfile)
                                                                    .getName(),
                                                    toDir
                                                            + System.getProperty("file.separator")
                                                            + newName);
                                    ExternalProgramExecuter
                                            .copyBinary(
                                                    tmpfile,
                                                    toDir
                                                            + System.getProperty("file.separator")
                                                            + new File(downfile)
                                                                    .getName());
                                } else if (cf.getDuplicateFile().equals(
                                        ChaseFile.RENAME_LOCAL)) {
                                    ExternalProgramExecuter
                                            .copyBinary(
                                                    tmpfile,
                                                    toDir
                                                            + System.getProperty("file.separator")
                                                            + newName);
                                }
                            } catch (IOException ioe) {
                                logger.error("failed post-rename manipulation.");
                                ioe.printStackTrace();
                            }
                        }
                        logger.info("... done");
                    } catch (SftpException e) {
                        logger.error("failed to download: " + downfile);
                        e.printStackTrace();
                    }
                }
            }
            if (listeners != null) {
                for (int i = 0; i < listeners.size(); i++) {
                    ((SftpListener) listeners.elementAt(i))
                            .downloadFinished(new SftpEvent(this));
                }
            }
            // parent.reload(SftpClient.LOCAL_PANE);
        }

    }

}
