package leigh.eo.schema;

import leigh.eo.EO;
import leigh.eo.EODataType;
import leigh.eo.schema.dao.ElementElementDAO;
import leigh.eo.schema.dao.FieldElementDAO;
import leigh.eo.schema.dao.LoopElementDAO;
import leigh.eo.schema.dao.SchemaElementDAO;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.StringUtil;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Properties;

/**
 * This tool generates the source-code for DAO objects based on their ESP based
 * schema. This is handy for developing Java tooling that interacts with ESP records.
 *
 * @author Alex Leigh
 */
public final class DAOGenerator {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(DAOGenerator.class);
    // The name for the DAO class
    private final static String KEY_JAVACLASS = "javaClass";
    // The package for the DAO class
    private final static String KEY_JAVAPKG = "javaPackage";
    // The element EO
    private final static String KEY_ELEMENT = "element";
    private final static String KEY_FIELDS = "fields";
    private final static String KEY_GENERATOR = "generator";
    private final static String KEY_ECMAPKG = "ecmaPackage";
    private final File baseDirectory;
    private final VelocityEngine velocity;

    public DAOGenerator(final File baseDirectory) {
        this.baseDirectory = baseDirectory;
        velocity = new VelocityEngine();
        final Properties props = new Properties();
        props.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        props.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        velocity.init(props);
    }

    /**
     * Capitalize the first letter in the provided string. This method is provided for the benefit of the
     * template so that we do not need to involve other classes in the template execution context.
     *
     * @param str The string to capitalize
     * @return The capitalized String
     */
    @SuppressWarnings("unused")
    public String capitalize(final String str) {
        return org.apache.velocity.util.StringUtils.capitalizeFirstLetter(str);
    }

    /**
     * This method is provided for the benefit of the template.
     *
     * @param str
     * @return
     */
    @SuppressWarnings("unused")
    public String toUpperCase(final String str) {
        return str.toUpperCase();
    }

    @SuppressWarnings("unused")
    public String inferType(final EO field) {
        if (field.getApiType().equals(LoopElementDAO.API_TYPE))
            return "EOLoop";

        logger.debug("Inferring type on {}", field);

        String typ = EODataType.getType(EODataType.valueOf(FieldElementDAO.getType(field))).getCanonicalName();

        //logger.info("inferred type: {} {}", field, typ);

        return typ;
    }

    @SuppressWarnings("unused")
    public String inferAccessor(final EO field) {
        if (field.getApiType().equals(LoopElementDAO.API_TYPE))
            return "getValueLoop";

        return EODataType.getAccessor(EODataType.valueOf(FieldElementDAO.getType(field)));
    }

    /**
     * Used by the template; returns a java class name for the given element name. This is
     * the KEY_NAME of the element with a capitalized first letter.
     *
     * @param element The element to return the name for.
     * @return The name
     */
    public String elementClassName(final EO element) {
        return org.apache.velocity.util.StringUtils.capitalizeFirstLetter(StringUtil.last(ElementElementDAO.getApiName(element) + "ElementDAO", "\\."));
    }

    /**
     * Generate a set of DAO classes from the provided schema. The classes will be generated into the
     * provided package name - specify in . format e.g. "com.foo.bar". If files already exist, they will
     * be silently overwritten.
     *
     * @param basePkg
     * @param schema
     * @throws FileNotFoundException
     */
    public void generateJava(final String basePkg, final EO schema) throws FileNotFoundException {
        final Template javaTemplate = velocity.getTemplate("leigh/eo/schema/JavaDAO.vm");

        final String pkgBase = basePkg.replace('.', '/');

        logger.info("Creating new DAO source-code file: {}", pkgBase);

        final File classPkg = new File(baseDirectory, pkgBase);
        classPkg.mkdirs();

        for (final EO element : SchemaElementDAO.getElements(schema)) {
            final VelocityContext vc = new VelocityContext();
            vc.put(KEY_JAVACLASS, elementClassName(element));
            vc.put(KEY_JAVAPKG, basePkg);
            vc.put(KEY_ELEMENT, element);
            vc.put(KEY_FIELDS, ElementElementDAO.getFields(element));
            vc.put(KEY_GENERATOR, this);

            final File javaClassFile = new File(classPkg, elementClassName(element) + ".java");

            logger.info("Writing class: {}", javaClassFile);

            final PrintWriter javaPw = new PrintWriter(javaClassFile);
            javaTemplate.merge(vc, javaPw);
            javaPw.close();
        }
    }

    /**
     * Write the DAOs to a the provided output writer. Providing the same writer to successive calls
     * of this method allows the output ECMAScript v5 classes to be accumulated into a single file for
     * ease of use.
     *
     * @since mk12 mod0 (RETROGRADE)
     */
    public void generateEcma5(final PrintWriter output, final String ecmaPkg, final EO schema) throws FileNotFoundException {
        final Template jsTemplate = velocity.getTemplate("leigh/eo/schema/ECMA5.vm");

        for (final EO element : SchemaElementDAO.getElements(schema)) {
            final VelocityContext vc = new VelocityContext();
            vc.put(KEY_JAVACLASS, elementClassName(element));
            vc.put(KEY_ECMAPKG, ecmaPkg);
            vc.put(KEY_ELEMENT, element);
            vc.put(KEY_FIELDS, ElementElementDAO.getFields(element));
            vc.put(KEY_GENERATOR, this);

            jsTemplate.merge(vc, output);
        }
    }
}