package de.saumya.mojo.gems;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.maven.model.Dependency;
import org.apache.maven.model.Developer;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.License;
import org.apache.maven.model.Model;
import org.apache.maven.model.Relocation;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.velocity.VelocityComponent;

import de.saumya.mojo.gems.gem.Gem;
import de.saumya.mojo.gems.gem.GemPackager;
import de.saumya.mojo.gems.spec.GemDependency;
import de.saumya.mojo.gems.spec.GemRequirement;
import de.saumya.mojo.gems.spec.GemSpecification;
import de.saumya.mojo.gems.spec.GemSpecificationIO;
import de.saumya.mojo.gems.spec.GemVersion;
import de.saumya.mojo.ruby.gems.GemManager;

/**
 * This is full of "workarounds" here, since for true artifact2gem conversion I
 * would need interpolated POM!
 *
 * @author cstamas
 * @author mkristian
 */
@Component(role = MavenArtifactConverter.class)
public class DefaultMavenArtifactConverter implements MavenArtifactConverter {
    private static final String LIB_PATH = "lib/";
    private static final String LIB_MAVEN_PATH = LIB_PATH + "maven/";

    enum RubyDependencyType {

        RUNTIME, DEVELOPMENT;

        @Override
        public String toString() {
            return ":" + name().toLowerCase();
        }

        public static RubyDependencyType toRubyDependencyType(
                final String dependencyScope) {
            // ruby scopes
            // :development
            // :runtime
            if ("provided".equals(dependencyScope)
                    || "test".equals(dependencyScope)) {
                return DEVELOPMENT;
            }
            else if ("compile".equals(dependencyScope)
                    || "runtime".equals(dependencyScope)) {
                return RUNTIME;
            }
            else
            // dependencyScope: "system"
            {
                // TODO better throw an exception since there will be no gem for
                // such a dependency or something else
                return RUNTIME;
            }

        }
    }

    /**
     * The Java platform key.
     */
    String                                  PLATFORM_JAVA             = "java";

    @Requirement
    private GemPackager                     gemPackager;

    @Requirement
    private VelocityComponent               velocityComponent;

    @Requirement(hints = { "yaml" })
    private GemSpecificationIO              gemSpecificationIO;

    private final Maven2GemVersionConverter maven2GemVersionConverter = new Maven2GemVersionConverter();

    public boolean canConvert(final MavenArtifact artifact) {
        // TODO: this is where we filter currently what to convert.
        // for now, we convert only POMs with packaging "pom", or "jar" (but we
        // ensure there is the primary artifact JAR
        // also
        // RELAXING: doing "pom" packagings but also anything that has primary
        // artifact ending with ".jar".
        if (canConvert(artifact, "pom", null)) {
            return true;
        }

        if (canConvert(artifact, artifact.getPom().getPackaging(), "jar")) {
            return true;
        }

        return false;
    }

    private boolean canConvert(final MavenArtifact artifact,
            final String packaging, final String extension) {
        String fixedExtension = null;

        if (extension != null) {
            fixedExtension = extension.startsWith(".") ? extension : "."
                    + extension;
        }

        return StringUtils.equals(packaging, artifact.getPom().getPackaging())
                && ((extension == null && artifact.getArtifactFile() == null) || (extension != null
                        && artifact.getArtifactFile() != null && artifact.getArtifactFile()
                        .getName()
                        .endsWith(fixedExtension)));
    }

    public String getGemFileName(final MavenArtifact artifact) {
        return getGemFileName(artifact.getCoordinates().getGroupId(),
                              artifact.getCoordinates().getArtifactId(),
                              artifact.getCoordinates().getVersion(),
                              this.PLATFORM_JAVA);
    }

    public GemSpecification createSpecification(final MavenArtifact artifact) {
        final GemSpecification result = new GemSpecification();

        if( relocateIfNeeded(artifact)){
            Dependency dep = artifact.getPom().getDependencies().get(0);
            result.setPost_install_message("this gem has a new name: "
                                           + createGemName(dep.getGroupId(),
                                                           dep.getArtifactId(),
                                                           dep.getVersion()) + " version: " + dep.getVersion());
        }

        // this is fix
        result.setPlatform(this.PLATFORM_JAVA);

        // the must ones
        result.setName(createGemName(artifact.getCoordinates().getGroupId(),
                                     artifact.getCoordinates().getArtifactId(),
                                     artifact.getCoordinates().getVersion()));
        result.setVersion(new GemVersion(createGemVersion(artifact.getCoordinates()
                .getVersion())));

        // dependencies
        if (artifact.getPom().getDependencies().size() > 0) {
            for (final Dependency dependency : artifact.getPom()
                    .getDependencies()) {
                if (!dependency.isOptional()) {
                    result.getDependencies().add(convertDependency(artifact,
                                                                   dependency));
                }
            }
        }

        // and other stuff "nice to have"
        result.setDate(new Date()); // now
        result.setDescription(sanitizeStringValue(artifact.getPom()
                .getDescription() != null
                ? artifact.getPom().getDescription()
                : artifact.getPom().getName()));
        result.setSummary(sanitizeStringValue(artifact.getPom().getName()));
        result.setHomepage(sanitizeStringValue(artifact.getPom().getUrl()));

        if (artifact.getPom().getLicenses().size() > 0) {
            for (final License license : artifact.getPom().getLicenses()) {
                result.getLicenses().add(sanitizeStringValue(license.getName()
                        + " (" + license.getUrl() + ")"));
            }
        }
        if (artifact.getPom().getDevelopers().size() > 0) {
            for (final Developer developer : artifact.getPom().getDevelopers()) {
                result.getAuthors().add(sanitizeStringValue(developer.getName()
                        + " (" + developer.getEmail() + ")"));
            }
        }

        // by default, we pack into lib/ inside gem (where is the jar and the
        // stub ruby)
        result.getRequire_paths().add("lib");
        return result;
    }

    public GemArtifact createGemStubFromArtifact(final MavenArtifact artifact,
            final File target) throws IOException {
        final GemSpecification gemspec = createSpecification(artifact);

        if (target == null || (target.exists() && !target.isDirectory())) {
            throw new IOException("Must specify target file, where to generate Gem!");
        }

        // write file
        final File gemfile = this.gemPackager.createGemStub(gemspec, target);

        return new GemArtifact(gemspec, gemfile);
    }

    private boolean relocateIfNeeded(final MavenArtifact mart){
        Model model = mart.getPom();
        if (model.getDistributionManagement() != null) {
            final Relocation relocation = model.getDistributionManagement()
                    .getRelocation();
            if (relocation != null) {
                String newGroupId = relocation.getGroupId() == null
                        ? mart.getCoordinates().getGroupId()
                        : relocation.getGroupId();
                String newArtifactId = relocation.getArtifactId() == null
                        ? mart.getCoordinates().getArtifactId()
                        : relocation.getArtifactId();
                String newVersion = relocation.getVersion() == null
                        ? mart.getCoordinates().getVersion()
                        : relocation.getVersion();

                Dependency dep = new Dependency();
                dep.setArtifactId(newArtifactId);
                dep.setGroupId(newGroupId);
                dep.setVersion(newVersion);
                dep.setType(model.getPackaging());
                model.getDependencies().clear();
                model.addDependency(dep);
                model.setPackaging("pom");

                return true;
            }
        }
        return false;
    }

    public GemArtifact createGemFromArtifact(final MavenArtifact artifact,
            final File target) throws IOException {
        final GemSpecification gemspec = createSpecification(artifact);

        if (target == null || (target.exists() && !target.isDirectory())) {
            throw new IOException("Must specify target directory, where to generate Gem!");
        }

        final Gem gem = new Gem(gemspec);

        if (artifact.getArtifactFile() != null) {
            gem.addFile(artifact.getArtifactFile(), createLibFileName(artifact,
                                                                      ".jar"));
        }

        // create "meta" ruby file
        final String rubyStubMetaPath = createLibFileName(artifact,
                                                          "-maven.rb");
        final File rubyStubMetaFile = generateRubyMetaStub(gemspec, artifact);
        gem.addFile(rubyStubMetaFile, rubyStubMetaPath);

        // create runtime ruby file
        final String rubyStubPath = createLibFileName(artifact, ".rb");

        // System.err.println( rubyStubPath );
        final File rubyStubFile = generateRubyStub(gemspec,
                                                   artifact,
                                                   RubyDependencyType.RUNTIME);
        gem.addFile(rubyStubFile, rubyStubPath);

        // create development ruby file
        final String rubyDevelopmentStubPath = createLibFileName(artifact,
                                                                 "-dev.rb");
        final File rubyDevelopmentStubFile = generateRubyStub(gemspec,
                                                              artifact,
                                                              RubyDependencyType.DEVELOPMENT);
        gem.addFile(rubyDevelopmentStubFile, rubyDevelopmentStubPath);

        final File rubyMainStubFile = generateMainStub(artifact);
        gem.addFile(rubyMainStubFile, LIB_PATH + gemspec.getName() + ".rb" );

        // write file
        final File gemfile = this.gemPackager.createGem(gem, target);

        return new GemArtifact(gemspec, gemfile);
    }

    // ==

    protected String sanitizeStringValue(final String val) {
        if (val == null) {
            return null;
        }

        // for now, just to overcome the JRuby 1.4 Yaml parse but revealed by
        // this POM:
        // http://repo1.maven.org/maven2/org/easytesting/fest-assert/1.0/fest-assert-1.0.pom
        return val.replaceAll("'", "").replaceAll("\"", "").replace('\n', ' ');
    }

    protected String createLibFileName(final MavenArtifact artifact,
            final String postfix) {
        return LIB_MAVEN_PATH
                + createRequireName(artifact.getCoordinates().getGroupId(),
                                    artifact.getCoordinates().getArtifactId(),
                                    artifact.getCoordinates().getVersion())
                + postfix;
    }

    protected String createRequireName(final String groupId,
            final String artifactId, final String version) {
        return groupId + "/" + artifactId;
    }

    protected String createJarfileName(final String groupId,
            final String artifactId, final String version) {
        return artifactId + ".jar";
    }

    protected String createGemName(final String groupId,
            final String artifactId, final String version) {
        // TODO: think about this
        // return ( GEMNAME_PREFIX + groupId + GemManager.GROUP_ID_ARTIFACT_ID_SEPARATOR + artifactId ).replace( ':', '.' );
        // TODO think about this harder ;-)
        return ( GEMNAME_PREFIX + groupId + GemManager.GROUP_ID_ARTIFACT_ID_SEPARATOR + artifactId );
    }

    protected String getGemFileName(final String groupId,
            final String artifactId, final String version, final String platform) {
        final String gemName = createGemName(groupId, artifactId, version);

        final String gemVersion = createGemVersion(version);

        return Gem.constructGemFileName(gemName, gemVersion, platform);
    }

    protected String getGemFileName(final GemSpecification gemspec) {
        return Gem.constructGemFileName(gemspec.getName(), gemspec.getVersion()
                .getVersion(), gemspec.getPlatform());
    }

    protected String createGemVersion(final String mavenVersion)
            throws NullPointerException {
        return this.maven2GemVersionConverter.createGemVersion(mavenVersion);
    }

    // ==

    private File generateMainStub(MavenArtifact artifact) throws IOException {
        final VelocityContext context = new VelocityContext();
        context.put("groupId", artifact.getCoordinates().getGroupId());
        context.put("artifactId", artifact.getCoordinates().getArtifactId());

        return generateRubyFile("main", context, "rubyMainStub");
    }

    private File generateRubyMetaStub(final GemSpecification gemspec,
            final MavenArtifact artifact) throws IOException {
        final VelocityContext context = new VelocityContext();
        context.put("gemVersion", gemspec.getVersion().getVersion());
        context.put("groupId", artifact.getCoordinates().getGroupId());
        context.put("artifactId", artifact.getCoordinates().getArtifactId());
        context.put("type", artifact.getPom().getPackaging());
        context.put("version", artifact.getCoordinates().getVersion());
        if (artifact.getArtifactFile() != null) {
            context.put("filename", createJarfileName(artifact.getCoordinates()
                                                              .getGroupId(),
                                                      artifact.getCoordinates()
                                                              .getArtifactId(),
                                                      artifact.getCoordinates()
                                                              .getVersion()));
        }
        final List<String> packageParts = new ArrayList<String>();

        for (final String part : artifact.getCoordinates()
                .getGroupId()
                .split("\\.")) {
            packageParts.add(titleize(part));
        }
        packageParts.add(titleize(artifact.getCoordinates().getArtifactId()));
        context.put("packageParts", packageParts);

        return generateRubyFile("metafile", context, "rubyMetaStub");
    }

    public static class MavenDependency {
        public String       name;
        public List<String> exclusions = new ArrayList<String>();

        public String getName() {
            return this.name;
        }

        public String getExclusions() {
            final StringBuilder buf = new StringBuilder();
            for (final String ex : this.exclusions) {
                buf.append(",'").append(ex).append("'");
            }
            return this.exclusions.size() > 0 ? buf.substring(1) : "";
        }
    }

    private File generateRubyStub(final GemSpecification gemspec,
            final MavenArtifact artifact, final RubyDependencyType type)
            throws IOException {
        final VelocityContext context = new VelocityContext();
        switch (type) {
        case RUNTIME:
            if (artifact.getArtifactFile() != null) {
                context.put("jarfile",
                            createJarfileName(artifact.getCoordinates()
                                    .getGroupId(), artifact.getCoordinates()
                                    .getArtifactId(), artifact.getCoordinates()
                                    .getVersion()));
            }
            break;
        case DEVELOPMENT:
            context.put("filename", artifact.getCoordinates().getArtifactId()
                    + ".rb");
            break;
        }
        final List<MavenDependency> deps = new ArrayList<MavenDependency>();
        for (final Dependency dependency : artifact.getPom().getDependencies()) {
            if (RubyDependencyType.toRubyDependencyType(dependency.getScope()) == type
                    && !dependency.isOptional()) {
                final MavenDependency mavenDependency = new MavenDependency();
                mavenDependency.name = createRequireName(dependency.getGroupId(),
                                                         dependency.getArtifactId(),
                                                         dependency.getVersion());
                for (final Exclusion exclusion : dependency.getExclusions()) {
                    mavenDependency.exclusions.add(exclusion.getGroupId() + "/"
                            + exclusion.getArtifactId());
                }
                deps.add(mavenDependency);
            }

        }
        context.put("dependencies", deps);

        return generateRubyFile("require" + type.name(), context, "rubyStub"
                + type.name());
    }

    private File generateRubyFile(final String templateName,
            final Context context, final String stubFilename)
            throws IOException {
        final InputStream input = getClass().getResourceAsStream("/"
                + templateName + ".rb.vm");

        if (input == null) {
            throw new FileNotFoundException(templateName + ".rb.vm");
        }

        final String rubyTemplate = IOUtil.toString(input);

        final File rubyFile = File.createTempFile(stubFilename, ".rb.tmp");

        final FileWriter fw = new FileWriter(rubyFile);

        this.velocityComponent.getEngine().evaluate(context,
                                                    fw,
                                                    "ruby",
                                                    rubyTemplate);

        fw.flush();

        fw.close();

        return rubyFile;
    }

    private String titleize(final String string) {
        final String[] titleParts = string.split("[-._]");
        final StringBuilder titleizedString = new StringBuilder();
        for (final String part : titleParts) {
            if (part != null && part.length() != 0) {
                titleizedString.append(StringUtils.capitalise(part));
            }
        }
        return titleizedString.toString();
    }

    private GemDependency convertDependency(final MavenArtifact artifact,
            final Dependency dependency) {
        final GemDependency result = new GemDependency();

        result.setName(createGemName(dependency.getGroupId(),
                                     dependency.getArtifactId(),
                                     dependency.getVersion()));

        result.setType(RubyDependencyType.toRubyDependencyType(dependency.getScope())
                .toString());

        final GemRequirement requirement = new GemRequirement();

        // TODO: we are adding "hard" dependencies here, but we should maybe
        // support Maven ranges too
        // based on
        // http://blog.zenspider.com/2008/10/rubygems-howto-preventing-cata.html
        final String version = createGemVersion(getDependencyVersion(artifact,
                                                                     dependency));
        final GemVersion gemVersion;
        if (version.matches("^[^.]+\\.[^.]+\\..*")) {
            // TODO maybe just takethe first two parts
            gemVersion = new GemVersion(version.substring(0, version.indexOf('.')) + ".0.a");
        }
        else {
            gemVersion = new GemVersion(version);
        }

        requirement.addRequirement("~>", gemVersion);

        result.setVersion_requirement(requirement);

        return result;
    }

    private String getDependencyVersion(final MavenArtifact artifact,
            final Dependency dependency) {
        if (dependency.getVersion() != null) {
            return dependency.getVersion();
        }
        else if (StringUtils.equals(artifact.getCoordinates().getGroupId(),
                                    dependency.getGroupId())) {
            // hm, this is same groupId, let's suppose they have same
            // dependency!
            return artifact.getCoordinates().getVersion();
        }
        else {
            // no help here, just interpolated POM
            return "unknown";
        }
    }

    public File createGemspecFromArtifact(final MavenArtifact artifact,
            final File target) throws IOException {
        final GemSpecification gemspec = createSpecification(artifact);
        final File targetFile = new File(target, getGemFileName(gemspec)
                + "spec");

        FileWriter writer = null;
        try {
            writer = new FileWriter(targetFile);
            writer.append(this.gemSpecificationIO.write(gemspec));
        }
        finally {
            IOUtil.close(writer);
        }
        return targetFile;
    }
}
