/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.interview;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Vector;

/**
 * An API (with a basic front-end application) for batch editing an
 * interview.
 */
public class WizEdit
{
    /**
     * This exception is used to indicate a problem with the command line arguments.
     */

    public static class BadArgs extends Exception
    {
        /**
         * Create a BadArgs exception.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         */
        BadArgs(ResourceBundle i18n, String s) {
            super(i18n.getString(s));
        }

        /**
         * Create a BadArgs exception.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         * @param o An argument to be formatted with the detail message by
         * {@link java.text.MessageFormat#format}
         */
        BadArgs(ResourceBundle i18n, String s, Object o) {
            super(MessageFormat.format(i18n.getString(s), new Object[] {o}));
        }


        /**
         * Create a BadArgs exception.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         * @param o An array of arguments to be formatted with the detail message by
         * {@link java.text.MessageFormat#format}
         */
        BadArgs(ResourceBundle i18n, String s, Object[] o) {
            super(MessageFormat.format(i18n.getString(s), o));
        }
    }

    /**
     * This exception is to report problems that occur while editing
     * the responses to questions in an interview.
     */
    public static class Fault extends Exception
    {
        /**
         * Create a Fault.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         */
        Fault(ResourceBundle i18n, String s) {
            super(i18n.getString(s));
        }

        /**
         * Create a Fault.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         * @param o An argument to be formatted with the detail message by
         * {@link java.text.MessageFormat#format}
         */
        Fault(ResourceBundle i18n, String s, Object o) {
            super(MessageFormat.format(i18n.getString(s), new Object[] {o}));
        }

        /**
         * Create a Fault.
         * @param i18n A resource bundle in which to find the detail message.
         * @param s The key for the detail message.
         * @param o An array of arguments to be formatted with the detail message by
         * {@link java.text.MessageFormat#format}
         */
        Fault(ResourceBundle i18n, String s, Object[] o) {
            super(MessageFormat.format(i18n.getString(s), o));
        }
    }

    /**
     * Simple command-line front-end to the facilities of the API.
     * @param args Command line arguments.
     */
    public static void main(String[] args) {
        try {
            Vector v = new Vector();
            File interviewFile = null;
            File outFileName = null;

            for (int i = 0; i < args.length; i++) {
                if (args[i].equals("-o") && i + 1 < args.length)
                    outFileName = new File(args[++i]);
                else if (args[i].equals("-e"))
                    v.addElement(args[++i]);
                else if (args[i].startsWith("-"))
                    throw new BadArgs(i18n, "edit.badOption", args[i]);
                else if (i == args.length - 1 && args[i].endsWith(".jti"))
                    interviewFile = new File(args[i]);
                else
                    throw new BadArgs(i18n, "edit.badOption", args[i]);
            }

            if (interviewFile == null)
                throw new BadArgs(i18n, "edit.noInterview");

            Interview interview;

            try {
                InputStream in = new BufferedInputStream(new FileInputStream(interviewFile));
                Properties p = new Properties();
                p.load(in);
                String interviewClassName = (String)p.get("INTERVIEW");
                if (interviewClassName == null)
                    throw new Fault(i18n, "edit.noInterview");
                Class ic = Class.forName(interviewClassName);
                interview = (Interview)(ic.newInstance());
                interview.load(p, false);
            }
            catch (FileNotFoundException e) {
                throw new Fault(i18n, "edit.cantFindFile", interviewFile);
            }
            catch (IOException e) {
                throw new Fault(i18n, "edit.cantReadFile", e);
            }

            String[] cmds = new String[v.size()];
            v.copyInto(cmds);

            WizEdit editor = new WizEdit(interview);
            editor.edit(cmds);

            try {
                OutputStream out = new BufferedOutputStream(new FileOutputStream(outFileName));
                Properties p = new Properties();
                interview.save(p);
                p.store(out, "Interview: " + interview.getTitle());
            }
            catch (IOException e) {
                throw new Fault(i18n, "edit.cantWriteFile", e);
            }
        }
        catch (BadArgs e) {
            System.err.println("Error: " + e.getMessage());
            //usage();
            System.exit(1);
        }
        catch (Interview.Fault e) {
            System.err.println("Problem reading file: " + e);
            System.exit(2);
        }
        catch (ClassNotFoundException e) {
            System.err.println("Problem reading file: the interview could not be loaded because some classes that are required by the interview were not found on your classpath. The specific exception that occurred was: " + e);
            System.exit(2);
        }
        catch (IllegalAccessException e) {
            System.err.println("Problem reading file: the interview could not be loaded because some classes that are required by the interview caused access violations. The specific exception that occurred was: " + e);
            System.exit(2);
        }
        catch (InstantiationException e) {
            System.err.println("Problem reading file: the interview could not be loaded because some classes that are required by the interview could not be instantiated. The specific exception that occurred was: " + e);
            System.exit(2);
        }
        catch (Fault e) {
            System.err.println(e.getMessage());
            System.exit(2);
        }
    }

    /**
     * Create an editor for the questions in an interview.
     * @param interview The interview containing the responses to be edited.
     */
    public WizEdit(Interview interview) {
        this.interview = interview;
    }

    /**
     * Set whether or not the edit should be done verbosely.
     * @param verbose Set to true for verbose mode, and false otherwise.
     * @see #setVerbose(boolean, PrintStream)
     */
    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    /**
     * Set whether or not the edit should be done verbosely,
     * and set the stream to which tracingn information should be output.
     * @param verbose Set to true for verbose mode, and false otherwise.
     * @param out The stream to which verbose output should be directed.
     */
    public void setVerbose(boolean verbose, PrintStream out) {
        this.verbose = verbose;
        this.out = out;
    }

    /**
     * Apply a series of edits to the set of responses in an interview.
     * The edits are applied one at a time, and as each edit is applied,
     * the set of questions in the current interview path may change:
     * specifically, the set of questions after the one that is edited
     * may change.
     * @param cmds A set of editing commands to apply to the responses.
     * @throws WizEdit.Fault if there is a problem while applying the edits.
     * @see #edit(String)
     */
    public void edit(String[] cmds) throws Fault {
        for (int i = 0; i < cmds.length; i++)
            edit(cmds[i]);
    }

    /**
     * Apply an edit to the set of responses in an interview.
     * After the edit is applied, the set of questions in the
     * cuurent interview path may change: specifically, the set
     * of questions after the one that is edited may change.
     * @param cmd An edit command to apply to the responses.
     * @throws WizEdit.Fault if there is a problem while applying the edit.
     * @see #edit(String[])
     */
    public void edit(String cmd) throws Fault {
        if (cmd == null || cmd.length() == 0)
            throw new Fault(i18n, "edit.nullCmd");
        char delim = cmd.charAt(0);
        int left = 0;
        int center = cmd.indexOf(delim, left+1);
        if (center == -1)
            throw new Fault(i18n, "edit.badCmd", cmd);
        int right = cmd.indexOf(delim, center+1);
        String searchText = cmd.substring(left+1, center);
        String replaceText = cmd.substring(center+1, right);
        if (searchText.length() == 0)
            throw new Fault(i18n, "edit.badCmd", cmd);

        Hashtable answers = new Hashtable();
        interview.save(answers);

        Question[] path = interview.getPath();
        for (int i = 0; i < path.length; i++) {
            Question q = path[i];
            try {
                String answer = (String)(answers.get(q.getTag()));
                if (answer == null)
                    continue;
                // // currently hardwired: considerCase: false; word match: false
                // int pos = match(searchText, answer, false, false);
                // if (pos >= 0) {
                //     String newAnswer = answer.substring(0, pos)
                //      + replaceText
                //      + answer.substring(pos+searchText.length());
                if (answer.equalsIgnoreCase(searchText)) {
                    String newAnswer = replaceText;
                    q.setValue(newAnswer);

                    if (verbose) {
                        Hashtable h = new Hashtable();
                        q.save(h);
                        out.println("Question:     " + q.getSummary());
                        out.println("changed from: " + answer);
                        out.println("          to: " + h.get(q.getTag()));
                    }
                }
            }
            catch (Interview.Fault e) {
                throw new Fault(i18n, "edit.cantSetValue",
                                new Object[] { q.getSummary(), e.getMessage() });
            }
        }

    }

    private static int match(String s1, String s2, boolean considerCase, boolean word) {
        int s1len = s1.length();
        int s2len = s2.length();
        for (int i = 0; i <= s2len - s1len; i++) {
            if (s1.regionMatches(!considerCase, 0, s2, i, s1len)) {
                if (!word || (word &&
                              ( (i == 0 || isBoundaryCh(s2.charAt(i-1)))
                                && (i+s1len == s2.length() || isBoundaryCh(s2.charAt(i+s1len))) )))
                    return i;
            }
        }
        return -1;
    }

    private static boolean isBoundaryCh(char c) {
        return !(Character.isUnicodeIdentifierStart(c)
                 || Character.isUnicodeIdentifierPart(c));
    }

    private Interview interview;
    private boolean considerCase = false;
    private boolean word = false;
    private boolean verbose;
    private PrintStream out = System.err;

    private static final ResourceBundle i18n = Interview.i18n;
}
