/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.inputmethod.latin;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.LinkedList;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

/**
 * Main class/method for DictionaryMaker.
 */
public class DictionaryMaker {

    static class Arguments {
        private final static String OPTION_VERSION_2 = "-2";
        private final static String OPTION_INPUT_SOURCE = "-s";
        private final static String OPTION_INPUT_BIGRAM_XML = "-b";
        private final static String OPTION_OUTPUT_BINARY = "-d";
        private final static String OPTION_OUTPUT_XML = "-x";
        private final static String OPTION_HELP = "-h";
        public final String mInputBinary;
        public final String mInputUnigramXml;
        public final String mInputBigramXml;
        public final String mOutputBinary;
        public final String mOutputXml;

        private void checkIntegrity() {
            checkHasExactlyOneInput();
            checkHasAtLeastOneOutput();
        }

        private void checkHasExactlyOneInput() {
            if (null == mInputUnigramXml && null == mInputBinary) {
                throw new RuntimeException("No input file specified");
            } else if (null != mInputUnigramXml && null != mInputBinary) {
                throw new RuntimeException("Both input XML and binary specified");
            } else if (null != mInputBinary && null != mInputBigramXml) {
                throw new RuntimeException("Cannot specify a binary input and a separate bigram "
                        + "file");
            }
        }

        private void checkHasAtLeastOneOutput() {
            if (null == mOutputBinary && null == mOutputXml) {
                throw new RuntimeException("No output specified");
            }
        }

        private void displayHelp() {
            MakedictLog.i("Usage: makedict "
                    + "[-s <unigrams.xml> [-b <bigrams.xml>] | -s <binary input>] "
                    + " [-d <binary output>] [-x <xml output>] [-2]\n"
                    + "\n"
                    + "  Converts a source dictionary file to one or several outputs.\n"
                    + "  Source can be an XML file, with an optional XML bigrams file, or a\n"
                    + "  binary dictionary file.\n"
                    + "  Both binary and XML outputs are supported. Both can be output at\n"
                    + "  the same time but outputting several files of the same type is not\n"
                    + "  supported.");
        }

        public Arguments(String[] argsArray) {
            final LinkedList<String> args = new LinkedList<String>(Arrays.asList(argsArray));
            if (args.isEmpty()) {
                displayHelp();
            }
            String inputBinary = null;
            String inputUnigramXml = null;
            String inputBigramXml = null;
            String outputBinary = null;
            String outputXml = null;

            while (!args.isEmpty()) {
                final String arg = args.get(0);
                args.remove(0);
                if (arg.charAt(0) == '-') {
                    if (OPTION_VERSION_2.equals(arg)) {
                        // Do nothing, this is the default
                    } else if (OPTION_HELP.equals(arg)) {
                        displayHelp();
                    } else {
                        // All these options need an argument
                        if (args.isEmpty()) {
                            throw new RuntimeException("Option " + arg + " requires an argument");
                        }
                        String filename = args.get(0);
                        args.remove(0);
                        if (OPTION_INPUT_SOURCE.equals(arg)) {
                            if (BinaryDictInputOutput.isBinaryDictionary(filename)) {
                                inputBinary = filename;
                            } else {
                                inputUnigramXml = filename;
                            }
                        } else if (OPTION_INPUT_BIGRAM_XML.equals(arg)) {
                            inputBigramXml = filename;
                        } else if (OPTION_OUTPUT_BINARY.equals(arg)) {
                            outputBinary = filename;
                        } else if (OPTION_OUTPUT_XML.equals(arg)) {
                            outputXml = filename;
                        }
                    }
                } else {
                    if (null == inputBinary && null == inputUnigramXml) {
                        if (BinaryDictInputOutput.isBinaryDictionary(arg)) {
                            inputBinary = arg;
                        } else {
                            inputUnigramXml = arg;
                        }
                    } else if (null == outputBinary) {
                        outputBinary = arg;
                    } else {
                        throw new RuntimeException("Several output binary files specified");
                    }
                }
            }

            mInputBinary = inputBinary;
            mInputUnigramXml = inputUnigramXml;
            mInputBigramXml = inputBigramXml;
            mOutputBinary = outputBinary;
            mOutputXml = outputXml;
            checkIntegrity();
        }
    }

    public static void main(String[] args)
            throws FileNotFoundException, ParserConfigurationException, SAXException, IOException,
            UnsupportedFormatException {
        final Arguments parsedArgs = new Arguments(args);
        FusionDictionary dictionary = readInputFromParsedArgs(parsedArgs);
        writeOutputToParsedArgs(parsedArgs, dictionary);
    }

    /**
     * Invoke the right input method according to args.
     *
     * @param args the parsed command line arguments.
     * @return the read dictionary.
     */
    private static FusionDictionary readInputFromParsedArgs(final Arguments args)
            throws IOException, UnsupportedFormatException, ParserConfigurationException,
            SAXException, FileNotFoundException {
        if (null != args.mInputBinary) {
            return readBinaryFile(args.mInputBinary);
        } else if (null != args.mInputUnigramXml) {
            return readXmlFile(args.mInputUnigramXml, args.mInputBigramXml);
        } else {
            throw new RuntimeException("No input file specified");
        }
    }

    /**
     * Read a dictionary from the name of a binary file.
     *
     * @param binaryFilename the name of the file in the binary dictionary format.
     * @return the read dictionary.
     * @throws FileNotFoundException if the file can't be found
     * @throws IOException if the input file can't be read
     * @throws UnsupportedFormatException if the binary file is not in the expected format
     */
    private static FusionDictionary readBinaryFile(final String binaryFilename)
            throws FileNotFoundException, IOException, UnsupportedFormatException {
        final RandomAccessFile inputFile = new RandomAccessFile(binaryFilename, "r");
        return BinaryDictInputOutput.readDictionaryBinary(inputFile, null);
    }

    /**
     * Read a dictionary from a unigram XML file, and optionally a bigram XML file.
     *
     * @param unigramXmlFilename the name of the unigram XML file. May not be null.
     * @param bigramXmlFilename the name of the bigram XML file. Pass null if there are no bigrams.
     * @return the read dictionary.
     * @throws FileNotFoundException if one of the files can't be found
     * @throws SAXException if one or more of the XML files is not well-formed
     * @throws IOException if one the input files can't be read
     * @throws ParserConfigurationException if the system can't create a SAX parser
     */
    private static FusionDictionary readXmlFile(final String unigramXmlFilename,
            final String bigramXmlFilename) throws FileNotFoundException, SAXException,
            IOException, ParserConfigurationException {
        final FileInputStream unigrams = new FileInputStream(new File(unigramXmlFilename));
        final FileInputStream bigrams = null == bigramXmlFilename ? null :
                new FileInputStream(new File(bigramXmlFilename));
        return XmlDictInputOutput.readDictionaryXml(unigrams, bigrams);
    }

    /**
     * Invoke the right output method according to args.
     *
     * This will write the passed dictionary to the file(s) passed in the command line arguments.
     * @param args the parsed arguments.
     * @param dict the file to output.
     * @throws FileNotFoundException if one of the output files can't be created.
     * @throws IOException if one of the output files can't be written to.
     */
    private static void writeOutputToParsedArgs(final Arguments args, final FusionDictionary dict)
            throws FileNotFoundException, IOException {
        if (null != args.mOutputBinary) {
            writeBinaryDictionary(args.mOutputBinary, dict);
        }
        if (null != args.mOutputXml) {
            writeXmlDictionary(args.mOutputXml, dict);
        }
    }

    /**
     * Write the dictionary in binary format to the specified filename.
     *
     * @param outputFilename the name of the file to write to.
     * @param dict the dictionary to write.
     * @throws FileNotFoundException if the output file can't be created.
     * @throws IOException if the output file can't be written to.
     */
    private static void writeBinaryDictionary(final String outputFilename,
            final FusionDictionary dict) throws FileNotFoundException, IOException {
        final File outputFile = new File(outputFilename);
        BinaryDictInputOutput.writeDictionaryBinary(new FileOutputStream(outputFilename), dict);
    }

    /**
     * Write the dictionary in XML format to the specified filename.
     *
     * @param outputFilename the name of the file to write to.
     * @param dict the dictionary to write.
     * @throws FileNotFoundException if the output file can't be created.
     * @throws IOException if the output file can't be written to.
     */
    private static void writeXmlDictionary(final String outputFilename,
            final FusionDictionary dict) throws FileNotFoundException, IOException {
        XmlDictInputOutput.writeDictionaryXml(new FileWriter(outputFilename), dict);
    }
}
