/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * See LICENSE.txt included in this distribution for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at LICENSE.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

package org.opensolaris.opengrok.history;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.opensolaris.opengrok.OpenGrokLogger;
import org.opensolaris.opengrok.util.Executor;

/**
 * Access to Surround SCM repository.
 *
 */
public class SSCMRepository extends Repository {

    private static final long serialVersionUID = 1L;
    
    /** The property name used to obtain the client command for this repository. */
    public static final String CMD_PROPERTY_KEY =
        "org.opensolaris.opengrok.history.sscm";
    /** The command to use to access the repository if none was given explicitly */
    public static final String CMD_FALLBACK = "sscm";

    private static final Pattern ANNOTATE_PATTERN = Pattern.compile("^(\\w+)\\s+(\\d+)\\s+.*$");
    
    private static final String MYSCMSERVERINFO_FILE = ".MySCMServerInfo";
    private static final String BRANCH_PROPERTY = "SCMBranch";
    private static final String REPOSITORY_PROPERTY = "SCMRepository";

    public SSCMRepository() {
        setType("SSCM");
        setDatePattern("M/d/yyyy h:mm a");
        setRemote(true);
    }
    
    @Override
    boolean fileHasHistory(File file) {
        return true;
    }

    @Override
    boolean hasHistoryForDirectories() {
        return false;
    }

    @Override
    public boolean isWorking() {
        if (working == null) {
            ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
            working = checkCmd(cmd , "version");
        }
        return working.booleanValue();
    }

    private Properties getProperties(File file) {
        Properties props = new Properties();
        File propFile;
        if (file.isDirectory()) {
            propFile = new File(file, MYSCMSERVERINFO_FILE);
        } else {
            propFile = new File(file.getParent(), MYSCMSERVERINFO_FILE);
        }

        if (propFile.isFile()) {
            try (BufferedReader br = new BufferedReader(new FileReader(propFile))) {
                props.load(br);
            } catch (IOException ex) {
                OpenGrokLogger.getLogger().log(Level.WARNING,
                        "Failed to work with {0} file of {1}: {2}", new Object[] {
                            MYSCMSERVERINFO_FILE,
                            getDirectoryName(), ex.getClass().toString() } );
            }
        }

        return props;
    }
    
    Executor getHistoryLogExecutor(final File file, String sinceRevision) throws IOException {

        List<String> argv = new ArrayList<>();
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
        argv.add(this.cmd);
        argv.add("history");
        if (file.isDirectory()) {
            argv.add("/");
        }
        else {
            argv.add(file.getName());
        }
        if (sinceRevision != null && new Scanner(sinceRevision).hasNextInt()) {
            argv.add("-v"+(Integer.parseInt(sinceRevision) + 1)+":"+Integer.MAX_VALUE);
        }
        argv.add("-w-");

        Properties props = getProperties(file);
        String branch = props.getProperty(BRANCH_PROPERTY);
        if (branch!=null && !branch.isEmpty())
        {
            argv.add("-b"+branch);
        }
        String repo = props.getProperty(REPOSITORY_PROPERTY);
        if (repo!=null && !repo.isEmpty())
        {
            argv.add("-p"+repo);
        }

        return new Executor(argv, new File(getDirectoryName()));
    }
    
    @Override
    History getHistory(File file) throws HistoryException {
        return getHistory(file, null);
    }

    @Override
    History getHistory(File file, String sinceRevision)
            throws HistoryException {
        return new SSCMHistoryParser(this).parse(file, sinceRevision);
    }

    @Override
    InputStream getHistoryGet(String parent, final String basename, String rev) {
        InputStream ret = null;

        File directory = new File(parent);

        Process process = null;
        try {
            final File tmp = File.createTempFile("opengrok", "tmp");
            String tmpName = tmp.getCanonicalPath();

            // cleartool can't get to a previously existing file
            if (tmp.exists() && !tmp.delete()) {
                OpenGrokLogger.getLogger().log(Level.WARNING,
                    "Failed to remove temporary file used by history cache");
            }

            if (!tmp.mkdir()) {
                OpenGrokLogger.getLogger().log(Level.WARNING,
                    "Failed to create temporary directory used by history cache");
            }
            
            List<String> argv = new ArrayList<>();
            ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
            argv.add(this.cmd);
            argv.add("get");
            argv.add(basename);
            argv.add("-d"+tmpName);
            Properties props = getProperties(directory);
            String branch = props.getProperty(BRANCH_PROPERTY);
            if (branch!=null && !branch.isEmpty())
            {
                argv.add("-b"+branch);
            }
            String repo = props.getProperty(REPOSITORY_PROPERTY);
            if (repo!=null && !repo.isEmpty())
            {
                argv.add("-p"+repo);
            }
            if (rev != null) {
                argv.add("-v"+rev);
            }
            argv.add("-q");
            argv.add("-tmodify");
            argv.add("-wreplace");
            Executor exec = new Executor(argv, directory);
            int status = exec.exec();

            if (status != 0) {
                OpenGrokLogger.getLogger().log(Level.WARNING,
                    "Failed get revision {2} for: \"{0}\" Exit code: {1}",
                    new Object[]{new File(parent, basename).getAbsolutePath(), String.valueOf(status), rev});
                return null;
            }

            ret = new BufferedInputStream(new FileInputStream(new File(tmp, basename))) {

                @Override
                public void close() throws IOException {
                    super.close();
                    boolean deleteOnExit = false;
                    File tmpFile = new File(tmp, basename);
                    // delete the temporary file on close
                    if (!tmpFile.delete()) {
                        // try on JVM exit
                        deleteOnExit = true;
                        tmpFile.deleteOnExit();
                    }
                    // delete the temporary directory on close
                    if (deleteOnExit || !tmp.delete()) {
                        // try on JVM exit
                        tmp.deleteOnExit();
                    }
                }
            };
        } catch (IOException exp) {
            OpenGrokLogger.getLogger().log(Level.SEVERE,
                "Failed to get file: " + exp.getClass().toString(), exp);
        } finally {
            // Clean up zombie-processes...
            if (process != null) {
                try {
                    process.exitValue();
                } catch (IllegalThreadStateException exp) {
                    // the process is still running??? just kill it..
                    process.destroy();
                }
            }
        }

        return ret;
    }

    @Override
    boolean fileHasAnnotation(File file) {
        File propFile = new File(file.getParent(), MYSCMSERVERINFO_FILE);
        if (propFile.isFile()) {
            try (BufferedReader br = new BufferedReader(new FileReader(propFile))) {
                // The bottom part is formatted:
                //  file name.ext;date;version;crc;
                String line;
                while ((line = br.readLine()) != null) {
                    String[] parts = line.split(";");
                    // Check if the filename matches
                    if (parts[0].equals(file.getName())) {
                        // Check if the version field is greater than 1
                        //  which indicates that annotate will work
                        if (parts.length > 2 && new Scanner(parts[2]).hasNextInt()) {
                            return Integer.parseInt(parts[2]) > 1;
                        }
                        break;
                    }
                }
            } catch (IOException ex) {
                OpenGrokLogger.getLogger().log(Level.WARNING,
                        "Failed to work with {0} file of {1}: {2}", new Object[] {
                            MYSCMSERVERINFO_FILE,
                            getDirectoryName(), ex.getClass().toString() } );
            }
        }
        return false;
    }

    /**
     * Annotate the specified file/revision.
     *
     * @param file file to annotate
     * @param revision revision to annotate
     * @return file annotation
     */
    @Override
    Annotation annotate(File file, String revision) throws IOException {
        ArrayList<String> argv = new ArrayList<>();

        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
        argv.add(this.cmd);
        argv.add("annotate");
        argv.add(file.getName());
        Properties props = getProperties(file);
        String branch = props.getProperty(BRANCH_PROPERTY);
        if (branch!=null && !branch.isEmpty())
        {
            argv.add("-b"+branch);
        }
        String repo = props.getProperty(REPOSITORY_PROPERTY);
        if (repo!=null && !repo.isEmpty())
        {
            argv.add("-p"+repo);
        }
        if (revision != null) {
            argv.add("-aV:"+revision);
        }
        Executor exec = new Executor(argv, file.getParentFile());
        int status = exec.exec();

        if (status != 0) {
            OpenGrokLogger.getLogger().log(Level.WARNING,
                "Failed annotate for: {2} \"{0}\" Exit code: {1}",
                new Object[]{file.getAbsolutePath(), String.valueOf(status), revision});
        }

        return parseAnnotation(exec.getOutputReader(), file.getName());
    }

    protected Annotation parseAnnotation(Reader input, String fileName)
        throws IOException
    {
        BufferedReader in = new BufferedReader(input);
        Annotation ret = new Annotation(fileName);
        String line = "";
        int lineno = 0;
        boolean hasStarted = false;
        Matcher matcher = ANNOTATE_PATTERN.matcher(line);
        while ((line = in.readLine()) != null) {
            // For some reason there are empty lines.  Line ends may not be applied correctly.
            if (line.isEmpty()) continue;
            ++lineno;
            matcher.reset(line);
            if (matcher.find()) {
                hasStarted = true;
                String rev = matcher.group(2);
                String author = matcher.group(1);
                ret.addLine(rev, author, true);
            } else if (hasStarted) {
                OpenGrokLogger.getLogger().log(Level.SEVERE,
                    "Error: did not find annotation in line {0}: [{1}]",
                    new Object[]{String.valueOf(lineno), line});
            }
        }
        return ret;
    }
    
    @Override
    void update() throws IOException {
        File directory = new File(getDirectoryName());
        List<String> argv = new ArrayList<>();
        
        ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
        argv.add(this.cmd);
        argv.add("get");
        argv.add("/");
        Properties props = getProperties(directory);
        String branch = props.getProperty(BRANCH_PROPERTY);
        if (branch!=null && !branch.isEmpty())
        {
            argv.add("-b"+branch);
        }
        String repo = props.getProperty(REPOSITORY_PROPERTY);
        if (repo!=null && !repo.isEmpty())
        {
            argv.add("-p"+repo);
        }
        argv.add("-r");
        argv.add("-q");
        argv.add("-tmodify");
        argv.add("-wreplace");

        Executor executor = new Executor(argv, directory);
        if (executor.exec() != 0) {
            throw new IOException(executor.getErrorString());
        }
    }

    @Override
    boolean isRepositoryFor(File file) {
        if (file.isDirectory()) {
            File f = new File(file, MYSCMSERVERINFO_FILE);
            return f.exists() && f.isFile();
        }
        return false;
    }
    
}
