package org.jboss.windup.rules.apps.mavenize;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.frames.TypeAwareFramedGraphQuery;
import org.jboss.windup.graph.model.ProjectModel;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.rules.apps.java.config.ScanPackagesOption;
import org.jboss.windup.rules.apps.java.model.JavaClassFileModel;
import org.jboss.windup.rules.apps.java.model.project.MavenProjectModel;
import org.jboss.windup.util.Logging;

import com.thinkaurelius.titan.core.attribute.Text;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.gremlin.java.GremlinPipeline;
import com.tinkerpop.pipes.PipeFunction;
import com.tinkerpop.pipes.branch.CopySplitPipe;
import com.tinkerpop.pipes.filter.PropertyFilterPipe;
import com.tinkerpop.pipes.sideeffect.AggregatePipe;
import com.tinkerpop.pipes.sideeffect.GroupCountPipe;
import com.tinkerpop.pipes.transform.OutPipe;
import com.tinkerpop.pipes.transform.PropertyPipe;

/**
 * Contains methods for assistance in analyzing modules
 *
 * @author <a href="http://ondra.zizka.cz/">Ondrej Zizka, ozizka at seznam.cz</a>
 */
public class ModuleAnalysisHelper
{
    public static final String LAST_RESORT_DEFAULT_GROUP_ID = "com.mycompany.mavenized";
    private static final Logger LOG = Logging.get(ModuleAnalysisHelper.class);

    protected GraphContext graphContext;


    public ModuleAnalysisHelper(GraphContext context)
    {
        this.graphContext = context;
    }

    String deriveGroupId(ProjectModel projectModel)
    {
        if (projectModel instanceof MavenProjectModel)
        {
            MavenProjectModel mavenProject = (MavenProjectModel) projectModel;
            mavenProject.getGroupId();
        }

        // User can set this via --mavenizeGroupId.
        {
            String groupId = (String) graphContext.getOptionMap().get(MavenizeGroupIdOption.NAME);
            if (groupId != null)
            {
                if (groupId.matches(MavenizeGroupIdOption.REGEX_GROUP_ID))
                    return groupId;
                LOG.severe(MavenizeGroupIdOption.NAME + " doesn't match the groupId pattern, ignoring: " + groupId);
            }
        }

        // Try to take what the user put to --packages.
        {
            List<String> scanPackages = (List<String>) graphContext.getOptionMap().get(ScanPackagesOption.NAME);
            if (scanPackages != null && !scanPackages.isEmpty() && scanPackages.get(0).contains("."))
                return scanPackages.get(0);
        }

        // FIXME TODO - If this is unused, it should be removed.
        // Get shared prefix of all packages in this application. Can result to "".
        //String fromPackages = new ModuleAnalysisHelper(graphContext).deriveGroupIdFromPackages(projectModel);
        //if (fromPackages != null)
            //return fromPackages;

        return LAST_RESORT_DEFAULT_GROUP_ID;
    }

    /**
     * Counts the packages prefixes appearing in this project and if some of them make more than half of the total of existing packages, this prefix
     * is returned. Otherwise, returns null.
     *
     * This is just a helper, it isn't something really hard-setting the package. It's something to use if the user didn't specify using
     * --mavenize.groupId, and the archive or project name is something insane, like few sencences paragraph (a description) or a number or such.
     */
    String deriveGroupIdFromPackages(ProjectModel projectModel)
    {
        Map<String, Integer> pkgsMap = new HashMap<>();
        Set<String> pkgs = new HashSet<>(1000);
        GremlinPipeline<Vertex, Vertex> pipeline = new GremlinPipeline<>(projectModel);

        PipeFunction<String, String> upToThirdDot = new PipeFunction<String, String>()
        {
            @Override
            public String compute(String pkgName)
            {
                int upToThirdDot = StringUtils.ordinalIndexOf(pkgName, ".", 3);
                return StringUtils.substring(pkgName, 0, upToThirdDot);
            }
        };
        PipeFunction<String, String> identity = new PipeFunction<String, String>()
        {
            @Override
            public String compute(String pkgName)
            {
                return pkgName;
            }
        };

        pipeline
                    .add(new OutPipe(ProjectModel.PROJECT_MODEL_TO_FILE))
                    .add(new TypePipe(JavaClassFileModel.class))
                    // .add(new GroupByPipe(identity, identity)
                    .add(new PropertyPipe(JavaClassFileModel.PROPERTY_PACKAGE_NAME))
                    .add(new CopySplitPipe(
                                new AggregatePipe(pkgs, upToThirdDot),
                                new GroupCountPipe(pkgsMap)))
        // .add(new AggregatePipe(pkgs, upToThirdDot))
        ;

        Map.Entry<String, Integer> biggest = null;
        for (Map.Entry<String, Integer> entry : pkgsMap.entrySet())
        {
            if (biggest == null || biggest.getValue() < entry.getValue())
                biggest = entry;
        }
        // More than a half is of this package.
        if (biggest != null && biggest.getValue() > pkgsMap.size() / 2)
            return biggest.getKey();

        return null;
    }

    public class TypePipe extends PropertyFilterPipe<Vertex, Vertex>
    {
        public TypePipe(Class<? extends WindupVertexFrame> clazz)
        {
            super(WindupVertexFrame.TYPE_PROP, Text.CONTAINS, TypeAwareFramedGraphQuery.getTypeValue(clazz));
        }
    }

}
