// $Id: ArgumentCreator.java,v 1.9 2004/06/14 16:12:49 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" is free software; you can redistribute
// it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program 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 for more details.

package com.meyling.principia.argument;


import com.meyling.principia.io.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * This class provides static methods for reading and
 * writing an {@link Argument}.
 *
 * @version $Revision: 1.9 $
 * @author  Michael Meyling
 */
public final class ArgumentCreator {


    /**
     * Creates a new {@link Argument}. Could't create a new
     * {@link Counter}
     * or a {@link Text}.
     *
     * @param   kind        class to generate
     * @param   argument    argument for constructor of that class
     * @return  new generated Argument
     * @throws  ArgumentException   if creation failed
     * @throws  IllegalArgumentException   if creation failed
     *           because of a programming error
     */
    public final static Argument createArgument(final Class kind,
            final Argument[] argument)
            throws IllegalArgumentException, ArgumentException {

        try {
            Constructor constructor = kind.getConstructor(new Class[] {
                Argument[].class});
            if (constructor == null) {
                throw new IllegalArgumentException(kind.getName()
                + " couldn't be created with an argument list");
            }
            return (Argument) constructor.newInstance(
                new Object[] {argument});
        } catch (ClassCastException e) {
            throw new IllegalArgumentException("Programming error "
                + e.toString());
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof ArgumentException) {
                throw (ArgumentException) e.getTargetException().fillInStackTrace();
            } else {
                throw new IllegalArgumentException(e.getTargetException().getMessage());
            }
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Programming error " 
                + e.toString());
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Programming error " 
                + e.toString());
        } catch (InstantiationException e) {
            throw new IllegalArgumentException("Programming error " 
                + e.toString());
        }

    }


    /**
     * Create an {@link Argument} out of an {@link TextInput} object.
     *
     * @param   input   source to read from
     * @param   string2class    use this mapping for parsing
     * @param   parsing     for saving positions of created arguments
     * @return  created argument
     * @throws  ParsingException    if creation failed
     */
    public final static Argument readArgument(final TextInput input,
            final Map string2class, final ParsingTable parsing)
            throws ParsingException {
        String key = null;
        char c;
        int begin = input.getPosition();
        input.skipWhiteSpace();
        if (input.isEmpty()) {
            input.setPosition(begin);
            throw new ParsingException(
                ArgumentConstants.CODE_PARSER_ARGUMENT_EXPECTED,
                ArgumentConstants.ARGUMENT_EXPECTED,
                input.getRow(),
                input.getColumn(),
                input.getPosition(),
                input.getLine(), input.getLocalAddress());
        }

        c = input.getChar();
        begin = input.getPosition();
        if (c == ')') {
            throw new ParsingException(
                ArgumentConstants.CODE_PARSER_ARGUMENT_EXPECTED,
                ArgumentConstants.ARGUMENT_EXPECTED,
                input.getRow(),
                input.getColumn(),
                input.getPosition(),
                input.getLine(), input.getLocalAddress());
        } else if (Character.isUpperCase(c) || c == '(') {
            final Class cl;
            if (c == '(') {
                cl = VariableList.class;
            } else {
                key = input.readLetterDigitString();
                cl = (Class) string2class.get(key);
            }
            if (cl != null) {
                try {
                    final Argument argument
                         = createArgument(cl, readArgumentList(input,
                         string2class, parsing));
                    final int end = input.getPosition();
                    parsing.put(argument, new Position(begin, end));
                    return argument;
                } catch (ArgumentException e) {
                    final Argument argument = e.getArgument();
                    if (argument != null) {
                        try {
                            final Position pos = parsing.get(argument);
                            if (e.isBegin()) {
                                input.setPosition(pos.getBegin());
                            } else {
                                input.setPosition(pos.getEnd());
                            }
                            throw new ParsingException(e.getCode(),
                                e.getDescription(),
                                input.getRow(),
                                input.getColumn(),
                                input.getPosition(),
                                input.getLine(), input.getLocalAddress());
                        } catch (IllegalArgumentException ex) {
                            // ignore and continue
                        }
                    }
                    input.setPosition(begin);
                    throw new ParsingException(e.getCode(),
                        e.getDescription(),
                        input.getRow(),
                        input.getColumn(),
                        input.getPosition(),
                        input.getLine(), input.getLocalAddress());
                }
            } else {
                input.setPosition(begin);
                throw new ParsingException(
                    ArgumentConstants.CODE_PARSER_UNKNOWN_OPERATOR,
                    ArgumentConstants.UNKNOWN_OPERATOR + Utility.quote(key),
                    input.getRow(),
                    input.getColumn(),
                    input.getPosition(),
                    input.getLine(), input.getLocalAddress());
            }
        } else if (Character.isDigit(c)) {
            try {
                final Counter counter = new Counter(input.readCounter());
                final int end = input.getPosition();
                parsing.put(counter, new Position(begin, end));
                return counter;
            } catch (IllegalArgumentException e) {
                throw new ParsingException(ArgumentConstants
                    .CODE_PARSER_WRONG_ATOM_FORMAT,
                    e.getMessage(),
                    input.getRow(),
                    input.getColumn(),
                    input.getPosition(),
                    input.getLine(), input.getLocalAddress());
            } catch (ArgumentException e) {
                throw new ParsingException(e.getCode(),
                    e.getDescription(),
                    input.getRow(),
                    input.getColumn(),
                    input.getPosition(),
                    input.getLine(), input.getLocalAddress());
            }
        } else if (c == '\"') {
            try {
                final Text text = new Text(input.readQuoted());
                final int end = input.getPosition();
                parsing.put(text, new Position(begin, end));
                return text;
            } catch (IllegalArgumentException e) {
                throw new ParsingException(ArgumentConstants
                    .CODE_PARSER_WRONG_ATOM_FORMAT,
                    e.getMessage(),
                    input.getRow(),
                    input.getColumn(),
                    input.getPosition(),
                    input.getLine(), input.getLocalAddress());
            }
        } else if (c == '(') {
                throw new ParsingException(ArgumentConstants
                    .CODE_PARSER_ARGUMENT_EXPECTED,
                    ArgumentConstants.ARGUMENT_EXPECTED,
                    input.getRow(),
                    input.getColumn(),
                    input.getPosition(),
                    input.getLine(), input.getLocalAddress());
        } else if (c == ',') {
                throw new ParsingException(ArgumentConstants
                .CODE_PARSER_ARGUMENT_EXPECTED,
                    ArgumentConstants.ARGUMENT_EXPECTED,
                    input.getRow(),
                    input.getColumn(),
                    input.getPosition(),
                    input.getLine(), input.getLocalAddress());
        } else {
            throw new ParsingException(ArgumentConstants
                .CODE_PARSER_UNKNOWN_ARGUMENT_CHAR,
                ArgumentConstants.UNKNOWN_ARGUMENT_CHAR + "\"" + c + "\"",
                input.getRow(),
                input.getColumn(),
                input.getPosition(),
                input.getLine(), input.getLocalAddress());
        }
    }


    /**
     * Create an argument array out of an {@link TextInput} object.
     *
     * @param   input   source to read from
     * @param   string2class    use this mapping for parsing
     * @param   parsing     for saving positions of created arguments
     * @return  created arguments
     * @throws  ParsingException    if creation failed
     */
    private final static Argument[] readArgumentList(final TextInput input,
            final Map string2class, final ParsingTable parsing)
            throws ParsingException {
        final int begin = input.getPosition();
        input.skipWhiteSpace();
        if (input.isEmpty() || input.getChar() != '(') {
            if (input.isEmpty()) {
                input.setPosition(begin);   // for showing correct position
            }
            throw new ParsingException(ArgumentConstants
                .CODE_PARSER_NO_BEGIN_ARGUMENT_LIST,
                ArgumentConstants.NO_BEGIN_ARGUMENT_LIST,
                input.getRow(),
                input.getColumn(),
                input.getPosition(),
                input.getLine(), input.getLocalAddress());

        }
        input.readChar();       // read '('

        final List argumentList = new ArrayList();
        int end = input.getPosition();
        input.skipWhiteSpace();
        if (!input.isEmpty() && ')' != input.getChar()) {
            do {
                argumentList.add(readArgument(input, string2class, parsing));
                end = input.getPosition();
                input.skipWhiteSpace();
                if (input.isEmpty() || ')' == input.getChar()) {
                    break;
                }
                if (input.getChar() == ',') {
                    input.readChar();
                } else {
                    throw new ParsingException(ArgumentConstants
                        .CODE_PARSER_NO_ARGUMENT_SEPARATOR,
                        ArgumentConstants.NO_ARGUMENT_SEPARATOR,
                        input.getRow(),
                        input.getColumn(),
                        input.getPosition(),
                        input.getLine(), input.getLocalAddress());
                }
            } while (true);
        }

        input.skipWhiteSpace();

        if (input.isEmpty() || ')' != input.getChar()) {
            if (input.isEmpty()) {
                input.setPosition(end);   // for showing correct position
            }
            throw new ParsingException(ArgumentConstants
                .CODE_PARSER_NO_END_ARGUMENT_LIST,
                ArgumentConstants.NO_END_ARGUMENT_LIST,
                input.getRow(),
                input.getColumn(),
                input.getPosition(),
                input.getLine(), input.getLocalAddress());
        }
        input.readChar();       // read ')'

        return (Argument[]) argumentList.toArray(
            new Argument[argumentList.size()]);
    }


    /**
     * Write an {@link Argument} to an {@link Output} object.
     *
     * @param   output   destination to write to
     * @param   argument this object will be written
     * @param   class2string    use this mapping for getting a string
     * @throws  IllegalArgumentException    if writing failed
     */
    public final static void writeArgument(final Output output,
            final Argument argument, final Map class2string)
            throws IllegalArgumentException {
        final String function
            = (String) class2string.get(argument.getClass());
        if (function == null) {
            if (argument.getClass() == Counter.class) {
                output.write(((Counter) argument).getNumber());
                return;
            } else if (argument.getClass() == Text.class) {
                output.write(Utility.quote(((Text) argument).getText()));
                return;
            } else {
                throw new IllegalArgumentException(
                    ArgumentConstants.UNKNOWN_ARGUMENT_CLASS
                    + argument.getClass());
            }
        }
        output.write(function);
        final int size = argument.getArgumentSize();
        output.write("(");
        for (int i = 0; i < size; i++) {
            if (i > 0) {
                output.write(", ");
            }
            writeArgument(output, argument.getArgument(i), class2string);
        }
        output.write(")");

    }


    /**
     * Write an {@link Argument} to an <code>String</code>.
     *
     * @param   argument this object will be written
     * @param   class2string    use this mapping for getting a string
     * @return  created string
     * @throws  IllegalArgumentException    if writing failed
     */
    public final static String writeArgument(
            final Argument argument, final Map class2string)
            throws IllegalArgumentException {
        final StringBuffer buffer = new StringBuffer();
        writeArgument(new Output(buffer), argument, class2string);
        return buffer.toString();
    }


}
