/*
 * 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
 */

/*
 * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 */
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.HashMap;
import java.util.logging.Level;
import org.opensolaris.opengrok.OpenGrokLogger;
import org.opensolaris.opengrok.util.Executor;

/**
 * Parse a stream of CVS log comments.
 */
class CVSHistoryParser implements Executor.StreamHandler {

    private enum ParseState {
        NAMES, TAG, REVISION, METADATA, COMMENT
    }

    private History history;
    private CVSRepository repository=new CVSRepository();

   /**
     * Process the output from the log 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();
        ArrayList<HistoryEntry> entries = new ArrayList<HistoryEntry>();

        BufferedReader in = new BufferedReader(new InputStreamReader(input));

        history = new History();
        HistoryEntry entry = null;
        HashMap<String, String> tags = null;
        ParseState state = ParseState.NAMES;
        String s = in.readLine();
        while (s != null) {
            if (state == ParseState.NAMES && s.startsWith("symbolic names:")) {
                tags = new HashMap<String, String>();
                state = ParseState.TAG;
                s = in.readLine();
            }
            if (state == ParseState.TAG) {
                if (s.startsWith("\t")) {
                    String[] pair = s.trim().split(": ");
                    if (pair.length != 2) {
                        OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to parse tag: ''{0}''", s);
                    } else {
                        if (tags.containsKey(pair[1])) {
                            // Join multiple tags for one revision
                            String oldtag = tags.get(pair[1]);
                            tags.remove(pair[1]);
                            tags.put(pair[1], oldtag + " " + pair[0]);
                        } else {
                            tags.put(pair[1], pair[0]);
                        }
                    }
                } else {
                    state = ParseState.REVISION;
                    s = in.readLine();
                }             
            }
            if (state == ParseState.REVISION && s.startsWith("revision")) {
                if (entry != null) {
                    entries.add(entry);
                }
                entry = new HistoryEntry();
                entry.setActive(true);
                String commit = s.substring("revision".length()).trim();
                entry.setRevision(commit);
                if (tags.containsKey(commit)) {
                    entry.setTags(tags.get(commit));
                }
                state = ParseState.METADATA;
                s = in.readLine();
            }
            if (state == ParseState.METADATA && s.startsWith("date:")) {
                for (String pair : s.split(";")) {
                    String[] keyVal = pair.split(":", 2);
                    String key = keyVal[0].trim();
                    String val = keyVal[1].trim();

                    if ("date".equals(key)) {
                        try {
                            val = val.replace('/', '-');
                            entry.setDate(df.parse(val));
                        } catch (ParseException pe) {
                            OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to parse date: '" + val + "'", pe);
                        }
                    } else if ("author".equals(key)) {
                        entry.setAuthor(val);
                    }
                }

                state = ParseState.COMMENT;
                s = in.readLine();
            }
            if (state == ParseState.COMMENT) {
                if (s.startsWith("--------")) {
                    state = ParseState.REVISION;
                } else if (s.startsWith("========")) {
                    state = ParseState.NAMES;
                } else {
                    if (entry != null) {
                        entry.appendMessage(s);
                    }
                }
            }
            s = in.readLine();
        }

        if (entry != null) {
            entries.add(entry);
        }

        history.setHistoryEntries(entries);
    }

    /**
     * Parse the history for the specified file.
     *
     * @param file the file to parse history for
     * @param repos Pointer to the SubversionReporitory
     * @return object representing the file's history
     */
    History parse(File file, Repository repos) throws HistoryException {
        repository = (CVSRepository) repos;
        try {
            Executor executor = repository.getHistoryLogExecutor(file);
            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;
    }
}
