package de.renew.plugindevelopment.generating;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;

import CH.ifa.draw.DrawPlugin;
import CH.ifa.draw.io.SimpleFileFilter;
import de.renew.plugin.IPlugin;
import de.renew.plugin.PluginManager;
import de.renew.plugin.PluginProperties;
import de.renew.plugindevelopment.PluginDevelopmentPlugin;


/**
 * Generates a (Renew) plugin folder structure for a modular Renew Plugin.
 * Includes source folder structure, build.gradle, gradle.properties, plugin.cfg, module-info.java
 *
 * This version is written for AOSE projects (2020ff):
 *
 * Excerpt from the README.pd
 * <pre>
 *  PluginDevelopment for Renew (Beta)
 *
 *  Menu Command extends Plugin menu by entry
 *  Plugin Development >> Create Renew Plugin Folder
 *
 *  Use as follows.
 *
 *  - Create a new text file "name.plg" (properties-like file)
 *  - add the entry (key value) "appName = YourPluginName"
 *  - Start Renew with pd and hit command.
 *  - plugindevelopment will prompt to create a new Plugin Directory
 *    default given in in plugindevelopment plugin load path/../../../Renew
 *
 *    Will create:
 *    - src/main/java folder
 *    - README.md
 *    - resources/plugin.cfg
 *    - build.gradle
 *    - module-info.java
 *    </pre>
 *
 * @author cabac
 */
public class StandardPluginGenerator implements PluginGenerator {
    private static String _standardTemplateFolder = "/templates/standard/";
    private static final String SOURCE_PATH_PREFIX = "src/main/java/";
    /**
     * Logger instance for logging messages and errors in the StandardPluginGenerator class.
     */
    public static org.apache.log4j.Logger _logger =
        org.apache.log4j.Logger.getLogger(StandardPluginGenerator.class);
    private String _dirRenewCheckout = "";
    /**
     * Velocity Engine responsible for processing templates and generating files for the plugin structure.
     */
    protected VelocityEngine _ve;
    protected URL _location;
    /**
     * The folder path where template files are located for generating the plugin structure.
     */
    protected String _templateFolder;

    /**
     * Default constructor for StandardPluginGenerator.
     */
    public StandardPluginGenerator() {
        _location = PluginDevelopmentPlugin.getLocation();
        this._templateFolder = _standardTemplateFolder;
        _dirRenewCheckout = new File(_location.getPath()).getParentFile().getParentFile()
            .getParentFile().getParent();
        setUpVelocityEngine();
    }

    /**
     * Initializes the velocity engine.
     */
    protected void setUpVelocityEngine() {
        _ve = new VelocityEngine();

        _ve.setProperty("resource.loader", "file, class, jar");
        _ve.setProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
        _ve.setProperty(
            "class.resource.loader.class",
            "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

        //Jar Resource Loader to search the jar file of the JavaCodeGeneration plugin for the templates.
        _ve.setProperty("jar.resource.loader.description", "Velocity Jar Resource Loader");
        _ve.setProperty(
            "jar.resource.loader.class",
            "org.apache.velocity.runtime.resource.loader.JarResourceLoader");

        _ve.setProperty("jar.resource.loader.path", "jar:" + _location.toString());

        //Jar Resource Loader to search the jar file of the UseCaseComponents plugin for the templates.
        _ve.setProperty("jar.resource.loader.description", "Velocity Jar Resource Loader");
        _ve.setProperty(
            "jar.resource.loader.class",
            "org.apache.velocity.runtime.resource.loader.JarResourceLoader");

        _ve.setProperty(
            "jar.resource.loader.path", "jar:" + PluginDevelopmentPlugin.getLocation().toString());



        try {
            _ve.init();
        } catch (Exception e) {
            _logger.error(e.getMessage(), e);
        }

    }

    /**
     * Tries to find a config file (*.plg) with the name 'test'.* Prompts for an alternative location if it does not exist. Does
     * some simple checks on the file system and triggers the creation of the
     * folder structure and the generation of the files from the templates.
     */
    @Override
    public void generate() {
        _logger.info(StandardPluginGenerator.class.getName() + "Root dir is: " + _dirRenewCheckout);
        String name = "test"; //hot-hack
        File plgfile = new File(
            _dirRenewCheckout + File.separator + "develop" + File.separator + name + ".plg");
        File propsfile = null;
        Properties props = new Properties();
        InputStream inStream;
        String appName = null;

        // if test exists ask if this should be used
        boolean plgFileChosen = false;
        if (plgfile.exists()) {
            int answer = JOptionPane.showConfirmDialog(
                editorFrame(), "Found 'test' in Renew/develop.\n"
                    + "do you want to use this file as your configuration file?");
            _logger.info("PLG file found. Using default config file: " + name + ".plg");
            if (answer == JOptionPane.OK_OPTION) {
                propsfile = plgfile;
                plgFileChosen = true;
            }
        }

        boolean proceedWithoutPropertyfile = false;

        // if no test file exists or test file is not chosen.
        if (!plgfile.exists() || !plgFileChosen) {
            // choose a prop file for the setup of a Renew-Plugin
            JFileChooser jfc = new JFileChooser(_dirRenewCheckout);
            FileFilter ff = new PLGFileFilter();
            jfc.setFileFilter(ff);
            jfc.setDialogTitle("Choose a Plugin Setup Property File");
            int returnValue = jfc.showOpenDialog(DrawPlugin.getGui().getFrame());
            if (returnValue == JFileChooser.APPROVE_OPTION) {
                propsfile = jfc.getSelectedFile();
            } else {
                String answer = JOptionPane.showInputDialog(
                    "You have not chosen a *.plg file.\n "
                        + "If you wish to create a Plugin without a plg file you need to provide a Name.");
                if (answer == null || answer.isEmpty()) {
                    _logger.info(StandardPluginGenerator.class.getName() + ": Canceled");
                    return;
                } else {
                    appName = answer;
                    proceedWithoutPropertyfile = true;
                    props.put("appName", appName);
                }
            } // else {
              //                logger.info("Cancel pressed.");
              //                return;
              //            }
        }

        //  not canceled  -- propsfile was set
        // try to read properties from it
        if (!proceedWithoutPropertyfile) {
            try {
                inStream = new FileInputStream(propsfile);
                props.load(inStream);

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // check if property appName is set
        //NOTICEnull
        if (propsfile != null && !props.containsKey("appName") && appName != null
            && !appName.isEmpty()) {
            _logger.info("You must specify the property \"appName\".");
            JOptionPane.showMessageDialog(
                editorFrame(), "Please set the property 'appName' in " + propsfile.getName());
            return;
        }
        appName = props.getProperty("appName");
        PluginManager pluginManager = PluginManager.getInstance();
        IPlugin pdPlugin = pluginManager.getPluginByName("Renew Plugin Development");
        PluginProperties pdProperties = pdPlugin.getProperties();
        String pdProperty = pdProperties.getVersion();
        props.setProperty("pdversion", pdProperty);
        _logger.info("PluginDevelopment Plugin version: " + props.getProperty("pdversion"));

        JFileChooser chooser = new JFileChooser(_dirRenewCheckout + File.separator + "..");
        chooser.setSelectedFile(new File("Renew"));
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        chooser.setDialogType(JFileChooser.CUSTOM_DIALOG);
        chooser.setDialogTitle("Select Renew Plugin Source Folder.");
        chooser.setToolTipText("If you do not know better choose the Renew folder.");
        int returnValue = chooser.showDialog(editorFrame(), "Choose Directory");

        File selectedDir;
        if (returnValue == JFileChooser.APPROVE_OPTION) {
            selectedDir = chooser.getSelectedFile();
        } else {
            _logger.info("User abort.");
            return;
        }
        if (!new File(selectedDir, "ant").exists()) {
            _logger.info("Not a valid Root for Plugins.");
            return;
        }

        // assuming that now we have found the right location 
        // which should be a directory, where the NamedPlugin should be created in 
        String pluginParentDirName = selectedDir.getAbsolutePath();


        // check if plugin directory does not already exist
        if (new File(pluginParentDirName + File.separator + appName).exists()) {
            _logger.info(
                "Plugin as plugin named " + appName
                    + " already exists. Please erase and try again.");
            return;
        }
        createFolders(pluginParentDirName, appName);
        createFilesFromTemplates(pluginParentDirName, props);
    }

    private JFrame editorFrame() {
        return DrawPlugin.getGui().getFrame();
    }

    /**
     * Prepares the context for velocity and creates the files from the
     * templates.
     *
     * @param rootDir The root directory.
     * @param props   The properties for configuring the generation from configuration file (*plg).
     */
    protected void createFilesFromTemplates(String rootDir, Properties props) {
        try {
            // copy the build file
            Context context = getContextFromProperties(props);
            context
                .put("generic-description", "This is the generated description for this plugin.");
            context.put("generic-name", props.getProperty("appName").toLowerCase());
            context.put("smallAppName", props.getProperty("appName").toLowerCase());

            // copy the build.gradle file
            createFile(_ve, context, rootDir, "", "build.gradle", null);

            // copy the gradle.properties file
            createFile(_ve, context, rootDir, "", "gradle.properties", null);

            // copy the module-info.java file
            createFile(
                _ve, context, rootDir,
                "src/main/java/de.renew." + props.getProperty("appName").toLowerCase(),
                "module-info.java", null);

            // copy the plugin.cfg file
            createFile(_ve, context, rootDir, "src/main/resources", "plugin.cfg", null);

            // copy the README.md file
            createFile(_ve, context, rootDir, "", "README.md", null);

            // copy the plugin class facade file
            createFile(
                _ve, context, rootDir,
                SOURCE_PATH_PREFIX + File.separator + "de.renew." + context.get("smallAppName")
                    + File.separator + "de" + File.separator + "renew" + File.separator
                    + context.get("smallAppName"),
                "PluginClass.java", props.getProperty("appName") + "Plugin.java");


        } catch (ResourceNotFoundException e) {
            _logger.error(e.getMessage());
            _logger.debug(e.getStackTrace());
        } catch (ParseErrorException e) {
            _logger.error(e.getMessage());
            _logger.debug(e.getStackTrace());
        } catch (Exception e) {
            _logger.error(e.getMessage());
            _logger.debug(e.getStackTrace());
        }
    }


    /**
    * Pipes the properties into a velocity context.
    *
    * @param properties -
    *            given Properties from configuration file (*plg)
    * @return a newly created context containing the values of the properties
    */
    protected Context getContextFromProperties(Properties properties) {
        Context context;
        context = new VelocityContext();
        Enumeration<Object> en = properties.keys();
        while (en.hasMoreElements()) {
            String key = (String) en.nextElement();
            context.put(key, properties.getProperty(key));

        }
        return context;
    }

    /**
     * Creates a given file from a template in a specified folder using the Velocity
     * engine.
     *
     * @param ve           The Velocity engine.
     * @param context      The context for the created file.
     * @param rootDir      The root directory where the file should be created.
     * @param folderName   The name of the subfolder where the file should be created.
     * @param fileName     The name of the file to be created without an extension.
     * @param newFileName  The new filename if different from the original filename (optional).
     * @throws ResourceNotFoundException   If the resource is not found.
     * @throws ParseErrorException        If a parse error occurs.
     * @throws Exception                  If an exception occurs.
     * @throws IOException                If an I/O exception occurs.
     * @throws MethodInvocationException   If a method invocation error occurs.
     */
    protected void createFile(
        VelocityEngine ve, Context context, String rootDir, String folderName, String fileName,
        String newFileName) throws ResourceNotFoundException, ParseErrorException, Exception,
        IOException, MethodInvocationException
    {
        Template buildTemp;
        String sourceFilepath = this._templateFolder + fileName + ".vm";
        _logger.debug("PluginGenerator: Templates taken from: " + sourceFilepath);
        buildTemp = ve.getTemplate(sourceFilepath);

        String destPath = createDestFolder(context, rootDir, folderName, fileName, newFileName);
        File file = new File(destPath);
        _logger.debug("PluginGenerator: ----> writing: " + destPath);
        FileWriter fw;
        fw = new FileWriter(file);
        buildTemp.merge(context, fw);
        fw.close();
    }


    /**
     * Creates the destination folder and returns the path to it.
     *
     * @param context      The context for the created file.
     * @param rootDir      The root directory where the file should be created.
     * @param folderName   The name of the subfolder where the file should be created.
     * @param filename     The name of the file to be created without an extension.
     * @param newFileName  The new filename if different from the original filename (optional).
     * @return The path to the created destination folder
     */
    protected String createDestFolder(
        Context context, String rootDir, String folderName, String filename, String newFileName)
    {
        String destPath;
        String destFoldername = rootDir + File.separator + context.get("appName");
        _logger.debug("PluginGenerator: Destfoldername " + destFoldername);
        if (folderName != null && !folderName.isEmpty()) {
            destFoldername += File.separator + folderName;
            new File(destFoldername).mkdirs();
        }
        if (newFileName == null || newFileName.isEmpty()) {
            destPath = destFoldername + File.separator + filename;
        } else {
            destPath = destFoldername + File.separator + newFileName;
        }
        return destPath;
    }


    /**
     * Creates a folder structure in the Mulan root directory.
     *
     * @param rootDir The root directory.
     * @param appName The name of the application/plugin.
     */
    protected void createFolders(String rootDir, String appName) {
        String sourcePath = SOURCE_PATH_PREFIX + File.separator + "de.renew."
            + appName.toLowerCase() + File.separator + "de" + File.separator + "renew"
            + File.separator + appName.toLowerCase();
        File directory = new File(rootDir + File.separator + appName + File.separator + sourcePath);
        directory.mkdirs();


    }

    /**
     * Custom file filter for Plugin Configuration files (*.plg).
     * Extends {@link SimpleFileFilter}.
     */
    class PLGFileFilter extends SimpleFileFilter {
        public PLGFileFilter() {
            super();
            setExtension("plg");
            setDescription("Plugin Configuration (*.plg)");
        }
    }
}