/*
 *  Copyright 2006-2008 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/>.
 *
 *  $Id: Standard.java,v 1.34 2008/02/19 17:32:17 mikhail Exp $
 */

package kryshen.tema.functions;

import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import kryshen.tema.Context;
import kryshen.tema.Function;
import kryshen.tema.FunctionDataParser;
import kryshen.tema.Tema;
import kryshen.tema.TemplateException;
import kryshen.tema.io.NullWriter;

/**
 * Standard TEMA functions.
 */
public class Standard {
    
    /**
     * Output Tema version.
     */
    public static final Function TEMA = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            out.write(Tema.TITLE + " " + Tema.VERSION
                    /*+ " by " + Tema.AUTHOR*/);
            return 1;
        }
    };
    
    /**
     * Define static variable.
     */
    public static final Function SET = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            String name = fdp.getNextArg();
            
            if (fdp.getLastReturnCode() == 0)
                return 0;
            
            String data = fdp.getData(out);
            fdp.getContext().set(name, data);
            
            return fdp.getLastReturnCode();
        }
    };
    
    /**
     * Recursively remove definition.
     */
    public static final Function UNSET = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            int r = 0;
            
            while (fdp.hasMoreData()) {
                String name = fdp.getNextArg();
                
                if (fdp.getLastReturnCode() == 0)
                    continue;
                
                if (fdp.getContext().unset(name))
                    r++;
            }
            
            return r;
        }
    };
    
    /**
     * Export variable to the global (outermost) context.
     */
    public static final Function EXPORT = new Function() {
        public int invoke(FunctionDataParser fdp,
                final Writer out)
                throws IOException, TemplateException {
            
            String name = fdp.getNextArg();
            
            if (fdp.getLastReturnCode() == 0)
                return 0;
            
            // Set and export.
            if (fdp.hasMoreData()) {
                Object value = fdp.getData(out);
                
                fdp.getContext().export(name, value);
                return fdp.getLastReturnCode();
            }
            
            // Only export.
            if (fdp.getContext().export(name)) {
                out.write(name);
                return 1;
            }
            
            return 0;
        }
    };
    
    /**
     * Copy macro definitions.
     */
    public static final Function ASSIGN = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
                throws IOException, TemplateException {

            String name1 = fdp.getNextArg();

            if (fdp.getLastReturnCode() == 0)
                return 0;
            
            String name2 = fdp.getData();
            
            if (fdp.getLastReturnCode() == 0)
                return 0;

            Context context = fdp.getContext();
            Object value = context.get(name2);
            
            if (value == null)
                return 0;
            
            context.set(name1, value);
            
            return 1;
        }
    };
    
    /**
     * Invoke function specified by name.
     */
    public static final Function INVOKE = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            String name = fdp.getNextArg();
            
            if (fdp.getLastReturnCode() == 0)
                return 0;
            
            return fdp.getTemplateParser().invoke(name, fdp, out);            
        }
    };
    
    /**
     * Create definiton as a new Java object.
     */
    public static final Function LOAD = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            final String name = fdp.getNextArg();
            final String className = fdp.getNextArg();
            
            List<URL> urls = new ArrayList<URL>(1);
            
            while (fdp.hasMoreData()) {
                urls.add(new URL(fdp.getNextArg()));
            }
            
            ClassLoader loader = this.getClass().getClassLoader();
            
            if (urls.size() > 0) {
                loader = new URLClassLoader
                        (urls.toArray(new URL[0]), loader);
            }
            
            Class<?> loadedClass;
            Object instance;
            
            try {
                loadedClass = loader.loadClass(className);
            } catch (ClassNotFoundException e) {
                throw new TemplateException
                        ("Class not found", e, fdp.getTemplateReader());
            }
            
            try {
                instance = loadedClass.newInstance();
            } catch (InstantiationException e) {
                throw new TemplateException
                        ("Could not load class", e, fdp.getTemplateReader());
            } catch (IllegalAccessException e) {
                throw new TemplateException
                        ("Could not load class", e, fdp.getTemplateReader());
            }
            
            fdp.getContext().set(name, instance);
                        
            out.write(name);
            return 1;
        }
    };
    
    public static final Function ECHO = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            if (!fdp.hasMoreData())
                return 0;
            
            return fdp.parseData(out);
        }
    };
    
    /**
     * Process arguments, but output nothing.
     */
    public static final Function SILENT = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            if (!fdp.hasMoreData())
                return 0;
            
            return fdp.parseData(NullWriter.INSTANCE);
        }
    };
    
    /**
     * Skip arguments.
     */
    public static final Function SKIP = new Function() {
        public int invoke(FunctionDataParser fdp, Writer out)
        throws IOException, TemplateException {
            
            // Ignore arguments.
            return 0;
        }
    };
}