/*
 * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. 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 Flamingo Kirill Grouchnikov 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 org.pushingpixels.flamingo.api.bcb.core;

import java.io.*;
import java.util.*;

import org.pushingpixels.flamingo.api.bcb.*;
import org.pushingpixels.flamingo.api.common.StringValuePair;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

/**
 * Breadcrumb bar that allows browsing a single local / remote SVN repository.
 * The implementation uses <a href="http://www.svnkit.com">SVNKit</a> library.
 * Make sure to read the <a
 * href="http://www.svnkit.com/licensing/index.html">licensing terms</a> before
 * using this component in your application.
 * 
 * @author Kirill Grouchnikov
 */
public class BreadcrumbSvnSelector extends JBreadcrumbBar<String> {
	/**
	 * SVN-specific implementation of the {@link BreadcrumbBarCallBack}.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static class PathCallback extends BreadcrumbBarCallBack<String> {
		/**
		 * Repository URL.
		 */
		private String url;

		/**
		 * User name.
		 */
		private String userName;

		/**
		 * Password.
		 */
		private String password;

		/**
		 * The actual SVN repository.
		 */
		private SVNRepository repository;

		/**
		 * Creates a new callback.
		 * 
		 * @param url
		 *            Repository URL.
		 * @param userName
		 *            User name.
		 * @param password
		 *            Password.
		 */
		public PathCallback(String url, String userName, String password) {
			this.url = url;
			this.userName = userName;
			this.password = password;

		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#setup()
		 */
		@Override
		public void setup() throws BreadcrumbBarException {
			DAVRepositoryFactory.setup();
			SVNRepositoryFactoryImpl.setup();
			FSRepositoryFactory.setup();

			try {
				this.repository = SVNRepositoryFactory.create(SVNURL
						.parseURIEncoded(this.url));
				ISVNAuthenticationManager authManager = SVNWCUtil
						.createDefaultAuthenticationManager(this.userName,
								this.password);
				this.repository.setAuthenticationManager(authManager);
			} catch (SVNException svne) {
				if (this.throwsExceptions) {
					throw new BreadcrumbBarException(svne);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getPathChoices(java.
		 * util.List)
		 */
		@Override
		public List<StringValuePair<String>> getPathChoices(
				List<BreadcrumbItem<String>> path)
				throws BreadcrumbBarException {
			String lastPath = (path == null) ? "" : path.get(path.size() - 1)
					.getData();
			try {
				return BreadcrumbSvnSelector.getPathChoices(this.repository,
						lastPath);
			} catch (SVNException svne) {
				if (this.throwsExceptions) {
					throw new BreadcrumbBarException(svne);
				}
				return null;
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafs(java.util.List)
		 */
		@Override
		public List<StringValuePair<String>> getLeafs(
				List<BreadcrumbItem<String>> path)
				throws BreadcrumbBarException {
			String lastPath = (path == null) ? "" : path.get(path.size() - 1)
					.getData();
			try {
				return BreadcrumbSvnSelector
						.getLeafs(this.repository, lastPath);
			} catch (SVNException svne) {
				svne.printStackTrace();
				return null;
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafContent(java.
		 * lang.Object)
		 */
		@Override
		public InputStream getLeafContent(String leaf)
				throws BreadcrumbBarException {
			try {
				return BreadcrumbSvnSelector.getLeafContent(this.repository,
						leaf);
			} catch (SVNException svne) {
				return null;
			}
		}

	}

	/**
	 * Creates a new empty breadcrumb bar that is not connected to any SVN
	 * repository.
	 */
	public BreadcrumbSvnSelector() {
		super(null);
	}

	/**
	 * Creates a new breadcrumb bar to browse the specified SVN repository. The
	 * actual connection is done off EDT so that the application remains
	 * responsive.
	 * 
	 * @param url
	 *            Repository URL.
	 * @param userName
	 *            User name.
	 * @param password
	 *            Password.
	 */
	public BreadcrumbSvnSelector(String url, String userName, String password) {
		super(new PathCallback(url, userName, password));
	}

	/**
	 * Sets the connection parameters. The breadcrumb bar path is reset to the
	 * root of thew specified SVN repository.
	 * 
	 * @param url
	 *            Repository URL.
	 * @param userName
	 *            User name.
	 * @param password
	 *            Password.
	 */
	public void setConnectionParams(String url, String userName, String password) {
		this.setConnectionParams(url, userName, password, false);
	}

	/**
	 * Sets the connection parameters. The breadcrumb bar path is reset to the
	 * root of thew specified SVN repository.
	 * 
	 * @param url
	 *            Repository URL.
	 * @param userName
	 *            User name.
	 * @param password
	 *            Password.
	 * @param throwsException
	 *            Indicates whether the inner exceptions should be reported to
	 *            the registered exception handlers.
	 */
	public void setConnectionParams(String url, String userName,
			String password, boolean throwsException) {
		this.callback = new PathCallback(url, userName, password);
		this.callback.setup();
		this.callback.setThrowsExceptions(throwsException);
		this.setPath(new LinkedList<BreadcrumbItem<String>>());
	}

	/**
	 * Returns the contents of the specified repository file.
	 * 
	 * @param repository
	 *            SVN repository.
	 * @param leaf
	 *            Full path to the file.
	 * @return Contents of the specified repository file.
	 * @throws SVNException
	 */
	@SuppressWarnings("unchecked")
	protected static InputStream getLeafContent(SVNRepository repository,
			String leaf) throws SVNException {
		Map fileProperties = new HashMap();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		repository.getFile(leaf, -1, SVNProperties.wrap(fileProperties), baos);
		return new ByteArrayInputStream(baos.toByteArray());
	}

	/**
	 * Returns a list of all the files in the specified folder.
	 * 
	 * @param repository
	 *            SVN repository.
	 * @param path
	 *            Full path to the folder.
	 * @return List of all the files in the specified folder.
	 * @throws SVNException
	 */
	@SuppressWarnings("unchecked")
	protected static List<StringValuePair<String>> getLeafs(
			SVNRepository repository, String path) throws SVNException {
		List<StringValuePair<String>> result = new ArrayList<StringValuePair<String>>();
		Collection entries = repository.getDir(path, -1, null,
				(Collection) null);
		Iterator iterator = entries.iterator();
		while (iterator.hasNext()) {
			SVNDirEntry entry = (SVNDirEntry) iterator.next();
			if (entry.getKind() == SVNNodeKind.FILE) {
				String prefix = ((path == null) || (path.length() == 0)) ? ""
						: path + "/";
				StringValuePair<String> leafInfo = new StringValuePair<String>(
						entry.getName(), prefix + entry.getName());
				leafInfo.set("size", entry.getSize());
				leafInfo.set("author", entry.getAuthor());
				leafInfo.set("date", entry.getDate());
				leafInfo.set("revision", entry.getRevision());
				result.add(leafInfo);
			}
		}
		Collections.sort(result, new Comparator<StringValuePair<String>>() {
			public int compare(StringValuePair<String> o1,
					StringValuePair<String> o2) {
				return o1.getKey().compareTo(o2.getKey());
			}
		});
		return result;
	}

	/**
	 * Returns a list of all the foldes in the specified folder.
	 * 
	 * @param repository
	 *            SVN repository.
	 * @param path
	 *            Full path to the folder.
	 * @return List of all the folders in the specified folder.
	 * @throws SVNException
	 */
	@SuppressWarnings("unchecked")
	protected static List<StringValuePair<String>> getPathChoices(
			SVNRepository repository, String path) throws SVNException {
		List<StringValuePair<String>> result = new ArrayList<StringValuePair<String>>();
		Collection entries = repository.getDir(path, -1, null,
				(Collection) null);
		Iterator iterator = entries.iterator();
		while (iterator.hasNext()) {
			SVNDirEntry entry = (SVNDirEntry) iterator.next();
			if (entry.getKind() == SVNNodeKind.DIR) {
				String prefix = ((path == null) || (path.length() == 0)) ? ""
						: path + "/";
				result.add(new StringValuePair<String>(entry.getName(), prefix
						+ entry.getName()));
			}
		}
		Collections.sort(result, new Comparator<StringValuePair<String>>() {
			public int compare(StringValuePair<String> o1,
					StringValuePair<String> o2) {
				return o1.getKey().compareTo(o2.getKey());
			}
		});
		return result;
	}
}
