package net.oni2.svnaccess;

import static java.lang.System.err;

import java.io.File;
import java.util.Vector;

import net.oni2.ProxySettings;

import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.BasicAuthenticationManager;
import org.tmatesoft.svn.core.auth.SVNAuthentication;
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.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

/**
 * SVN handling
 * 
 * @author Christian Illy
 */
public class SVN {

	SVNClientManager svnCManager = null;

	private void setup() {
		// For using over http:// and https://
		DAVRepositoryFactory.setup();
		// For using over svn:// and svn+xxx://
		SVNRepositoryFactoryImpl.setup();
		// For using over file:///
		FSRepositoryFactory.setup();
	}

	private void setProxy(BasicAuthenticationManager authMan) {
		ProxySettings prox = ProxySettings.getInstance();
		if (prox.validate() && prox.isUseProxy()) {
			authMan.setProxy(prox.getHostOrIp(), prox.getPort(), null, null);
		}
	}

	private BasicAuthenticationManager getAuthManager() {
		BasicAuthenticationManager auth = new BasicAuthenticationManager(
				new SVNAuthentication[0]);
		setProxy(auth);
		return auth;
	}

	private BasicAuthenticationManager getAuthManager(String username,
			String password) {
		BasicAuthenticationManager auth = new BasicAuthenticationManager(
				username, password);
		setProxy(auth);
		return auth;
	}

	/**
	 * Constructor
	 */
	public SVN() {
		setup();

		svnCManager = SVNClientManager.newInstance(
				SVNWCUtil.createDefaultOptions(true), getAuthManager());
	}

	/**
	 * Constructor with init values
	 * 
	 * @param username
	 *            Username
	 * @param password
	 *            Password
	 */
	public SVN(String username, String password) {
		setup();

		svnCManager = SVNClientManager.newInstance(
				SVNWCUtil.createDefaultOptions(true),
				getAuthManager(username, password));
	}

	/**
	 * Checkout/update a repository to a local path
	 * 
	 * @param reposUrl
	 *            Repository URL
	 * @param wcDir
	 *            Local path
	 * @param listener
	 *            The listener for the status events
	 * @return True if successful
	 * @throws Exception
	 *             if missing parameters or something went wrong
	 */
	public boolean updateWC(String reposUrl, File wcDir,
			SVNUpdateListener listener) throws Exception {
		SVNURL repos = SVNURL.parseURIEncoded(reposUrl);

		if (wcDir.exists()) {
			int rev = pathIsWCof(repos, wcDir);
			if (rev < 0)
				throw new Exception(
						"Destination path exists but is not a Working Copy of the SVN");
			return update(repos, wcDir, rev, listener);
		} else {
			return checkout(repos, wcDir, listener);
		}
	}

	/**
	 * Checks if the SVN contains newer revisions than the local working copy
	 * 
	 * @param reposUrl
	 *            URL of repository to check for newer revisions
	 * @param wcDir
	 *            Local working copy path to compare against
	 * @return -2: No connection to remote repos<br>
	 *         -1: No local working copy yet<br>
	 *         0: Revisions are equal<br>
	 *         1: SVN contains newer revisions<br>
	 *         2: WC has manually deleted files
	 * @throws Exception
	 *             If destination is not a WC of the given repository
	 */
	public int checkSVN(String reposUrl, File wcDir) throws Exception {
		SVNURL repos = SVNURL.parseURIEncoded(reposUrl);

		if (wcDir.exists()) {
			int localRev = pathIsWCof(repos, wcDir);
			if (localRev < 0) {
				if (wcDir.listFiles().length > 0) {
					throw new Exception(
							"Destination path exists but is not a Working Copy of the SVN");
				} else {
					wcDir.delete();
					return -1;
				}
			}
			int remoteRev = getRemoteHeadRevision(repos);
			if (remoteRev > localRev)
				return 1;
			else {
				if (remoteRev < 0) {
					return -2;
				} else {
					if (getMissingFiles(wcDir))
						return 2;
					else
						return 0;
				}
			}
		} else {
			return -1;
		}
	}

	private boolean getMissingFiles(File wcDir) {
		try {
			final Vector<String> files = new Vector<String>();
			svnCManager.getStatusClient().doStatus(wcDir, null,
					SVNDepth.INFINITY, false, false, false, false,
					new ISVNStatusHandler() {
						@Override
						public void handleStatus(SVNStatus status)
								throws SVNException {
							SVNStatusType stat = status
									.getCombinedNodeAndContentsStatus();
							if (stat == SVNStatusType.MISSING
									|| stat == SVNStatusType.STATUS_MISSING) {
								files.add(status.getFile().getPath());
							}
						}
					}, null);
			return files.size() > 0;
		} catch (SVNException e) {
			e.printStackTrace();
		}
		return false;
	}

	private int getRemoteHeadRevision(SVNURL reposUrl) {
		try {
			SVNInfo info = svnCManager.getWCClient().doInfo(reposUrl,
					SVNRevision.HEAD, SVNRevision.HEAD);
			return (int) info.getRevision().getNumber();
		} catch (SVNException e) {
			e.printStackTrace();
		}
		return -1;
	}

	private int pathIsWCof(SVNURL reposUrl, File wcDir) {
		if (wcDir.exists()) {
			try {
				SVNInfo info = svnCManager.getWCClient().doInfo(wcDir,
						SVNRevision.WORKING);

				if (info.getURL().equals(reposUrl))
					return (int) info.getRevision().getNumber();
			} catch (SVNException e) {
				err.println("Error while getting information of working copy for the location '"
						+ reposUrl + "': " + e.getMessage());
				e.printStackTrace();
			}
		}
		return -1;
	}

	private Vector<String> getUpdatedFilesInRepository(SVNURL reposUrl,
			int fromRev) {
		Vector<String> list = new Vector<String>();
		try {
			svnCManager.getLogClient().doLog(reposUrl,
					new String[] { reposUrl.getPath() }, SVNRevision.HEAD,
					SVNRevision.create(fromRev + 1), SVNRevision.HEAD, true,
					true, 0, new LogEntryHandler(list, reposUrl.getPath()));
		} catch (Exception e) {
			if (!e.getMessage().contains("No such revision ")) {
				err.println("Error while getting the list of updated files of the location '"
						+ reposUrl + "': " + e.getMessage());
				e.printStackTrace();
			}
		}

		return list;
	}

	private boolean update(SVNURL reposUrl, File wcDir, int fromRev,
			SVNUpdateListener listener) throws Exception {
		Vector<String> updatedFiles = getUpdatedFilesInRepository(reposUrl,
				fromRev);

		svnCManager.getUpdateClient().setEventHandler(
				new UpdateEventHandler(updatedFiles, listener));

		try {
			svnCManager.getUpdateClient().doUpdate(wcDir, SVNRevision.HEAD,
					SVNDepth.INFINITY, true, true);
			return true;
		} catch (Exception e) {
			err.println("Error while updating the working copy for the location '"
					+ reposUrl + "': " + e.getMessage());
			e.printStackTrace();
		}
		return false;
	}

	private Vector<String> getFilesInRepository(SVNURL reposUrl)
			throws Exception {
		Vector<String> list = new Vector<String>();
		try {
			svnCManager.getLogClient().doList(reposUrl, SVNRevision.HEAD,
					SVNRevision.HEAD, false, SVNDepth.INFINITY,
					SVNDirEntry.DIRENT_ALL, new DirEntryHandler(list));
		} catch (Exception e) {
			err.println("Error while getting the list of files of the location '"
					+ reposUrl + "': " + e.getMessage());
			e.printStackTrace();
		}
		return list;
	}

	private boolean checkout(SVNURL reposUrl, File wcDir,
			SVNUpdateListener listener) throws Exception {
		Vector<String> newFiles = getFilesInRepository(reposUrl);
		svnCManager.getUpdateClient().setEventHandler(
				new UpdateEventHandler(newFiles, listener));

		boolean result = false;
		try {
			wcDir.mkdirs();
			svnCManager.getUpdateClient()
					.doCheckout(reposUrl, wcDir, SVNRevision.HEAD,
							SVNRevision.HEAD, SVNDepth.INFINITY, true);
			result = true;
		} catch (Exception e) {
			err.println("Error while checking out a working copy for the location '"
					+ reposUrl + "': " + e.getMessage());
			e.printStackTrace();
		}
		return result;
	}
}
