/*
 *  Copyright 2006-2009 Mikhail Kryshen
 *
 *  This file is part of Tema.
 *
 *  Tema is free software: you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  Tema 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the 
 *  GNU Lesser General Public License along with Tema.  
 *  If not, see <http://www.gnu.org/licenses/>.
 */

package kryshen.tema;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import kryshen.tema.TemplateParser.DataFormat;
import static kryshen.tema.TemplateParser.Terminator;
import kryshen.tema.io.CopyWriter;
import kryshen.tema.io.NullWriter;
import kryshen.tema.io.TemplateReader;

/**
 * Parser for a function data.
 *
 * @author Mikhail Kryshen
 */
public class FunctionDataParser {
    static final char[] ARG_SEPARATORS = TemplateParser.LIST_SEPARATORS;
    
    private final TemplateParser tp;
    private final DataFormat fd;
    private final TemplateReader in;
    private final String name;

    private boolean available = true;
    
    private int lastReturnCode = 0;
    
    FunctionDataParser(TemplateParser tp, DataFormat fd, TemplateReader in,
            String name) throws IOException, TemplateException {
        this.tp = tp;
        this.fd = fd;
        this.in = in;
        this.name = name;
        
        // Check for empty function data.
        checkAvailable();
    }
    
    private void checkAvailable() throws IOException, TemplateException {
        if (tp.checkCloseBracket(in)) {
            // Complete parsing the instruction.
            skipData();
        }
    }
    
    private int parseData(Writer out, boolean argument, boolean skip)
    throws IOException, TemplateException {
        
        if (!available)
            throw new TemplateException
                    ("Unexpected end of instruction data.", in);
        
        DataFormat fd = skip ? this.fd.noInvoke() : this.fd;
        
        TemplateParser.Result r;
        
//        if (fd.subfunction && argument) {
//            // Allow subfunction to pass a list of arguments
//            StringWriter sw = new StringWriter();
//            tp.parse(in, sw, fd);
//            sw.close();
//            in = new TemplateReader(new StringReader(sw.toString()), in);
//            this.fd = DataFormat.VERBATIM;
//            r = parseData(out, true, false);
//        } else
        if (fd.subfunction) {
            r = tp.parse(in, out, fd);
        } else if (argument) {
            // Skip duplicate separators before argument.
            tp.skip(in, ARG_SEPARATORS);
            
            r = tp.parse(in, out, fd, ARG_SEPARATORS);
            
            // Skip duplicate separators after argument.
            if (r.terminator == Terminator.SEPARATOR) {
                tp.skip(in, ARG_SEPARATORS);
                checkAvailable();
            }
        } else {
            r = tp.parse(in, out, fd, null);
        }

        if (r.terminator == Terminator.EOF) {
            throw new TemplateException
                    ("Unexpected end of instruction data", in);
        }

        if (r.terminator != Terminator.SEPARATOR)
            available = false; // No more function data available.
        
        lastReturnCode = r.retCode;
        return r.retCode;
    }
    
    /**
     * Parse function data.
     */
    public int parseData(Writer out) throws IOException, TemplateException {
        return parseData(out, false, false);
    }
    
    /**
     * Skip function data (do not call any functions).
     */
    public void skipData() throws IOException, TemplateException {
        parseData(NullWriter.INSTANCE, false, true);
    }
    
    /**
     * Get function data as string.
     */
    public String getData() throws IOException, TemplateException {
        StringWriter sw = new StringWriter();
        parseData(sw);
        sw.close();
        return sw.toString();
    }
    
    /**
     * Parse function data into specified <code>Writer</code>.
     * Returns copy of the parsed data as string.
     */
    public String getData(Writer out) throws IOException, TemplateException {
        StringWriter sw = new StringWriter();
        parseData(new CopyWriter(sw, out));
        sw.close();
        return sw.toString();
    }
    
    public int parseNextArg(Writer out) throws IOException, TemplateException {
        return parseData(out, true, false);
    }
    
    public void skipNextArg() throws IOException, TemplateException {
        parseData(NullWriter.INSTANCE, true, true);
    }
    
    public String getNextArg() throws IOException, TemplateException {
        StringWriter sw = new StringWriter();
        parseNextArg(sw);
        sw.close();
        return sw.toString();
    }
    
    public List<String> getArgs() throws IOException, TemplateException {
        List<String> l = new ArrayList<String>();
        
        while (available) {
            l.add(getNextArg());
        }
        
        return l;
    }
    
    public boolean hasMoreData() {
        return available;
    }
    
    public int getLastReturnCode() {
        return lastReturnCode;
    }
    
    public TemplateParser getTemplateParser() {
        return tp;
    }
    
    public Context getContext() {
        return tp.getContext();
    }
    
    public TemplateReader getTemplateReader() {
        return in;
    }

    public String getName() {
        return name;
    }

    public void warning(String message) {
        System.err.println(in.getSource() + ":" + (in.getLineNumber() + 1) +
                ": " + name + ": " + message);
    }

    public File createFile(String path) {
        File file = new File(path);
        
        if (file.isAbsolute())
            return file;

        File base = tp.getContext().getBaseDirectory();

        return base.getPath().isEmpty()
                ? new File(path)
                : new File(base, path);
    }
}