/*
 * 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.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
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;

/**
 *
 * @author michailf
 */
public class SSCMHistoryParser implements Executor.StreamHandler {

    private final SSCMRepository repository;
    
    SSCMHistoryParser(SSCMRepository repository) {
        this.repository = repository;
    }

    private static final String ACTION_PATTERN = "[a-z][a-z ]+";
    private static final String USER_PATTERN = "\\w+";
    private static final String VERSION_PATTERN = "\\d+";
    private static final String TIME_PATTERN = "\\d{1,2}/\\d{1,2}/\\d{4} \\d{1,2}:\\d{2} [AP]M";
    private static final String COMMENT_START_PATTERN = "Comments - ";
    // ^([a-z][a-z ]+)(?:\[(.*?)\])?\s+(\w+)\s+(\d+)\s+(\d{1,2}/\d{1,2}/\d{4} \d{1,2}:\d{2} [AP]M)$\s*(?:Comments - )?
    private static final Pattern HISTORY_PATTERN = Pattern.compile("^(" + ACTION_PATTERN + ")(?:\\[(.*?)\\])?\\s+(" + USER_PATTERN + ")\\s+(" + VERSION_PATTERN + ")\\s+(" + TIME_PATTERN + ")$\\s*(?:" + COMMENT_START_PATTERN + ")?",
                                                    Pattern.MULTILINE);
    
    private static final String NEWLINE = System.getProperty("line.separator");
    
    private History history;
    
    /**
     * Process the output from the history command and insert the HistoryEntries
     * into the history field.
     *
     * @param input The output from the process
     * @throws java.io.IOException If an error occurs while reading the stream
     */
    @Override
    public void processStream(InputStream input) throws IOException {
        DateFormat df = repository.getDateFormat();
        history = new History();

        BufferedReader in = new BufferedReader(new InputStreamReader(input));
        StringBuilder total = new StringBuilder(input.available());
        String line;
        while ((line = in.readLine()) != null) {
            total.append(line).append(NEWLINE);
        }

        ArrayList<HistoryEntry> entries = new ArrayList<>();
        HistoryEntry entry = null;
        int prevEntryEnd = 0;
        
        long revisionCounter = 0;
        Matcher matcher = HISTORY_PATTERN.matcher(total);
        while (matcher.find()) {
            if (entry != null) {
                if (matcher.start() != prevEntryEnd) {
                    // Get the comment and reduce all double new lines to single
                    //  add a space as well for better formatting in RSS feeds.
                    entry.appendMessage(total.substring(prevEntryEnd, matcher.start()).replaceAll("(\\r?\\n){2}", " $1").trim());
                }
                entries.add(0, entry);
                entry = null;
            }
            String revision = matcher.group(4);
            String author = matcher.group(3);
            String context = matcher.group(2);
            String date = matcher.group(5);

            long currentRevision = 0;
            try {
                currentRevision = Long.parseLong(revision);
            } catch (NumberFormatException ex) {
                OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to parse revision: '" + revision + "'", ex);
            }
            // We're only interested in history entries that change file content
            if (revisionCounter < currentRevision) {
                revisionCounter = currentRevision;

                entry = new HistoryEntry();
                // Add context of action to message.  Helps when branch name is used
                //   as indicator of why promote was made.
                if (context != null)
                    entry.appendMessage("[" + context + "] ");
                entry.setAuthor(author);
                entry.setRevision(revision);
                try {
                    entry.setDate(df.parse(date));
                } catch (ParseException ex) {
                    OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to parse date: '" + date + "'", ex);
                }
                entry.setActive(true);
            }
            prevEntryEnd = matcher.end();
        }

        if (entry != null) {
            if (total.length() != prevEntryEnd) {
                // Get the comment and reduce all double new lines to single
                //  add a space as well for better formatting in RSS feeds.
                entry.appendMessage(total.substring(prevEntryEnd).replaceAll("(\\r?\\n){2}", " $1").trim());
            }
            entries.add(0, entry);
        }
        history.setHistoryEntries(entries);
    }

    History parse(File file, String sinceRevision) throws HistoryException {
        try {
            Executor executor = repository.getHistoryLogExecutor(file, sinceRevision);
            int status = executor.exec(true, this);

            if (status != 0) {
                throw new HistoryException("Failed to get history for: \"" +
                                           file.getAbsolutePath() + "\" Exit code: " + status);
            }
        } catch (IOException e) {
            throw new HistoryException("Failed to get history for: \"" +
                                       file.getAbsolutePath() + "\"", e);
        }

        return history;
    }

    /**
     * Parse the given string.
     *
     * @param buffer The string to be parsed
     * @return The parsed history
     * @throws IOException if we fail to parse the buffer
     */
    History parse(String buffer) throws IOException {
        processStream(new ByteArrayInputStream(buffer.getBytes("UTF-8")));
        return history;
    }
}
