/*
 * [The "BSD license"]
 *  Copyright (c) 2011 Terence Parr
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.stringtemplate.v4;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.Token;
import org.stringtemplate.v4.compiler.CompiledST;
import org.stringtemplate.v4.compiler.STException;
import org.stringtemplate.v4.misc.ErrorType;
import org.stringtemplate.v4.misc.Misc;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

// TODO: caching?

/** A directory or directory tree full of templates and/or group files.
 *  We load files on-demand. Dir search path: current working dir then
 *  CLASSPATH (as a resource).  Do not look for templates outside of this dir
 *  subtree (except via imports).
 */
public class STGroupDir extends STGroup {
    public String groupDirName;
    public URL root;

    public STGroupDir(String dirName) { this(dirName, '<', '>'); }
    public STGroupDir(URL root) { this(root, "UTF-8", '<', '>'); }

    public STGroupDir(String dirName, char delimiterStartChar, char delimiterStopChar) {
        super(delimiterStartChar, delimiterStopChar);
        this.groupDirName = dirName;
        File dir = new File(dirName);
        if ( dir.exists() && dir.isDirectory() ) {
            // we found the directory and it'll be file based
            try {
                root = dir.toURI().toURL();
            }
            catch (MalformedURLException e) {
                throw new STException("can't load dir "+dirName, e);
            }
            if ( verbose ) System.out.println("STGroupDir("+dirName+") found at "+root);
        }
        else {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            root = cl.getResource(dirName);
            if ( root==null ) {
                cl = this.getClass().getClassLoader();
                root = cl.getResource(dirName);
            }
            if ( verbose ) System.out.println("STGroupDir("+dirName+") found via CLASSPATH at "+root);
            if ( root==null ) {
                throw new IllegalArgumentException("No such directory: "+
                                                       dirName);
            }
        }
        root = normalizeURL(root);
    }

    public STGroupDir(String dirName, String encoding) {
        this(dirName, encoding, '<', '>');
    }

    public STGroupDir(String dirName, String encoding,
                      char delimiterStartChar, char delimiterStopChar)
    {
        this(dirName, delimiterStartChar, delimiterStopChar);
        this.encoding = encoding;
    }

    public STGroupDir(URL root, String encoding,
                      char delimiterStartChar, char delimiterStopChar)
    {
        super(delimiterStartChar, delimiterStopChar);
        this.groupDirName = new File(root.getFile()).getName();
        this.root = root;
        this.encoding = encoding;
        this.root = normalizeURL(this.root);
    }

    @Override
    public void importTemplates(Token fileNameToken) {
        String msg =
            "import illegal in group files embedded in STGroupDirs; "+
            "import "+fileNameToken.getText()+" in STGroupDir "+this.getName();
        throw new UnsupportedOperationException(msg);
    }

    /** Load a template from directory or group file.  Group file is given
     *  precedence over directory with same name. {@code name} is always fully-qualified.
     */
    @Override
    protected CompiledST load(String name) {
        if ( verbose ) System.out.println("STGroupDir.load("+name+")");
        String parent = Misc.getParent(name); // must have parent; it's fully-qualified
        String prefix = Misc.getPrefix(name);
//      if (parent.isEmpty()) {
//          // no need to check for a group file as name has no parent
//            return loadTemplateFile("/", name+TEMPLATE_FILE_EXTENSION); // load t.st file
//      }

        URL groupFileURL;
        try { // see if parent of template name is a group file
            groupFileURL = new URI(root+parent+GROUP_FILE_EXTENSION).normalize().toURL();
        }
        catch (MalformedURLException | URISyntaxException e) {
            errMgr.internalError(null, "bad URL: "+root+parent+GROUP_FILE_EXTENSION, e);
            return null;
        }

        InputStream is = null;
        try {
            is = groupFileURL.openStream();
        }
        catch (IOException ioe) {
            // must not be in a group file
            String unqualifiedName = Misc.getFileName(name);
            return loadTemplateFile(prefix, unqualifiedName+TEMPLATE_FILE_EXTENSION); // load t.st file
        }
        finally { // clean up
            try {
                if (is!=null ) is.close();
            }
            catch (IOException ioe) {
                errMgr.internalError(null, "can't close template file stream "+name, ioe);
            }
        }

        loadGroupFile(prefix, root+parent+GROUP_FILE_EXTENSION);
        return rawGetTemplate(name);
    }

    /** Load .st as relative file name relative to root by {@code prefix}. */
    public CompiledST loadTemplateFile(String prefix, String unqualifiedFileName) {
        if ( verbose ) System.out.println("loadTemplateFile("+unqualifiedFileName+") in groupdir "+
                                          "from "+root+" prefix="+prefix);
        URL f;
        try {
            f = new URI(root+prefix+unqualifiedFileName).normalize().toURL();
        }
        catch (MalformedURLException | URISyntaxException me) {
            errMgr.runTimeError(null, null, ErrorType.INVALID_TEMPLATE_NAME,
                                me, root+ unqualifiedFileName);
            return null;
        }

        ANTLRInputStream fs;
        try {
            fs = new ANTLRInputStream(f.openStream(), encoding);
            fs.name = unqualifiedFileName;
        }
        catch (IOException ioe) {
            if ( verbose ) System.out.println(root+"/"+unqualifiedFileName+" doesn't exist");
            //errMgr.IOError(null, ErrorType.NO_SUCH_TEMPLATE, ioe, unqualifiedFileName);
            return null;
        }

        return loadTemplateFile(prefix, unqualifiedFileName, fs);
    }

    @Override
    public String getName() { return groupDirName; }
    @Override
    public String getFileName() { return root.getFile(); }
    @Override
    public URL getRootDirURL() { return root; }

    /** verify there is no extra slash on the end of URL */
    public URL normalizeURL(URL url) {
        String urlS = url.toString();
        if ( urlS.endsWith("/") ) {
            try {
                url = new URL(urlS.substring(0,urlS.length()-1));
            }
            catch (MalformedURLException e) {
                errMgr.internalError(null, "bad URL: "+urlS, e);
            }
        }
        return url;
    }
}
