package org.jboss.windup.rules.apps.java.archives.listener;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.logging.Logger;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.addon.dependencies.Coordinate;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.ArchiveModel;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.ArchiveService;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.graph.service.WindupConfigurationService;
import org.jboss.windup.rules.apps.java.archives.identify.ArchiveIdentificationService;
import org.jboss.windup.rules.apps.java.archives.model.ArchiveCoordinateModel;
import org.jboss.windup.rules.apps.java.archives.model.IdentifiedArchiveModel;
import org.jboss.windup.rules.apps.java.archives.model.IgnoredArchiveModel;
import org.jboss.windup.util.exception.WindupException;

import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.wrappers.event.listener.GraphChangedListener;
import org.jboss.windup.util.Logging;

/**
 * {@link GraphChangedListener} responsible for identifying {@link ArchiveModel} instances when they are added to the graph.
 *
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 * @author <a href="mailto:ozizka@redhat.com">Ondrej Zizka</a>
 */
public final class ArchiveIdentificationGraphChangedListener implements GraphChangedListener
{
    private static final Logger LOG = Logging.get(ArchiveIdentificationGraphChangedListener.class);

    private final ArchiveIdentificationService identifier;
    private GraphContext context;
    private ArchiveService archiveService;

    public ArchiveIdentificationGraphChangedListener setGraphContext(GraphContext context)
    {
        this.context = context;
        this.archiveService = new ArchiveService(context);
        return this;
    }

    public ArchiveIdentificationGraphChangedListener(GraphContext context, ArchiveIdentificationService identifier)
    {
        this.identifier = identifier;
        this.setGraphContext(context);
    }

    @Override
    public void vertexPropertyChanged(Vertex vertex, String key, Object oldValue, Object setValue)
    {
        if (ArchiveModel.ARCHIVE_NAME.equals(key))
        {
            ArchiveModel archive = archiveService.frame(vertex);

            setArchiveHashes(archive);

            Coordinate coordinate = identifier.getCoordinate(archive.getSHA1Hash());
            if (coordinate != null)
            {
                // If this is not a jar file, do not ignore it
                if (!StringUtils.endsWithIgnoreCase(archive.getFileName(), ".jar"))
                    return;

                // Never ignore the input application
                WindupConfigurationModel configurationModel = WindupConfigurationService.getConfigurationModel(this.context);
                for (FileModel inputPath : configurationModel.getInputPaths())
                {
                    if (inputPath.equals(archive))
                        return;
                }

                IdentifiedArchiveModel identifiedArchive = GraphService.addTypeToModel(context, archive, IdentifiedArchiveModel.class);
                ArchiveCoordinateModel coordinateModel = new GraphService<>(context, ArchiveCoordinateModel.class).create();

                coordinateModel.setArtifactId(coordinate.getArtifactId());
                coordinateModel.setGroupId(coordinate.getGroupId());
                coordinateModel.setVersion(coordinate.getVersion());
                coordinateModel.setClassifier(coordinate.getClassifier());
                identifiedArchive.setCoordinate(coordinateModel);

                LOG.info("Identified archive: [" + archive.getFilePath() + "] as [" + coordinate + "] will not be unzipped or analyzed.");
                IgnoredArchiveModel ignoredArchive = GraphService.addTypeToModel(context, archive, IgnoredArchiveModel.class);
                ignoredArchive.setIgnoredRegex("Known open-source library");
            }
            else
            {
                LOG.info("Archive not identified: " + archive.getFilePath() + " SHA1: " + archive.getSHA1Hash());
            }
        }
    }

    private void setArchiveHashes(ArchiveModel payload)
    {
        if (payload.getMD5Hash() == null)
        {
            try (InputStream is = payload.asInputStream())
            {
                String md5 = DigestUtils.md5Hex(is);
                payload.setMD5Hash(md5);
            }
            catch (IOException e)
            {
                throw new WindupException("Failed to read archive file at: " + payload.getFilePath() + " due to: " + e.getMessage(), e);
            }
        }

        if (payload.getSHA1Hash() == null)
        {
            try (InputStream is = payload.asInputStream())
            {
                String sha1 = DigestUtils.sha1Hex(is);
                payload.setSHA1Hash(sha1);
            }
            catch (IOException e)
            {
                throw new WindupException("Failed to read archive file at: " + payload.getFilePath() + " due to: " + e.getMessage(), e);
            }
        }
    }

    @Override
    public void vertexPropertyRemoved(Vertex vertex, String key, Object removedValue)
    {
    }

    @Override
    public void vertexAdded(Vertex vertex)
    {
    }

    @Override
    public void vertexRemoved(Vertex vertex, Map<String, Object> props)
    {
    }

    @Override
    public void edgeAdded(Edge edge)
    {
    }

    @Override
    public void edgePropertyChanged(Edge edge, String key, Object oldValue, Object setValue)
    {
    }

    @Override
    public void edgePropertyRemoved(Edge edge, String key, Object removedValue)
    {
    }

    @Override
    public void edgeRemoved(Edge edge, Map<String, Object> props)
    {
    }

}
