package com.interpress_project.modernshare.client.controller.delegate;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;
import org.tigris.subversion.svnclientadapter.ISVNDirEntry;
import org.tigris.subversion.svnclientadapter.ISVNNotifyListener;
import org.tigris.subversion.svnclientadapter.SVNClientAdapterFactory;
import org.tigris.subversion.svnclientadapter.SVNClientException;
import org.tigris.subversion.svnclientadapter.SVNNodeKind;
import org.tigris.subversion.svnclientadapter.SVNRevision;
import org.tigris.subversion.svnclientadapter.SVNUrl;
import org.tigris.subversion.svnclientadapter.javahl.JhlClientAdapterFactory;

import com.interpress_project.modernshare.client.controller.delegate.exceptions.AccountNotFoundException;
import com.interpress_project.modernshare.client.controller.delegate.exceptions.ConnectionFailException;
import com.interpress_project.modernshare.client.controller.delegate.exceptions.InternalFailException;
import com.interpress_project.modernshare.client.controller.delegate.exceptions.TagExistException;
import com.interpress_project.modernshare.client.controller.model.PropertyManager;
import com.interpress_project.modernshare.client.controller.model.SVNRoot;
import com.interpress_project.modernshare.client.events.GUIEventSource;
import com.interpress_project.modernshare.client.events.LogEvent;
import com.interpress_project.modernshare.client.events.LogEventSource;
import com.interpress_project.modernshare.client.events.LogEventType;
import com.interpress_project.modernshare.ipcommon.SystemBase;

public class ISVNManager implements ISVNNotifyListener {
	// static variables that are shared with Remote/LocalManagers.
	protected static SystemBase sb = SystemBase.getInstance();
	protected static PropertyManager propmgr = PropertyManager.getInstance();
	protected static LogEventSource logEventSource = LogEventSource.getInstance();
	protected static GUIEventSource guiEventSource = GUIEventSource.getInstance();
	protected static SVNUrl svnAccounturl = null; // SVNUrl, like a "svn://<hostname>:<port>/<account>/".
	protected static String accounturl = null; // url, like a "svn://<hostname>:<port>/<account>/".
	protected static SVNUrl svnHosturl = null; // SVNUrl, like a "svn://<hostname>:<port>/".	
	protected static String hosturl = null; // url, like a "svn://<hostname>:<port>/".

	protected ISVNClientAdapter svnClient = null;
	protected boolean bConnected = false;
	private String[] ignoredfilter = null;
	private String account = null;

	/**
	 * ISVNManager
	 */
	public ISVNManager() {
		try {
			JhlClientAdapterFactory.setup();
		}
		catch (SVNClientException e) {
			// Must be javahl already registered. 
		}
		String bestClientType = null;
		try {
			bestClientType = SVNClientAdapterFactory.getPreferredSVNClientType();
		}
		catch (SVNClientException ex) {
			sb.getLogger().fatal("JhlClientAdapter: アダプタの初期化に失敗しました。アプリケーションを強制終了します。(" + ex.getMessage() + ")", ex);
			System.exit(-1);
		}
		svnClient = SVNClientAdapterFactory.createSVNClient(bestClientType);
		addNotifyListener();

		ignoredfilter = propmgr.getIgnoreFilterList();
	}

	/**
	 * addNotifyListener
	 */
	protected void addNotifyListener() {
		svnClient.addNotifyListener(this);
	}

	/**
	 * removeNotifyListener
	 */
	protected void removeNotifyListener() {
		svnClient.removeNotifyListener(this);
	}

	/**
	 * connect
	 * @throws ConnectionFailException
	 */
	public void connect() throws ConnectionFailException {
		String account = propmgr.getUsername();
		String password = propmgr.getPassword();

		connect(account, password);
	}

	/**
	 * connect
	 * @param account
	 * @param password
	 * @throws ConnectionFailException 
	 */
	public void connect(String account, String password) throws ConnectionFailException {
		sb.getLogger().debug("接続を開始します。(" + account + ")");

		this.account = account;
		svnClient.setUsername(account);
		svnClient.setPassword(password);

		/**
		 * Check if it already connected.
		 */
		if (bConnected) {
			sb.getLogger().debug("接続済みです。(" + account + ")");
			return;
		}

		// Get svnurl, like a "svn://<hostname>:<port>/<account>/".
		hosturl = propmgr.getTargetSVNURL();
		accounturl = propmgr.getTargetSVNURL(account);

		try {
			svnAccounturl = new SVNUrl(accounturl);
			svnHosturl = new SVNUrl(hosturl);
		}
		catch (MalformedURLException ex) {
			bConnected = false;
			throw new ConnectionFailException(accounturl + "に対するアドレス変換が失敗しました。(" + ex.getMessage() + ")", ex);
		}

		// Ping for the host.
		try {
			sb.getLogger().debug(propmgr.getAdminHost() + "へのpingを行います。");
			svnClient.getInfo(svnHosturl);
		}
		catch (SVNClientException ex) {
			bConnected = false;
			throw new ConnectionFailException(
			  "データセンタ" + propmgr.getAdminHost() + "へ接続中にタイムアウトが発生しました。データセンタのホスト情報を確認してください。", ex);
		}
		sb.getLogger().debug("接続が成功しました。(" + account + ")");
		bConnected = true;
	}

	/**
	 * addIgnoredFile
	 * @param file
	 * @throws InternalFailException
	 */
	public void addIgnoredFilter(File file) throws InternalFailException {
		ArrayList<String> list = new ArrayList<String>();
		for (int i = 0; i < ignoredfilter.length; i++) {
			list.add(ignoredfilter[i]);
		}

		try {
			svnClient.setIgnoredPatterns(file, list);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException(file.getAbsolutePath() + "への除外パターン設定に失敗しました。", ex);
		}
	}

	/**
	 * existAccount
	 * @throws InternalFailException
	 */
	public void existAccount() throws AccountNotFoundException {
		try {
			svnClient.getInfo(svnAccounturl);
		}
		catch (SVNClientException ex) {
			throw new AccountNotFoundException("データセンタに、アカウント" + account + "は登録されていません。", ex);
		}
	}

	/**
	 * _getTagNames
	 * @return
	 * @throws InternalFailException
	 */
	private ISVNDirEntry[] _getTagNames() throws InternalFailException {
		ISVNDirEntry[] entries = null;
		try {
			entries = svnClient.getList(svnAccounturl, SVNRevision.HEAD, false);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("アカウント" + account + "に対するタグ名リストの取得に失敗しました。", ex);
		}
		return entries;
	}

	/**
	 * existTagName
	 * @param tagName
	 * @throws InternalFailException
	 * @throws TagExistException 
	 */
	public void existTagName(String tagName) throws InternalFailException, TagExistException {
		ISVNDirEntry[] tagnames = _getTagNames();
		for (int i = 0; i < tagnames.length; i++) {
			String tgname = tagnames[i].getPath();
			if (tgname.equalsIgnoreCase(tagName)) {
				throw new TagExistException("タグ名" + tagName + "は、既にデータセンタに登録されています。");
			}
		}
		return;
	}

	/**
	 * getTagNames
	 * @return
	 * @throws InternalFailException
	 */
	public ArrayList<SVNRoot> getTagNames() throws InternalFailException {
		ArrayList<SVNRoot> list = new ArrayList<SVNRoot>();
		ISVNDirEntry[] tagnames = _getTagNames();
		for (int i = 0; i < tagnames.length; i++) {
			ISVNDirEntry entry = tagnames[i];
			String name = entry.getPath();
			long fsize = entry.getSize();
			String author = entry.getLastCommitAuthor();
			Date lcdate = entry.getLastChangedDate();
			long revision = entry.getLastChangedRevision().getNumber();
			long lcrevision = revision;
			SVNNodeKind kind = entry.getNodeKind();
			if (kind == SVNNodeKind.DIR) {
				list.add(new SVNRoot(name, fsize, author, lcdate, revision, lcrevision, accounturl + "/" + name, ""));
			}
		}
		return list;
	}

	/**
	 * createSVNUrl
	 * @param url
	 * @return
	 * @throws InternalFailException
	 */
	protected SVNUrl createSVNUrl(String url) throws InternalFailException {
		SVNUrl u = null;
		try {
			u = new SVNUrl(url);
		}
		catch (MalformedURLException ex) {
			throw new InternalFailException(url + "に対するアドレス変換が失敗しました。(" + ex.getMessage() + ")", ex);
		}
		return u;
	}

	/**
	 * deleteRemoteFolder
	 * @param tagName
	 * @throws InternalFailException
	 */
	public void deleteRemoteFolder(String tagName) throws InternalFailException {

		SVNUrl u = createSVNUrl(accounturl + "/" + tagName);

		String msg = "Unregister deletion at" + new Date().toString();
		try {
			svnClient.remove(new SVNUrl[] { u }, msg);
		}
		catch (SVNClientException ex) {
			throw new InternalFailException("データセンタにあるフォルダ" + tagName + "の削除に失敗しました。(" + u.toString() + ")", ex);
		}
	}

	/**
	 * commitDirectory
	 * @param path
	 * @throws InternalFailException 
	 */
	public void commitDirectory(File rootpath, File[] pathes) throws InternalFailException {
		try {
			String msg = "Committed at " + new Date().toString();
			/**
			 * commitはrootpathに対して行う。引数渡しをしてみたが削除アイテムを含ませるとエラーが発生する。
			 */
			svnClient.commit(new File[] { rootpath }, msg, true);
			logEventSource.fireEvent(new LogEvent(this, "データセンタへの同期コミットが正常に終了しました。", LogEventType.INFO));
		}
		catch (SVNClientException ex) {
			/**
			 * commitの完了前にファイルが移動もしくは削除された可能性がある。
			 * とりあえず空ファイルを作成することで整合性を保つ。
			 */
			for (int i = 0; i < pathes.length; i++) {
				if (!pathes[i].exists()) {
					try {
						FileUtils.touch(pathes[i]);
					}
					catch (IOException e) {
					}
				}
			}
			throw new InternalFailException("データセンタへの同期コミットに失敗しました。");
		}
	}

	/**
	 * logCommandLine
	 */
	public void logCommandLine(String msg) {
		msg = msg.replace("\r", "");
		msg = msg.replace("\n", " ");
		sb.getLogger().debug("Enter logCommandLine : " + msg);
	}

	/**
	 * logCompleted
	 */
	public void logCompleted(String msg) {
		logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.INFO));
	}

	/**
	 * logError
	 */
	public void logError(String msg) {
		msg = msg.replace("\r", "");
		msg = msg.replace("\n", " ");
		logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.ERROR));
	}

	/**
	 * logMessage 
	 */
	public void logMessage(String msg) {
		logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.INFO));
	}

	/**
	 * logRevision
	 */
	public void logRevision(long arg0, String msg) {
		logEventSource.fireEvent(new LogEvent(this, msg, LogEventType.INFO));
	}

	/**
	 * onNotify
	 */
	public void onNotify(File arg0, SVNNodeKind arg1) {
	}

	/**
	 * setCommand
	 */
	public void setCommand(int arg0) {
	}
}
