/**
 *
 */
package de.saumya.mojo.ruby.rails;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.repository.RepositorySystem;
import org.apache.velocity.VelocityContext;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.velocity.VelocityComponent;

import de.saumya.mojo.ruby.gems.DefaultGemManager;
import de.saumya.mojo.ruby.gems.GemException;
import de.saumya.mojo.ruby.gems.GemsInstaller;
import de.saumya.mojo.ruby.script.Script;
import de.saumya.mojo.ruby.script.ScriptException;

@Component(role = RailsManager.class)
public class DefaultRailsManager implements RailsManager {

    private static Map<String, String> DATABASES         = new HashMap<String, String>();
    static {
        DATABASES.put("sqlite", "sqilte3");
        DATABASES.put("postgres", "postgresql");
    }

    @Requirement
    private RepositorySystem           repositorySystem;

    @Requirement
    private ProjectBuilder             builder;

    @Requirement
    private VelocityComponent          velocityComponent;

    @Requirement
    private Logger                     logger;

    public void initInstaller(final GemsInstaller installer,
            final File launchDirectory) throws RailsException, IOException {
        patchBootScript(launchDirectory);
        setupWebXML(launchDirectory);
        setupGemfile(installer, launchDirectory);
    }

    @Deprecated
    private void patchBootScript(final File launchDirectory)
            throws RailsException {
        final File boot = new File(new File(launchDirectory, "config"),
                "boot.rb");
        if (boot.exists() && new File(launchDirectory, "Gemfile.maven").exists()) {
            this.logger.info( "DEPRECATED: the use of Gemfile.maven deprecated. " +
              "use '$ mvn rails:pom' to generate a pom out of the Gemfile");
            InputStream bootIn = null;
            InputStream bootOrig = null;
            InputStream bootPatched = null;
            OutputStream bootOut = null;
            try {
                bootIn = new FileInputStream(boot);
                bootOrig = Thread.currentThread()
                        .getContextClassLoader()
                        .getResourceAsStream("boot.rb.orig");
                if (IOUtil.contentEquals(bootIn, bootOrig)) {
                    bootIn.close();
                    bootOut = new FileOutputStream(boot);
                    bootPatched = Thread.currentThread()
                            .getContextClassLoader()
                            .getResourceAsStream("boot.rb");
                    IOUtil.copy(bootPatched, bootOut);
                }
            }
            catch (final IOException e) {
                throw new RailsException("error patching config/boot.rb", e);
            }
            finally {
                IOUtil.close(bootIn);
                IOUtil.close(bootOrig);
                IOUtil.close(bootPatched);
                IOUtil.close(bootOut);
            }
        }
    }

    @Deprecated
    private void setupGemfile(final GemsInstaller installer,
            final File launchDirectory) {
        final File gemfile = new File(launchDirectory, "Gemfile.maven");
        if (gemfile.exists()) {
            installer.factory.addEnv("BUNDLE_GEMFILE", gemfile);
        }
    }

    public void createNew(final GemsInstaller installer,
            final Object repositorySystemSession,
            final File appPath, String database, String railsVersion,
            ORM orm, final String... args) throws RailsException, GemException,
            IOException, ScriptException {
        createNew(installer, repositorySystemSession, appPath, database, railsVersion, orm,
                  null, null, args);
    }

    public void createNew(final GemsInstaller installer,
                final Object repositorySystemSession,
                final File appPath, String database, String railsVersion,
                final ORM orm, final String template, final GwtOptions gwt,
                final String... args)
        throws RailsException, GemException, IOException, ScriptException {
        final File pomFile = new File(appPath, "pom.xml");
        if (pomFile.exists()) {
            throw new RailsException(pomFile
                    + " exists - skip installation of rails");
        }

        // use a rubygems directory in way that the new application can also use it
        if(installer.config.getGemHome() == null || !installer.config.getGemHome().exists()){
            installer.config.setGemBase(new File(new File(appPath, "target"),
                    "rubygems"));
        }

        if (railsVersion == null) {
            this.logger.info("NOTE: use rails version 3.0.9 to creates a nice platform independent Gemfile." +
                             " change the rails version anytime later");
        }
        List<ArtifactRepository> remotes = new LinkedList<ArtifactRepository>();
        ArtifactRepositoryPolicy enabled = new ArtifactRepositoryPolicy(true, "never", "strict");
        ArtifactRepositoryPolicy disabled = new ArtifactRepositoryPolicy(false, "never", "strict");
        ArtifactRepository repo = this.repositorySystem.createArtifactRepository("rubygems-releases",
                                                                                 DefaultGemManager.DEFAULT_GEMS_REPOSITORY_BASE_URL + "releases",
                                                                                 new DefaultRepositoryLayout(),
                                                                                 enabled, disabled);
        remotes.add(repo);
        //installer.
        railsVersion = installer.installGem("rails",
                                            railsVersion,
                                            repositorySystemSession,
                                            localRepository(), remotes).getVersion();

        // correct spelling
        if (DATABASES.containsKey(database)) {
            database = DATABASES.get(database);
        }

        // run the "rails new"-script
        final Script script = installer.factory.newScript(installer.config.binScriptFile("rails"))
                .addArg("_" + railsVersion + "_")
                .addArg("new");
        if (appPath != null) {
            script.addArg(appPath.getAbsolutePath());
        }
        for (final String arg : args) {
            script.addArg(arg);
        }
        if (database != null) {
            script.addArg("-d", database);
        }
        if (isOffline( repositorySystemSession )) {
            this.logger.info("system is offline: using jruby rails templates from jar file - might be outdated");
        }
        if (template != null || (gwt != null && gwt.packageName != null)){
            String tmp = templateFrom(orm, isOffline( repositorySystemSession ), railsVersion);
            if(tmp != null ){
                System.setProperty("maven.rails.basetemplate", tmp);
            }
            if (template != null){
                System.setProperty("maven.rails.extratemplate", template);
            }
            if (gwt != null && gwt.packageName != null){
                System.setProperty("maven.rails.gwt", gwt.packageName);
            }
            script.addArg("-m", templateFromResource("templates"));
        }
        else {
            script.addArg("-m", templateFrom(orm, isOffline( repositorySystemSession ), railsVersion));
        }

        // skip bundler
        script.addArg("--skip-bundle");

        script.execute();

        if (appPath != null) {
            installer.factory.newScriptFromResource("maven/tools/pom_generator.rb")
                .addArg("rails")
                .addArg("Gemfile")
                .executeIn(appPath, new File(appPath, "pom.xml"));

            // write out a new index.html
            final VelocityContext context = new VelocityContext();
            context.put("railsVersion", railsVersion);
            filterContent(appPath, context, "src/main/webapp/index.html");
            filterContent(appPath,
                          context,
                          "src/main/webapp/index.html",
                          "public/index.html",
                          true);

            setupWebXML(appPath);

            if (gwt!= null && gwt.packageName != null){
                installer.installGem("activerecord-jdbc" + database + "-adapter",
                                     null,// use the latest version
                                     repositorySystemSession,
                                     localRepository(), remotes);
                installer.installGem("resty-generators",
                                     null,// use the latest versiona
                                     repositorySystemSession,
                                     localRepository(), remotes);
                generate(installer,
                         repositorySystemSession,
                         appPath,
                         "resty:setup",
                         gwt.packageName,
                         railsBooleanOption(gwt.session, "session"),
                         railsBooleanOption(gwt.menu, "menu"));
            }
        }
    }
    
    private boolean isOffline( Object repositorySystemSession ){
        try {
            Method m = repositorySystemSession.getClass().getMethod( "isOffline", Boolean.class );
            return (Boolean) m.invoke( repositorySystemSession );
        }
        catch (IllegalAccessException e) {
            throw new RuntimeException( "error in calling isOffline", e );
        }
        catch (IllegalArgumentException e) {
            throw new RuntimeException( "error in calling isOffline", e );
        }
        catch (InvocationTargetException e) {
            throw new RuntimeException( "error in calling isOffline", e );
        }
        catch (NoSuchMethodException e) {
            throw new RuntimeException( "error in calling isOffline", e );
        }
        catch (SecurityException e) { 
            throw new RuntimeException( "error in calling isOffline", e );
        }
    }

    private String railsBooleanOption(final boolean option, final String name) {
        return "--" + (option ? "" : "skip-") + name;
    }

    private String templateFrom(final ORM orm, final boolean offline, String railsVersion) {
        switch(orm){
        case activerecord:
            if (railsVersion.matches("3.0.[0-9]")){
                if (offline) {
                    return templateFromResource(orm.name());
                }
                return "http://jruby.org/rails3.rb";
            }
            return null;
        case datamapper:
            if (railsVersion.startsWith("3.0.") || offline) {
                return templateFromResource(orm.name());
            }
            return "http://datamapper.org/templates/rails.rb";
        default:
            throw new RuntimeException( "unknown ORM :" + orm);
        }
    }

    private String templateFromResource(final String name) {
        return getClass().getResource("/rails-templates/" + name + ".rb").toString()
                      .replaceFirst("^jar:", "");
    }

    private void setupWebXML(final File launchDirectory) throws RailsException,
            IOException {
        // patch the system only when you find a config/application.rb file
        if (!new File(new File(launchDirectory, "config"), "application.rb").exists()) {
            // TODO log this !!
            return;
        }
        final VelocityContext context = new VelocityContext();
        context.put("basedir", launchDirectory.getAbsolutePath());

        // create web.xml unless it exists neither in maven location nor in rails location
        if ( !new File(launchDirectory, "src/main/webapp/WEB-INF/web.xml").exists() ) {
            filterContent(launchDirectory,
                          context,
                          "config/web.xml");
        }

        // create override-xyz-web.xml
        filterContent(launchDirectory,
                      context,
                      "target/jetty/override-development-web.xml");
        filterContent(launchDirectory,
                      context,
                      "target/jetty/override-production-web.xml");

        // create the keystore for SSL
        copyFile(launchDirectory, "src/test/resources/server.keystore");

        // add a monkey patch for throwables
        copyFile(launchDirectory, "config/initializers/java_throwable_monkey_patch.rb");

    }

    private void copyFile(final File launchDirectory, final String name)
            throws IOException {
        final File file = new File(launchDirectory, name);
        if (!file.exists()) {
            FileUtils.copyURLToFile(getClass().getResource("/rails-resources/"
                                            + name), file);
        }
    }

    private void filterContent(final File app, final VelocityContext context,
            final String template) throws RailsException {
        filterContent(app, context, template, template, false);
    }

    private void filterContent(final File app, final VelocityContext context,
            final String template, final String targetName, final boolean force)
            throws RailsException {
        final File templateFile = new File(app, targetName);
        if (!force && templateFile.exists()) {
            return;
        }
        final InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream("rails-resources/"
                + template);

        try {
            if (input == null) {
                throw new FileNotFoundException(template);
            }

            final String templateString = IOUtil.toString(input);

            templateFile.getParentFile().mkdirs();

            final FileWriter fw = new FileWriter(templateFile);

            this.velocityComponent.getEngine().evaluate(context,
                                                        fw,
                                                        "velocity",
                                                        templateString);

            fw.flush();

            fw.close();
        }
        catch (final IOException e) {
            throw new RailsException("failed to filter " + template, e);
        }
    }

    public void rake(final GemsInstaller installer,
            final Object repositorySystemSession,
            final File launchDirectory, final String environment,
            final String task, final String... args) throws IOException,
            ScriptException, GemException, RailsException {
        final Script script = installer.factory.newScriptFromSearchPath("rake");
        script.addArgs(task);
        for (final String arg : args) {
            script.addArg(arg);
        }
        if(environment != null && environment.trim().length() > 0){
            script.addArg("RAILS_ENV=" + environment);
        }
        script.executeIn(launchDirectory);
    }

    public void generate(final GemsInstaller installer,
            final Object repositorySystemSession,
            final File launchDirectory, final String generator,
            final String... args) throws IOException, ScriptException,
            GemException, RailsException {
        final Script script = installer.factory.newScript(new File(new File(launchDirectory,
                "script"),
                "rails"))
                .addArg("generate")
                .addArg(generator);
        for (final String arg : args) {
            script.addArg(arg);
        }
        script.executeIn(launchDirectory);
    }

    public void installGems(final GemsInstaller gemsInstaller,
            final Object repositorySystemSession)
            throws IOException, ScriptException, GemException, RailsException {
        final ArtifactRepository localRepository = localRepository();

        final ProjectBuildingRequest pomRequest = new DefaultProjectBuildingRequest().setLocalRepository(localRepository)
                .setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_STRICT)
                .setResolveDependencies(true);

        gemsInstaller.manager.setRepositorySession(pomRequest, repositorySystemSession);

        MavenProject pom;
        try {

            pom = this.builder.build(new File("pom.xml"), pomRequest)
                    .getProject();

        }
        catch (final ProjectBuildingException e) {
            throw new RailsException("error building the POM", e);
        }

        gemsInstaller.installPom(pom);
    }

    private ArtifactRepository localRepository() throws RailsException {
        ArtifactRepository localRepository;
        try {

            localRepository = this.repositorySystem.createDefaultLocalRepository();

        }
        catch (final InvalidRepositoryException e) {
            throw new RailsException("error creating local repository", e);
        }
        return localRepository;
    }
}
