/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed 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 com.gitblit.wicket.pages;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;

import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.utils.DiffUtils;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitLegendPanel;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.LogPanel;

/**
 * The compare page allows you to compare two branches, tags, or hash ids.
 * 
 * @author James Moger
 *
 */
public class ComparePage extends RepositoryPage {

	IModel<String> fromCommitId = new Model<String>("");
	IModel<String> toCommitId = new Model<String>("");

	IModel<String> fromRefId = new Model<String>("");
	IModel<String> toRefId = new Model<String>("");

	public ComparePage(PageParameters params) {
		super(params);
		Repository r = getRepository();
		RepositoryModel repository = getRepositoryModel();
		
		if (StringUtils.isEmpty(objectId)) {
			// seleciton form
			add(new Label("comparison").setVisible(false));
		} else {
			// active comparison
			Fragment comparison = new Fragment("comparison", "comparisonFragment", this);
			add(comparison);
			
			DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
					DiffOutputType.GITBLIT.name()));

			RevCommit fromCommit;
			RevCommit toCommit;
			
			String[] parts = objectId.split("\\.\\.");
			if (parts[0].startsWith("refs/") && parts[1].startsWith("refs/")) {
				// set the ref models
				fromRefId.setObject(parts[0]);
				toRefId.setObject(parts[1]);
				
				fromCommit = getCommit(r, fromRefId.getObject());
				toCommit = getCommit(r, toRefId.getObject());
			} else {
				// set the id models
				fromCommitId.setObject(parts[0]);
				toCommitId.setObject(parts[1]);
				
				fromCommit = getCommit(r, fromCommitId.getObject());
				toCommit = getCommit(r, toCommitId.getObject());
			}

			// prepare submodules
			getSubmodules(toCommit);
			
			final String startId = fromCommit.getId().getName();
			final String endId = toCommit.getId().getName();

			// commit ids
			fromCommitId.setObject(startId);
			toCommitId.setObject(endId);

			String diff = DiffUtils.getDiff(r, fromCommit, toCommit, diffType);

			// compare page links
//			comparison.add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
//					WicketUtils.newRangeParameter(repositoryName, fromCommitId.toString(), toCommitId.getObject())));

			// display list of commits
			comparison.add(new LogPanel("commitList", repositoryName, objectId, r, 0, 0, repository.showRemoteBranches));

			// changed paths list
			List<PathChangeModel> paths = JGitUtils.getFilesInRange(r, fromCommit, toCommit);

			comparison.add(new CommitLegendPanel("commitLegend", paths));
			ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(paths);
			DataView<PathChangeModel> pathsView = new DataView<PathChangeModel>("changedPath", pathsDp) {
				private static final long serialVersionUID = 1L;
				int counter;

				public void populateItem(final Item<PathChangeModel> item) {
					final PathChangeModel entry = item.getModelObject();
					Label changeType = new Label("changeType", "");
					WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
					setChangeTypeTooltip(changeType, entry.changeType);
					item.add(changeType);

					boolean hasSubmodule = false;
					String submodulePath = null;
					if (entry.isTree()) {
						// tree
						item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
								WicketUtils
								.newPathParameter(repositoryName, endId, entry.path)));
					} else if (entry.isSubmodule()) {
						// submodule
						String submoduleId = entry.objectId;
						SubmoduleModel submodule = getSubmodule(entry.path);
						submodulePath = submodule.gitblitPath;
						hasSubmodule = submodule.hasSubmodule;

						// add relative link
						item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
					} else {
						// add relative link
						item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
					}

					// quick links
					if (entry.isSubmodule()) {
						// submodule
						item.add(new ExternalLink("patch", "").setEnabled(false));
						item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
								.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
						item.add(new ExternalLink("blame", "").setEnabled(false));
						item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
								.newPathParameter(repositoryName, endId, entry.path))
								.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
					} else {
						// tree or blob
						item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils
								.newBlobDiffParameter(repositoryName, startId, endId, entry.path))
								.setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
						item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
								.newPathParameter(repositoryName, endId, entry.path))
								.setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
						item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
								.newPathParameter(repositoryName, endId, entry.path))
								.setEnabled(!entry.changeType.equals(ChangeType.ADD)
										&& !entry.changeType.equals(ChangeType.DELETE)));
						item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
								.newPathParameter(repositoryName, endId, entry.path))
								.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
					}
					WicketUtils.setAlternatingBackground(item, counter);
					counter++;
				}
			};
			comparison.add(pathsView);
			comparison.add(new Label("diffText", diff).setEscapeModelStrings(false));
		}

		//
		// ref selection form
		//
		SessionlessForm<Void> refsForm = new SessionlessForm<Void>("compareRefsForm", getClass(), getPageParameters()) {

			private static final long serialVersionUID = 1L;

			@Override
			public void onSubmit() {
				String from = ComparePage.this.fromRefId.getObject();
				String to = ComparePage.this.toRefId.getObject();
				
				PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
				String relativeUrl = urlFor(ComparePage.class, params).toString();
				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
			}
		};
		
		List<String> refs = new ArrayList<String>();
		for (RefModel ref : JGitUtils.getLocalBranches(r, true, -1)) {
			refs.add(ref.getName());
		}
		if (repository.showRemoteBranches) {
			for (RefModel ref : JGitUtils.getRemoteBranches(r, true, -1)) {
				refs.add(ref.getName());
			}
		}
		for (RefModel ref : JGitUtils.getTags(r, true, -1)) {
			refs.add(ref.getName());
		}
		refsForm.add(new DropDownChoice<String>("fromRef", fromRefId, refs).setEnabled(refs.size() > 0));
		refsForm.add(new DropDownChoice<String>("toRef", toRefId, refs).setEnabled(refs.size() > 0));
		add(refsForm);
		
		//
		// manual ids form
		//
		SessionlessForm<Void> idsForm = new SessionlessForm<Void>("compareIdsForm", getClass(), getPageParameters()) {

			private static final long serialVersionUID = 1L;

			@Override
			public void onSubmit() {
				String from = ComparePage.this.fromCommitId.getObject();
				String to = ComparePage.this.toCommitId.getObject();
				
				PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
				String relativeUrl = urlFor(ComparePage.class, params).toString();
				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
			}
		};
		
		TextField<String> fromIdField = new TextField<String>("fromId", fromCommitId);
		WicketUtils.setInputPlaceholder(fromIdField, getString("gb.from") + "...");
		idsForm.add(fromIdField);
		
		TextField<String> toIdField = new TextField<String>("toId", toCommitId);
		WicketUtils.setInputPlaceholder(toIdField, getString("gb.to") + "...");
		idsForm.add(toIdField);
		add(idsForm);
		
		r.close();
	}

	@Override
	protected String getPageName() {
		return getString("gb.compare");
	}
	
	@Override
	protected Class<? extends BasePage> getRepoNavPageClass() {
		return ComparePage.class;
	}

	private RevCommit getCommit(Repository r, String rev)
	{
		RevCommit otherCommit = JGitUtils.getCommit(r, rev);
		if (otherCommit == null) {
			error(MessageFormat.format(getString("gb.failedToFindCommit"), rev, repositoryName, getPageName()), true);
		}
		return otherCommit;
	}
}
