/*  Copyright (C) 2003-2011 JabRef contributors.
    This program 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.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.export.layout;

import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
import java.util.Vector;


/**
 * Helper class to get a Layout object.
 * 
 * <code>
 * LayoutHelper helper = new LayoutHelper(...a reader...);
 * Layout layout = helper.getLayoutFromText();
 * </code>
 *
 */
public class LayoutHelper {

    public static final int IS_LAYOUT_TEXT = 1;
    public static final int IS_SIMPLE_FIELD = 2;
    public static final int IS_FIELD_START = 3;
    public static final int IS_FIELD_END = 4;
    public static final int IS_OPTION_FIELD = 5;
    public static final int IS_GROUP_START = 6;
    public static final int IS_GROUP_END = 7;
    public static final int IS_ENCODING_NAME = 8;
    public static final int IS_FILENAME = 9;
    public static final int IS_FILEPATH = 10;
    
    private static String currentGroup = null;
    
    private PushbackReader _in;
    private Vector<StringInt> parsedEntries = new Vector<StringInt>();

    private boolean _eof = false;
    private int line = 1;

    public LayoutHelper(Reader in)
    {
        if (in == null)
        {
            throw new NullPointerException();
        }

        _in = new PushbackReader(in);
    }

    public Layout getLayoutFromText(String classPrefix) throws Exception
    {
        parse();

        StringInt si;

        for (int i = 0; i < parsedEntries.size(); i++)
        {
            si = parsedEntries.get(i);

            if ((si.i == IS_SIMPLE_FIELD) || (si.i == IS_FIELD_START) ||
                    (si.i == IS_FIELD_END) || (si.i == IS_GROUP_START) ||
                    (si.i == IS_GROUP_END))
            {
                si.s = si.s.trim().toLowerCase();
            }
        }

        Layout layout = new Layout(parsedEntries, classPrefix);

        return layout;
    }

    
    public static String getCurrentGroup() {
        return currentGroup;
    }
    
    public static void setCurrentGroup(String newGroup) {
        currentGroup = newGroup;
    }
    
    private String getBracketedField(int _field) throws IOException
    {
        StringBuffer buffer = null;
        int c;
        boolean start = false;

        while (!_eof)
        {
            c = read();

            //System.out.println((char)c);
            if (c == -1)
            {
                _eof = true;

                if (buffer != null)
                {
                    //myStrings.add(buffer.toString());
                    parsedEntries.add(new StringInt(buffer.toString(), _field));

                    //System.out.println("\nbracketedEOF: " + buffer.toString());
                }

                //myStrings.add(buffer.toString());
                //System.out.println("aha: " + buffer.toString());
                return null;
            }

            if ((c == '{') || (c == '}'))
            {
                if (c == '}')
                {
                    if (buffer != null)
                    {
                        //myStrings.add(buffer.toString());
                        parsedEntries.add(new StringInt(buffer.toString(),
                                _field));

                        //System.out.println("\nbracketed: " + buffer.toString());
                        return null;
                    }
                }
                else
                {
                    start = true;
                }
            }
            else
            {
                if (buffer == null)
                {
                    buffer = new StringBuffer(100);
                }

                if (start)
                {
                    if (c == '}')
                    {
                    }
                    else
                    {
                        buffer.append((char) c);
                    }
                }
            }
        }

        return null;
    }

    /**
     *
     */
    private String getBracketedOptionField(int _field)
        throws IOException
    {
        StringBuffer buffer = null;
        int c;
        boolean start = false;
        boolean inQuotes = false;
        boolean doneWithOptions = false;
        String option = null;
        String tmp;

        while (!_eof)
        {
            c = read();

            //System.out.println((char)c);
            if (c == -1)
            {
                _eof = true;

                if (buffer != null)
                {
                    //myStrings.add(buffer.toString());
                    if (option != null)
                    {
                        tmp = buffer.toString() + "\n" + option;
                    }
                    else
                    {
                        tmp = buffer.toString();
                    }

                    parsedEntries.add(new StringInt(tmp, IS_OPTION_FIELD));

                    //System.out.println("\nbracketedOptionEOF: " + buffer.toString());
                }

                return null;
            }
            if (!inQuotes && ((c == ']') || (c == '[') || (doneWithOptions && ((c == '{') || (c == '}')))))
            //if ((c == '{') || (c == '}') || (c == ']') || (c == '['))
            {
                if ((c == ']') || (doneWithOptions && (c == '}')))
                {
                    // changed section start - arudert
                    // buffer may be null for parameters
                    //if (buffer != null)
                    //{
                        if (c == ']' && buffer != null)
                        {
                    // changed section end - arudert
                            option = buffer.toString();
                            buffer = null;
                            start = false;
                            doneWithOptions = true;
                        }

                        //myStrings.add(buffer.toString());
                        //System.out.println("\nbracketedOption: " + buffer.toString());
                        
                        // changed section begin - arudert
                        // bracketed option must be followed by an (optionally empty) parameter
                        // if empty, the parameter is set to " " (whitespace to avoid that the tokenizer that
                        // splits the string later on ignores the empty parameter)
                        //if (buffer != null)
                        else if (c == '}')
                        {
                           String parameter = buffer == null ? " " : buffer.toString();
                           if (option != null)
                            {
                                tmp = parameter + "\n" + option;
                            }
                            else
                            {
                                tmp = parameter;
                            }

                            //System.out.println("FORMAT: '"+tmp+"'");
                            parsedEntries.add(new StringInt(tmp, IS_OPTION_FIELD));

                            return null;
                        }
                        // changed section end - arudert
                     // changed section start - arudert
                     // }
                     // changed section end - arudert
                }
                else
                {
                    start = true;
                }
            }
            else if (c == '"') {
                if (!inQuotes)
                    inQuotes = true;
                else inQuotes = false;

                if (buffer == null)
                    buffer = new StringBuffer(100);
                buffer.append('"');
            }
            else
            {
                if (buffer == null)
                {
                    buffer = new StringBuffer(100);
                }

                if (start)
                {

                    // changed section begin - arudert
                    // keep the backslash so we know wether this is a fieldname or an ordinary parameter
                    //if (c != '\\')
                    //{
                    buffer.append((char) c);
                    //}
                    // changed section end - arudert

                }
            }
        }

        return null;
    }

    private Object parse() throws IOException {
		skipWhitespace();

		int c;

		StringBuffer buffer = null;
		boolean escaped = false;

		while (!_eof) {
			c = read();

			if (c == -1) {
				_eof = true;

				/*
				 * CO 2006-11-11: Added check for null, otherwise a Layout that
				 * finishs with a curly brace throws a NPE
				 */
				if (buffer != null)
					parsedEntries.add(new StringInt(buffer.toString(), IS_LAYOUT_TEXT));

				return null;
			}

			if ((c == '\\') && (peek() != '\\') && !escaped) {
				if (buffer != null) {
					parsedEntries.add(new StringInt(buffer.toString(), IS_LAYOUT_TEXT));

					buffer = null;
				}

				parseField();

				// To make sure the next character, if it is a backslash,
				// doesn't get ignored, since "previous" now holds a backslash:
				escaped = false;
			} else {
				if (buffer == null) {
					buffer = new StringBuffer(100);
				}

				if ((c != '\\') || escaped)// (previous == '\\')))
				{
					buffer.append((char) c);
				}

				escaped = (c == '\\') && !escaped;
			}
		}

		return null;
	}

    /**
	 * 
	 */
    private void parseField() throws IOException
    {
        int c;
        StringBuffer buffer = null;
        String name;

        while (!_eof)
        {
            c = read();
            // System.out.print((char)c);
            if (c == -1)
            {
                _eof = true;
            }

            if (!Character.isLetter((char) c) && (c != '_') && (c != '-'))
            {
                unread(c);

                //System.out.println("\n#" + (char) c);
                name = buffer != null ? buffer.toString() : "";

                //System.out.println("NAME:" + name);
                buffer = null;

                if (name.charAt(0) == 'b')
                {
                    if (name.equalsIgnoreCase("begin"))
                    {
                        // get field name
                        getBracketedField(IS_FIELD_START);

                        return;
                    }
                    else if (name.equalsIgnoreCase("begingroup"))
                    {
                        // get field name
                        getBracketedField(IS_GROUP_START);
                        return;                    
                    }
                }
                else if (name.charAt(0) == 'f')
                {
                    if (name.equalsIgnoreCase("format"))
                    {
                        if (c == '[')
                        {
                            // get format parameter
                            // get field name
                            getBracketedOptionField(IS_OPTION_FIELD);

                            return;
                        }
                        else
                        {
                            // get field name
                            getBracketedField(IS_OPTION_FIELD);

                            return;
                        }
                    }
                    else if (name.equalsIgnoreCase("filename"))
                    {
                        // Print the name of the database bib file.
                        // This is only supported in begin/end layouts, not in
                        // entry layouts.
                        parsedEntries.add(new StringInt(name, IS_FILENAME));
                        return;
                    }
                    else if (name.equalsIgnoreCase("filepath"))
                    {
                        // Print the full path of the database bib file.
                        // This is only supported in begin/end layouts, not in
                        // entry layouts.
                        parsedEntries.add(new StringInt(name, IS_FILEPATH));
                        return;
                    }
                }
                else if (name.charAt(0) == 'e')
                {
                    if (name.equalsIgnoreCase("end"))
                    {
                        // get field name
                        getBracketedField(IS_FIELD_END);
                        return;
                    }
                    else if (name.equalsIgnoreCase("endgroup"))
                    {
                        // get field name
                        getBracketedField(IS_GROUP_END);
                        return;
                    }
                    else if (name.equalsIgnoreCase("encoding"))
                    {
                        // Print the name of the current encoding used for export.
                        // This is only supported in begin/end layouts, not in
                        // entry layouts.
                        parsedEntries.add(new StringInt(name, IS_ENCODING_NAME));
                        return;
                    }
                }

                // for all other cases
                parsedEntries.add(new StringInt(name, IS_SIMPLE_FIELD));

                //System.out.println(name);
                return;
            }
            else
            {
                if (buffer == null)
                {
                    buffer = new StringBuffer(100);
                }

                buffer.append((char) c);
            }
        }
    }

    private int peek() throws IOException
    {
        int c = read();
        unread(c);

        return c;
    }

    private int read() throws IOException
    {
        int c = _in.read();

        if (c == '\n')
        {
            line++;
        }

        //System.out.print((char) c);
        return c;
    }

    private void skipWhitespace() throws IOException
    {
        int c;

        while (true)
        {
            c = read();

            if ((c == -1) || (c == 65535))
            {
                _eof = true;

                return;
            }

            if (Character.isWhitespace((char) c))
            {
                continue;
            }
            else
            {
                unread(c);
            }

            break;
        }
    }

    private void unread(int c) throws IOException
    {
        if (c == '\n')
        {
            line--;
        }

        _in.unread(c);
    }
}
