/*
 *    Copyright 2006-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *
 */
package org.mybatis.generator.eclipse.ui.launcher;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.ant.core.AntCorePlugin;
import org.eclipse.ant.core.AntCorePreferences;
import org.eclipse.ant.core.AntRunner;
import org.eclipse.ant.core.IAntClasspathEntry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate;
import org.mybatis.generator.eclipse.ui.Activator;
import org.mybatis.generator.eclipse.ui.Messages;
import org.mybatis.generator.eclipse.ui.launcher.tabs.LauncherUtils;

/**
 * This launcher works by invoking the AntRunner on a generated ant script for the generator.
 * It turns out that calculating the classpath for running a plain Java launch configuration
 * is an incredible pain when we need to include Eclipse dependencies, and then we still
 * have the problem that the Java launch would need to be able run in the same JRE as the
 * workspace so that it could resolve Eclipse projects and directories.  In the end, it is
 * far simpler to reuse the built in Ant support which handles most of those issues
 * automatically.
 * 
 * @author Jeff Butler
 *
 */
public class GeneratorLaunchConfigurationDelegate extends AbstractJavaLaunchConfigurationDelegate {

    @Override
    public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor)
            throws CoreException {
        AntRunner antRunner = new AntRunner();
        
        String buildFile;
        try {
            buildFile = generateAntScript(configuration);
        } catch (IOException e) {
            Status status = new Status(Status.ERROR, Activator.PLUGIN_ID, Messages.LAUNCH_ERROR_ERROR_GENERATING_ANT_FILE, e);
            throw new CoreException(status);
        }

        antRunner.setBuildFileLocation(buildFile);
        antRunner.addBuildLogger("org.mybatis.generator.eclipse.ui.ant.GeneratorBuildLogger"); //$NON-NLS-1$
        modifyAntClasspathIfNecessary(configuration, antRunner);
        if (ILaunchManager.DEBUG_MODE.equals(mode)) {
            antRunner.setArguments("-debug"); //$NON-NLS-1$
        }
        
        antRunner.addUserProperties(getUserProperties(configuration));
        antRunner.run(monitor);
        
        if (LauncherUtils.getBooleanOrFalse(configuration, GeneratorLaunchConstants.ATTR_SQL_SCRIPT_SECURE_CREDENTIALS)) {
            File file = new File(buildFile);
            file.delete();
        }
    }
    
    private Map<String, String> getUserProperties(ILaunchConfiguration configuration) throws CoreException {
        String[] env = getEnvironment(configuration);
        if (env == null) {
            return Collections.emptyMap();
        }
        
        return Arrays.stream(env)
                .map(this::parseProperty)
                .collect(Collectors.toMap(sa -> sa[0], sa -> sa[1]));
    }
    
    private String[] parseProperty(String in) {
        return in.split("=", 2); //$NON-NLS-1$
    }

    private void modifyAntClasspathIfNecessary(ILaunchConfiguration configuration, AntRunner antRunner) throws CoreException {
        String[][] classpathAndModulePathEntries = getClasspathAndModulepath(configuration);
        if(classpathAndModulePathEntries == null || classpathAndModulePathEntries.length == 0) {
            return;
        }
        
        String[] classpathEntries = classpathAndModulePathEntries[0];
        if (classpathEntries == null || classpathEntries.length == 0) {
            return;
        }
        
        List<URL> classpathURLs = new ArrayList<>();

        // this is a hack determined by looking at the source for 
        // org.eclipse.ant.internal.core.ant.InternalAntRunner
        //
        // we may need to add JARs to the classpath for this runner.  If we don't
        // specify a custom classpath, then the classpath is 
        //   getAntHomeClasspathEntries() +
        //   getAdditionalClasspathEntries() +
        //   getExtraClasspathURLs()
        //
        // if we do specify a custom classpath, it is only our specified
        // classpath + getExtraClasspathURLs()
        //
        // So we will build the first two ourselves, then add our extra JARs
        //
        AntCorePreferences antCorePreferences = AntCorePlugin.getPlugin().getPreferences();
        
        for (IAntClasspathEntry entry : antCorePreferences.getAntHomeClasspathEntries()) {
            classpathURLs.add(entry.getEntryURL());
        }
        
        for (IAntClasspathEntry entry : antCorePreferences.getAdditionalClasspathEntries()) {
            classpathURLs.add(entry.getEntryURL());
        }
        
        for (String classPathEntry : classpathEntries) {
            File f = new File (classPathEntry);
            try {
                classpathURLs.add(f.toURI().toURL());
            } catch (MalformedURLException e) {
            }
        }

        antRunner.setCustomClasspath(classpathURLs.toArray(new URL[classpathURLs.size()]));
    }

    /**
     * Generates and writes an Ant file for the build.
     * 
     * @param configuration
     * @return the full path of the generated file
     * @throws IOException
     */
    private String generateAntScript(ILaunchConfiguration configuration) throws IOException {
        IPath ipath = Activator.getDefault().getStateLocation().append(".generatedAntScripts"); //$NON-NLS-1$
        File file = ipath.toFile();
        if (!file.exists()) {
            file.mkdirs();
        }
        file = ipath.append(configuration.getName() + ".xml").toFile(); //$NON-NLS-1$

        AntFileGenerator antFileGenerator = new AntFileGenerator(configuration);
        
        writeFile(file, antFileGenerator.getAntFileContent());
        
        return file.getAbsolutePath();
    }

    private void writeFile(File file, String content) throws IOException {
        FileOutputStream fos = new FileOutputStream(file, false);
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8"); //$NON-NLS-1$
        
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write(content);
        bw.close();
    }
}
