/*
 * Copyright (c) 2005, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package com.sun.tools.txw2;

import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.writer.FileCodeWriter;
import com.sun.codemodel.writer.SingleStreamCodeWriter;
import com.sun.tools.rngom.parse.Parseable;
import com.sun.tools.rngom.parse.compact.CompactParseable;
import com.sun.tools.rngom.parse.xml.SAXParseable;
import com.sun.xml.txw2.annotation.XmlNamespace;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.ResourceBundle;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;

/**
 * Controls the various aspects of the TXW generation.
 *
 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
 */
public class TxwOptions {

    public final JCodeModel codeModel = new JCodeModel();

    /**
     * The package to put the generated code into.
     */
    public JPackage _package;

    /**
     * Always non-null.
     */
    public ErrorListener errorListener;

    /**
     * The generated code will be sent to this.
     */
    CodeWriter codeWriter;

    /**
     * Schema file.
     */
    SchemaBuilder source;

    /**
     * If true, generate attribute/value methods that returns the
     * <code>this</code> object for chaining.
     */
    public boolean chainMethod;

    /**
     * If true, the generated code will not use the package-level
     * {@link XmlNamespace} annotation.
     */
    public boolean noPackageNamespace;

    /**
     * Type of input schema language. One of the {@code Language}
     * constants.
     */
    public Language language;

    /**
     * If true XML security features when parsing XML documents will be disabled.
     * The default value is false.
     * <p>
     * Boolean
     *
     * @since 2.2.6
     */
    public boolean disableXmlSecurity;

    /**
     * Type of the schema language.
     */
    public enum Language {
        XML,
        XMLSCHEMA,
        COMPACT
    }

    /**
     * Default constructor.
     */
    public TxwOptions() {}

    void parseArguments(String[] args) throws BadCommandLineException {
        String src = null;
        for (int i = 0; i < args.length; i++) {
            if (args[i].length() == 0) {
                throw new BadCommandLineException(getMessage("missingOperand"));
            }
            if (args[i].charAt(0) == '-') {
                int j = parseArgument(args, i);
                if (j == 0) {
                    throw new BadCommandLineException(getMessage("unrecognizedParameter", args[i]));
                }
                i += (j - 1);
            } else {
                src = args[i];
            }
        }
        if (_package == null) {
            _package = codeModel.rootPackage();
        }
        if (codeWriter == null) {
            codeWriter = new SingleStreamCodeWriter(System.out);
        }
        if (src == null) {
            throw new BadCommandLineException(getMessage("missingOperand"));
        }
        try {
            source = makeSourceSchema(src, errorListener);
        } catch (MalformedURLException mue) {
            throw new BadCommandLineException(mue.getMessage(), mue);
        }
    }

    private int parseArgument(String[] args, int i) throws BadCommandLineException {
        if (args[i].equals("-o")) {
            File targetDir = new File(requireArgument("-o", args, ++i));
            try {
                codeWriter = new FileCodeWriter(targetDir);
            } catch (IOException ex) {
                throw new BadCommandLineException(ex.getMessage(), ex);
            }
            return 2;
        }
        if (args[i].equals("-p")) {
            _package = codeModel._package(requireArgument("-p", args, ++i));
            return 2;
        }
        if (args[i].equals("-c")) {
            language = Language.COMPACT;
            return 1;
        }
        if (args[i].equals("-x")) {
            language = Language.XML;
            return 1;
        }
        if (args[i].equals("-xsd")) {
            language = Language.XMLSCHEMA;
            return 1;
        }
        if (args[i].equals("-h")) {
            chainMethod = true;
            return 1;
        }
        if (args[i].equals("-disableXmlSecurity")) {
            disableXmlSecurity = true;
            return 1;
        }
        return 0;   // unrecognized
    }

    private String requireArgument(String d, String[] args, int i) throws BadCommandLineException {
        if (i == args.length || args[i].startsWith("-")) {
            throw new BadCommandLineException(getMessage("missingOperand"));
        }
        return args[i];
    }

    public void printUsage() {
        System.out.println(getMessage("usage"));
    }

    /**
     * Parses the command line and makes a {@link Parseable} object out of the
     * specified schema file.
     */
    private SchemaBuilder makeSourceSchema(String src, ErrorHandler eh) throws BadCommandLineException, MalformedURLException {
        File f = new File(src);
        final InputSource in = new InputSource(f.toURI().toURL().toExternalForm());

        if (language == null) {
            // auto detect
            if (in.getSystemId().endsWith(".rnc")) {
                language = Language.COMPACT;
            } else if (in.getSystemId().endsWith(".rng")) {
                language = Language.XML;
            } else {
                language = Language.XMLSCHEMA;
            }
        }

        SchemaBuilder sb = null;
        switch (language) {
            case XMLSCHEMA:
                sb = new XmlSchemaLoader(in);
                break;
            case COMPACT:
                sb = new RELAXNGLoader(new CompactParseable(in, eh));
                break;
            case XML:
                sb = new RELAXNGLoader(new SAXParseable(in, eh));
                break;
            default:
                // should not happen
                throw new BadCommandLineException(getMessage("unknownGrammar"));
        }
        return sb;
    }

    /**
     * Gets the version number of TXW.
     *
     * @return TXW version
     */
    public static String getVersion() {
        try {
            Properties p = new Properties();
            p.load(TxwOptions.class.getResourceAsStream("version.properties"));
            return p.get("version").toString();
        } catch (Throwable t) {
            return "unknown";
        }
    }

    private static String getMessage(String key, Object... args) {
        String msg = ResourceBundle.getBundle(TxwOptions.class.getPackage().getName() + ".messages").getString(key);
        return MessageFormat.format(msg, args);
    }
}
