/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *   http://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.apache.maven.scm.provider.git.jgit.command.changelog;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.maven.scm.ChangeSet;
import org.apache.maven.scm.ScmBranch;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmVersion;
import org.apache.maven.scm.command.changelog.AbstractChangeLogCommand;
import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
import org.apache.maven.scm.command.changelog.ChangeLogSet;
import org.apache.maven.scm.provider.ScmProviderRepository;
import org.apache.maven.scm.provider.git.command.GitCommand;
import org.apache.maven.scm.provider.git.jgit.command.JGitUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;

import static org.apache.maven.scm.provider.git.jgit.command.JGitUtils.getTags;

/**
 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
 * @author Dominik Bartholdi (imod)
 * @since 1.9
 */
public class JGitChangeLogCommand extends AbstractChangeLogCommand implements GitCommand {

    /**
     * {@inheritDoc}
     */
    protected ChangeLogScmResult executeChangeLogCommand(
            ScmProviderRepository repo,
            ScmFileSet fileSet,
            ScmVersion startVersion,
            ScmVersion endVersion,
            String datePattern)
            throws ScmException {
        return executeChangeLogCommand(repo, fileSet, null, null, null, datePattern, startVersion, endVersion);
    }

    @Override
    protected ChangeLogScmResult executeChangeLogCommand(
            ScmProviderRepository repository, ScmFileSet fileSet, ScmVersion version, String datePattern)
            throws ScmException {
        return executeChangeLogCommand(repository, fileSet, null, null, null, datePattern, null, null, version);
    }

    /**
     * {@inheritDoc}
     */
    protected ChangeLogScmResult executeChangeLogCommand(
            ScmProviderRepository repo,
            ScmFileSet fileSet,
            Date startDate,
            Date endDate,
            ScmBranch branch,
            String datePattern)
            throws ScmException {
        return executeChangeLogCommand(repo, fileSet, startDate, endDate, branch, datePattern, null, null);
    }

    protected ChangeLogScmResult executeChangeLogCommand(
            ScmProviderRepository repo,
            ScmFileSet fileSet,
            Date startDate,
            Date endDate,
            ScmBranch branch,
            String datePattern,
            ScmVersion startVersion,
            ScmVersion endVersion)
            throws ScmException {
        return executeChangeLogCommand(
                repo, fileSet, startDate, endDate, branch, datePattern, startVersion, endVersion, null);
    }

    protected ChangeLogScmResult executeChangeLogCommand(
            ScmProviderRepository repo,
            ScmFileSet fileSet,
            Date startDate,
            Date endDate,
            ScmBranch branch,
            String datePattern,
            ScmVersion startVersion,
            ScmVersion endVersion,
            ScmVersion version)
            throws ScmException {
        Git git = null;
        boolean isARangeChangeLog = startVersion != null || endVersion != null;

        try {
            git = JGitUtils.openRepo(fileSet.getBasedir());

            boolean versionOnly = startVersion == null && endVersion == null && version != null;

            String startRev;
            String endRev;

            if (versionOnly) {
                startRev = null;
                endRev = version.getName();
            } else {
                startRev = startVersion != null ? startVersion.getName() : (isARangeChangeLog ? "HEAD" : null);
                endRev = endVersion != null ? endVersion.getName() : (isARangeChangeLog ? "HEAD" : null);
            }

            List<ChangeEntry> gitChanges =
                    this.whatchanged(git.getRepository(), null, startRev, endRev, startDate, endDate, -1);

            List<ChangeSet> modifications = new ArrayList<>(gitChanges.size());

            for (ChangeEntry change : gitChanges) {
                ChangeSet scmChange = new ChangeSet();

                scmChange.setAuthor(change.getAuthorName());
                scmChange.setComment(change.getBody());
                scmChange.setDate(change.getAuthorDate());
                scmChange.setRevision(change.getCommitHash());
                scmChange.setTags(change.getTags());
                // X TODO scmChange.setFiles( change.get )

                modifications.add(scmChange);
            }

            ChangeLogSet changeLogSet = new ChangeLogSet(modifications, startDate, endDate);
            changeLogSet.setStartVersion(startVersion);
            changeLogSet.setEndVersion(endVersion);

            return new ChangeLogScmResult("JGit changelog", changeLogSet);
        } catch (Exception e) {
            throw new ScmException("JGit changelog failure!", e);
        } finally {
            JGitUtils.closeRepo(git);
        }
    }

    public List<ChangeEntry> whatchanged(
            Repository repo, RevSort[] sortings, String fromRev, String toRev, Date fromDate, Date toDate, int maxLines)
            throws MissingObjectException, IncorrectObjectTypeException, IOException {
        List<RevCommit> revs = JGitUtils.getRevCommits(repo, sortings, fromRev, toRev, fromDate, toDate, maxLines);
        List<ChangeEntry> changes = new ArrayList<>(revs.size());

        if (fromRev != null && fromRev.equals(toRev)) {
            // there are no changes between 2 identical versions
            return changes;
        }

        for (RevCommit c : revs) {
            ChangeEntry ce = new ChangeEntry();

            ce.setAuthorDate(c.getAuthorIdent().getWhen());
            ce.setAuthorEmail(c.getAuthorIdent().getEmailAddress());
            ce.setAuthorName(c.getAuthorIdent().getName());
            ce.setCommitterDate(c.getCommitterIdent().getWhen());
            ce.setCommitterEmail(c.getCommitterIdent().getEmailAddress());
            ce.setCommitterName(c.getCommitterIdent().getName());

            ce.setSubject(c.getShortMessage());
            ce.setBody(c.getFullMessage());

            ce.setCommitHash(c.getId().name());
            ce.setTreeHash(c.getTree().getId().name());

            ce.setTags(getTags(repo, c));
            // X TODO missing: file list

            changes.add(ce);
        }

        return changes;
    }

    /**
     *
     */
    public static final class ChangeEntry {
        private String commitHash;

        private String treeHash;

        private String authorName;

        private String authorEmail;

        private Date authorDate;

        private String committerName;

        private String committerEmail;

        private Date committerDate;

        private String subject;

        private String body;

        private List<File> files;

        private List<String> tags;

        public String getCommitHash() {
            return commitHash;
        }

        public void setCommitHash(String commitHash) {
            this.commitHash = commitHash;
        }

        public String getTreeHash() {
            return treeHash;
        }

        public void setTreeHash(String treeHash) {
            this.treeHash = treeHash;
        }

        public String getAuthorName() {
            return authorName;
        }

        public void setAuthorName(String authorName) {
            this.authorName = authorName;
        }

        public String getAuthorEmail() {
            return authorEmail;
        }

        public void setAuthorEmail(String authorEmail) {
            this.authorEmail = authorEmail;
        }

        public Date getAuthorDate() {
            return authorDate;
        }

        public void setAuthorDate(Date authorDate) {
            this.authorDate = authorDate;
        }

        public String getCommitterName() {
            return committerName;
        }

        public void setCommitterName(String committerName) {
            this.committerName = committerName;
        }

        public String getCommitterEmail() {
            return committerEmail;
        }

        public void setCommitterEmail(String committerEmail) {
            this.committerEmail = committerEmail;
        }

        public Date getCommitterDate() {
            return committerDate;
        }

        public void setCommitterDate(Date committerDate) {
            this.committerDate = committerDate;
        }

        public String getSubject() {
            return subject;
        }

        public void setSubject(String subject) {
            this.subject = subject;
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }

        public List<File> getFiles() {
            return files;
        }

        public void setFiles(List<File> files) {
            this.files = files;
        }

        public List<String> getTags() {
            return tags;
        }

        public void setTags(List<String> tags) {
            this.tags = tags;
        }
    }
}
