/*
 * Copyright (c) 2013 by syntevo GmbH. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of syntevo GmbH nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.syntevo.bugtraq;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class BugtraqConfig {

	// Constants ==============================================================

	private static final String DOT_GIT_BUGTRAQ = ".gitbugtraq";
	private static final String DOT_TGITCONFIG = ".tgitconfig";

	private static final String BUGTRAQ = "bugtraq";

	private static final String URL = "url";
	private static final String ENABLED = "enabled";
	private static final String LOG_REGEX = "logregex";
	private static final String LOG_FILTERREGEX = "logfilterregex";
	private static final String LOG_LINKREGEX = "loglinkregex";
	private static final String LOG_LINKTEXT = "loglinktext";

	// Static =================================================================

	@Nullable
	public static BugtraqConfig read(@NotNull Repository repository) throws IOException, ConfigInvalidException {
		Config baseConfig = getBaseConfig(repository, DOT_GIT_BUGTRAQ);
		if (baseConfig == null) {
			baseConfig = getBaseConfig(repository, DOT_TGITCONFIG);
		}

		final Set<String> allNames = new HashSet<String>();
		final Config config;
		try {
			config = repository.getConfig();
		}
		catch (RuntimeException ex) {
			final Throwable cause = ex.getCause();
			if (cause instanceof IOException) {
				throw (IOException)cause;
			}
			throw ex;
		}
		if (getString(null, URL, config, baseConfig) != null) {
			allNames.add(null);
		}
		else {
			allNames.addAll(config.getSubsections(BUGTRAQ));
			if (baseConfig != null) {
				allNames.addAll(baseConfig.getSubsections(BUGTRAQ));
			}
		}

		final List<BugtraqEntry> entries = new ArrayList<BugtraqEntry>();
		for (String name : allNames) {
			final String url = getString(name, URL, config, baseConfig);
			if (url == null) {
				continue;
			}

			final String enabled = getString(name, ENABLED, config, baseConfig);
			if (enabled != null && !"true".equals(enabled)) {
				continue;
			}

			String idRegex = getString(name, LOG_REGEX, config, baseConfig);
			if (idRegex == null) {
				return null;
			}

			String filterRegex = getString(name, LOG_FILTERREGEX, config, baseConfig);
			final String linkRegex = getString(name, LOG_LINKREGEX, config, baseConfig);
			if (filterRegex == null && linkRegex == null) {
				final String[] split = idRegex.split("\n", Integer.MAX_VALUE);
				if (split.length == 2) {
					// Compatibility with TortoiseGit
					filterRegex = split[0];
					idRegex = split[1];
				}
				else {
					// Backwards compatibility with specification version < 0.3
					final List<String> logIdRegexs = new ArrayList<String>();
					for (int index = 1; index < Integer.MAX_VALUE; index++) {
						final String logIdRegexN = getString(name, LOG_REGEX + index, config, baseConfig);
						if (logIdRegexN == null) {
							break;
						}

						logIdRegexs.add(logIdRegexN);
					}

					if (logIdRegexs.size() > 1) {
						throw new ConfigInvalidException("More than three " + LOG_REGEX + " entries found. This is not supported anymore since bugtraq version 0.3, use " + LOG_FILTERREGEX + " and " + LOG_LINKREGEX + " instead.");
					}
					else if (logIdRegexs.size() == 1) {
						filterRegex = idRegex;
						idRegex = logIdRegexs.get(0);
					}
				}
			}

			final String linkText = getString(name, LOG_LINKTEXT, config, baseConfig);
			entries.add(new BugtraqEntry(url, idRegex, linkRegex, filterRegex, linkText));
		}

		if (entries.isEmpty()) {
			return null;
		}

		return new BugtraqConfig(entries);
	}

	// Fields =================================================================

	@NotNull
	private final List<BugtraqEntry> entries;

	// Setup ==================================================================

	BugtraqConfig(@NotNull List<BugtraqEntry> entries) {
		this.entries = entries;
	}

	// Accessing ==============================================================

	@NotNull
	public List<BugtraqEntry> getEntries() {
		return Collections.unmodifiableList(entries);
	}

	// Utils ==================================================================

	@Nullable
	private static Config getBaseConfig(@NotNull Repository repository, @NotNull String configFileName) throws IOException, ConfigInvalidException {
		final Config baseConfig;
		if (repository.isBare()) {
			// read bugtraq config directly from the repository
			String content = null;
			RevWalk rw = new RevWalk(repository);
			TreeWalk tw = new TreeWalk(repository);
			tw.setFilter(PathFilterGroup.createFromStrings(configFileName));
			try {
				final Ref ref = repository.getRef(Constants.HEAD);
				if (ref == null) {
					return null;
				}

				ObjectId headId = ref.getTarget().getObjectId();
				if (headId == null || ObjectId.zeroId().equals(headId)) {
					return null;
				}
				RevCommit commit = rw.parseCommit(headId);
				RevTree tree = commit.getTree();
				tw.reset(tree);
				while (tw.next()) {
					ObjectId entid = tw.getObjectId(0);
					FileMode entmode = tw.getFileMode(0);
					if (FileMode.REGULAR_FILE == entmode) {
						ObjectLoader ldr = repository.open(entid, Constants.OBJ_BLOB);
						content = new String(ldr.getCachedBytes(), commit.getEncoding());
						break;
					}
				}
			}
			finally {
				rw.dispose();
				tw.close();
			}

			if (content == null) {
				// config not found
				baseConfig = null;
			}
			else {
				// parse the config
				Config config = new Config();
				config.fromText(content);
				baseConfig = config;
			}
		}
		else {
			// read bugtraq config from work tree
			final File baseFile = new File(repository.getWorkTree(), configFileName);
			if (baseFile.isFile()) {
				FileBasedConfig fileConfig = new FileBasedConfig(baseFile, repository.getFS());
				fileConfig.load();
				baseConfig = fileConfig;
			}
			else {
				baseConfig = null;
			}
		}
		return baseConfig;
	}

	@Nullable
	private static String getString(@Nullable String subsection, @NotNull String key, @NotNull Config config, @Nullable Config baseConfig) {
		final String value = config.getString(BUGTRAQ, subsection, key);
		if (value != null) {
			return trimMaybeNull(value);
		}

		if (baseConfig != null) {
			return trimMaybeNull(baseConfig.getString(BUGTRAQ, subsection, key));
		}

			return value;
		}

	@Nullable
	private static String trimMaybeNull(@Nullable String string) {
		if (string == null) {
			return null;
		}

		string = string.trim();
		if (string.length() == 0) {
			return null;
		}

		return string;
	}
}