using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Threading;

using Perforce.P4;

namespace Perforce.sln.bld.gui
{
	public partial class FormMain : Form
	{
		int lastChange = 0;
		internal Repository rep = null;

		private void checkConnect()
		{
			if (rep != null)
			{
				//release any old connection
				rep.Dispose();
			}

			String conStr = mServerConnection.Text;
			String user = mUserText.Text;
			String password = mPaswordTxt.Text;
			try
			{
				Server server = new Server(new ServerAddress(conStr));

				rep = new Repository(server);

				rep.Connection.UserName = user;
				Options options = new Options();
				options["Password"] = password;

				rep.Connection.Client = new Client();

				rep.Connection.Connect(options);

			}
			catch (Exception ex)
			{
				rep = null;
				MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}




		public FormMain()
		{
			InitializeComponent();
		}

		private void mShowPasswordChk_CheckedChanged(object sender, EventArgs e)
		{
			mPaswordTxt.UseSystemPasswordChar = !mShowPasswordChk.Checked;
		}

		Thread MonitorChangesThread = null;

		private void mBrowseDepotBtn_Click(object sender, EventArgs e)
		{
			checkConnect();
			
				DepotPathDlg dlg = new DepotPathDlg(rep);
				if (dlg.ShowDialog() == DialogResult.OK)
				{
					mSolutionPath.Text = dlg.SelectedFile;

					int lastChange = GetLastChange();
					mLastChangeLbl.Text = lastChange.ToString();
					changeAtLastBuild = lastChange;

					// start the monitor
					MonitorChangesThread = new Thread(new ThreadStart(MonitorThreadProc));
					MonitorChangesThread.IsBackground = true;
					MonitorChangesThread.Start();
				}
			
		}

		private void mSelectBuildDir_Click(object sender, EventArgs e)
		{
			folderBrowseDlg.SelectedPath = mBuildFolderTxt.Text;
			if (folderBrowseDlg.ShowDialog() != DialogResult.Cancel)
			{
				mBuildFolderTxt.Text = folderBrowseDlg.SelectedPath;
			}
		}

		private void OnInfoResults(int level, String data)
		{
			String spaces = String.Empty;
			for (int idx = 0; idx < level; idx++)
			{
				spaces += ".";
			}
			AsynchAddLineToLog(String.Format("{0}{1}", spaces, data));
		}

		StreamWriter log;

		int changeAtLastBuild = 0;

		private void mBuildNowBtn_Click(object sender, EventArgs e)
		{
			RunBuild(true);
		}

		private void RunBuild(bool async)
		{
			DateTime buildTime = DateTime.Now;
			String buildId = buildTime.ToString("MMddyyHHmmss");
			String buildFolder = buildTime.ToString("MM-dd-yy_HHmmss");
			String buildPath = Path.Combine(mBuildFolderTxt.Text, buildFolder);

			int idx = 0;
			while ((Directory.Exists(buildPath)) && (idx < 26))
			{
				buildPath = Path.Combine(mBuildFolderTxt.Text, buildTime.ToString("MM-dd-yy HHmmss") + ((char)((int)'a' + idx)));
			}
			if (idx >= 26)
				return;

			string logFile = Path.Combine(buildPath, "BuildLog.txt");

			Directory.CreateDirectory(buildPath);

			String ConStr = mServerConnection.Text;
			String User = mUserText.Text;
			String Password = mPaswordTxt.Text;
			String Target = mSolutionPath.Text;
			Server pServer = new Server(new ServerAddress(ConStr));
			rep = new Repository(pServer);

			

			Connection con = rep.Connection;

			con.Connect(null);
			log = new StreamWriter(logFile);

			Client buildClient = new Client();

			buildClient.Name = "p4apinet_solution_builder_sample_application_client";
			buildClient.OwnerName = con.UserName;
			buildClient.ViewMap = new ViewMap();
			buildClient.Root = buildPath;
			buildClient.Options = ClientOption.AllWrite;
			buildClient.LineEnd = LineEnd.Local;
			buildClient.SubmitOptions = new ClientSubmitOptions(false, SubmitType.SubmitUnchanged);

			string depotPath = mSolutionPath.Text;

			IList<FileMetaData> fmd = null;

			try
			{
				fmd = rep.GetFileMetaData(null, FileSpec.DepotSpec(depotPath));
			}

			catch{}

			if (( fmd == null) || (fmd.Count !=1))
			{
				string message = string.Format("The solution file \"{0}\" does not exist in the depot.", depotPath);
				MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return;
			}


			if (mSolutionPath.Text.EndsWith(".sln"))
			{
				depotPath = mSolutionPath.Text.Substring(0, mSolutionPath.Text.LastIndexOf('/'));
			}
				string depotFolder = depotPath.Substring(depotPath.LastIndexOf('/') + 1);

				depotPath += "/...";

				String clientPath = String.Format("//{0}/{1}/...", buildClient.Name, depotFolder);

				MapEntry entry = new MapEntry(MapType.Include, new DepotPath(depotPath), new ClientPath(clientPath));

				buildClient.ViewMap.Add(entry);

				rep.CreateClient(buildClient);
				con.Client = rep.GetClient(buildClient.Name);

				string localPath = clientPath;
				localPath = localPath.TrimStart('/');
				localPath = localPath.TrimEnd('.');
				localPath = localPath.Substring(localPath.IndexOf('/') + 1);
				localPath = localPath.Replace('/', '\\');
				string solutionName = Target.Substring(depotPath.LastIndexOf('/'));
				solutionName = solutionName.TrimStart('/');
				localPath = Path.Combine(buildPath, localPath, solutionName);


				int lastChange = GetLastChange();
				AsynchSetLastChange(lastChange);

				IList<Changelist> changes = GetChangesAfter(changeAtLastBuild, lastChange);

				 changeAtLastBuild = lastChange;

				if (changes != null)
				{
					for (idx = 0; idx < changes.Count; idx++)
					{
						AsynchAddLineToLog(changes[idx].ToString(true));
					}
				}


				if (async)
				{
					Thread buildThread = new Thread(new ParameterizedThreadStart(RunBuildProc));
					buildThread.IsBackground = true;
					buildThread.Start(localPath);
				}
				else
				{
					RunBuildProc(localPath);
				}

				con.Disconnect(null);
		}

		private void mSelectMSBuildLoactionBtn_Click(object sender, EventArgs e)
		{
			fileBrowseDlg.FileName = mMsBuildPathTxt.Text;
			if (fileBrowseDlg.ShowDialog() != DialogResult.Cancel)
			{
				mMsBuildPathTxt.Text = fileBrowseDlg.FileName;
			}
		}

		private int GetLastChange()
		{
			string depotPath = mSolutionPath.Text;
			if (mSolutionPath.Text.EndsWith(".sln"))
			{
				depotPath = mSolutionPath.Text.Substring(0, mSolutionPath.Text.LastIndexOf('/'));
			}

			String ConStr = mServerConnection.Text;
			String User = mUserText.Text;
			String Password = mPaswordTxt.Text;
			String Target = mSolutionPath.Text;
			Server pServer = new Server(new ServerAddress(ConStr));
			rep = new Repository(pServer);
			Connection con = rep.Connection;
			con.Connect(null);

			Options opts = new Options();
			opts["-m"]="1";
			IList<Changelist> changes = rep.GetChangelists(opts, null);

			if (changes != null)
				return (int) changes[0].Id;

			return -1;
		}

		private IList<Changelist> GetChangesAfter(int previous, int current)
		{
			if (current <= previous)
				return null;

			string depotPath = mSolutionPath.Text;
			if (mSolutionPath.Text.EndsWith(".sln"))
			{
				depotPath = mSolutionPath.Text.Substring(0, mSolutionPath.Text.LastIndexOf('/'));
			}

			FileSpec fs = new DepotPath(depotPath);
			Options opts = new Options();
			opts["-m"] = (current - previous).ToString();
			IList<Changelist> changes = rep.GetChangelists(opts, fs);

			return changes;
		}

		Process buildProc = null;

		bool buildFailed = false;

		private void RunBuildProc(object oSolutionFilePath)
		{
			checkConnect();
			
			Connection con = rep.Connection;

			Client buildClient = new Client();
			buildClient.Name = "p4apinet_solution_builder_sample_application_client";
			con.Client = rep.GetClient(buildClient.Name);

			Options sFlags = new Options((SyncFilesCmdFlags.Force|SyncFilesCmdFlags.PopulateClient), -1);

			IList<FileSpec> rFiles = con.Client.SyncFiles(sFlags, null);

			Options opts = new Options();
			opts["-f"] = null;
			rep.DeleteClient(buildClient,opts);
			
			buildFailed = false;

			String SolutionFilePath = (String)oSolutionFilePath;

			String MSBuildPath = (String)mMsBuildPathTxt.Text;

			buildProc = new Process();

			string quotedPath = string.Format("\"{0}\"", SolutionFilePath);

			ProcessStartInfo si = new ProcessStartInfo(MSBuildPath, quotedPath);
			si.UseShellExecute = false;
			si.RedirectStandardOutput = true;
			si.RedirectStandardError = true;
			si.WorkingDirectory = Path.GetDirectoryName(SolutionFilePath);
			si.CreateNoWindow = false;
			buildProc.StartInfo = si;

			Thread stdoutThread = new Thread(new ThreadStart(ReadStandardOutThreadProc));
			stdoutThread.IsBackground = true;

			Thread stderrThread = new Thread(new ThreadStart(ReadStandardErrorThreadProc));
			stderrThread.IsBackground = true;

			buildProc.Start();
			stdoutThread.Start();
			stderrThread.Start();

			buildProc.WaitForExit();

			if (stdoutThread.IsAlive)
				stdoutThread.Abort();

			if (stderrThread.IsAlive)
				stderrThread.Abort();

			buildProc = null;

			log.Dispose();
			log = null;

			AsynchSetLastChange(buildFailed);

			return;
		}

		private delegate void AddLineDelegate(String Line);

		public void AddLineToLog(string line)
		{
			if (mLogText.TextLength > 0)
				mLogText.Text += "\r\n";

			mLogText.Text += line;

			mLogText.Select(mLogText.Text.Length, 0);
			mLogText.ScrollToCaret(); 
		}

		private void AsynchAddLineToLog( String line)
		{
			if (AddLine == null)
				AddLine = new AddLineDelegate(AddLineToLog);

			if (log != null)
			{
				log.WriteLine(line);
			}

			mLogText.BeginInvoke(AddLine, line);
		}

		AddLineDelegate AddLine = null;

		private delegate void SetLastChangeDelegate(int Line);

		public void SetLastChange(int change )
		{
			mLastChangeLbl.Text = change.ToString();
		}

		SetLastChangeDelegate SetLast = null;

		private void AsynchSetLastChange( int change )
		{
			if (SetLast == null)
				SetLast = new SetLastChangeDelegate(SetLastChange);

			mLastChangeLbl.BeginInvoke(SetLast, change);
		}

		private delegate void ShowBuildFailedDelegate(bool show);

		public void ShowBuildFailed(bool show)
		{
			mBuildFailedLbl.Visible = show;
		}

		ShowBuildFailedDelegate ShowFail = null;

		private void AsynchSetLastChange( bool show )
		{
			if (ShowFail == null)
				ShowFail = new ShowBuildFailedDelegate(ShowBuildFailed);

			mLastChangeLbl.BeginInvoke(ShowFail, show);
		}

		private void ReadStandardOutThreadProc()  //Process p, TextBox t)
		{
			try
			{
				String line;
				while ((line = buildProc.StandardOutput.ReadLine()) != null)
				{
					AsynchAddLineToLog(line);

					if (line.Contains("FAILED."))
						buildFailed = true;
				}
				return;
			}
			catch (ThreadAbortException) { return; }
		}

		private void ReadStandardErrorThreadProc()  //Process p, TextBox t)
		{
			try
			{
				String line;
				while ((line = buildProc.StandardError.ReadLine()) != null)
				{
					AsynchAddLineToLog(line);
				}
				return;
			}
			catch (ThreadAbortException) { return; }
		}

		int mBuildInterval = 2;

		private void MonitorThreadProc()
		{
			try
			{
				while (true)
				{
					int last = GetLastChange();

					mLogText.BeginInvoke(new AddLineDelegate(AddLineToLog), 
						String.Format("[{0} {1}] Checking for changes....", 
						DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString()));
					if (last > changeAtLastBuild)
					{
						mLogText.BeginInvoke(new AddLineDelegate(AddLineToLog), "Changes found");
						RunBuild(false);
					}

					AsynchAddLineToLog( String.Format("Next Check at {0}", 
						DateTime.Now.AddMinutes((double)mBuildInterval).ToShortTimeString()));
					Thread.Sleep(TimeSpan.FromMinutes(mBuildInterval));
				}
			}
			catch (ThreadAbortException) { }
		}

		private void mBuildIntervalCmb_SelectedIndexChanged(object sender, EventArgs e)
		{
			int.TryParse((String)mBuildIntervalCmb.SelectedItem, out mBuildInterval);
		}

		private void FormMain_Load(object sender, EventArgs e)
		{
			ReadSettings();

			if (String.IsNullOrEmpty(mSolutionPath.Text))
				return;

			lastChange = GetLastChange();
			mLastChangeLbl.Text = lastChange.ToString();
			changeAtLastBuild = lastChange;

			// start the monitor
			MonitorChangesThread = new Thread(new ThreadStart(MonitorThreadProc));
			MonitorChangesThread.IsBackground = true;
			MonitorChangesThread.Start();

		}

		private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
		{
			SaveSettings();
		}

		private void SaveSettings()
		{
			using (StreamWriter sw = new StreamWriter("buildProc.config"))
			{
				//Credentials

				if (mShowPasswordChk.Checked)
					sw.Write('1');
				else
					sw.Write('0');

				sw.WriteLine(mPaswordTxt.Text);
				sw.WriteLine(mUserText.Text);
				sw.WriteLine(mServerConnection.Text);

				sw.WriteLine(mBuildFolderTxt.Text);

				sw.WriteLine(mMsBuildPathTxt.Text);

				sw.WriteLine(mBuildIntervalCmb.SelectedIndex.ToString());

				sw.WriteLine(mSolutionPath.Text);
			}
	   }
		private void ReadSettings()
		{
			if (!System.IO.File.Exists("buildProc.config"))
				return;
			using (StreamReader sr = new StreamReader("buildProc.config"))
			{
				//Credentials
				String line = sr.ReadLine();
				mShowPasswordChk.Checked = (line[0] == '1');
				mPaswordTxt.Text = line.Substring(1);

				mUserText.Text = sr.ReadLine();
				mServerConnection.Text = sr.ReadLine();

				mBuildFolderTxt.Text = sr.ReadLine();

				mMsBuildPathTxt.Text = sr.ReadLine();

				line = sr.ReadLine();
				int idx = 1;
				int.TryParse(line, out idx);
				mBuildIntervalCmb.SelectedIndex = idx;

				mSolutionPath.Text = sr.ReadLine();
			}
		}

		private void mUserText_TextChanged(object sender, EventArgs e)
		{

		}

		private void mSolutionPath_TextChanged(object sender, EventArgs e)
		{
			string path = mSolutionPath.Text;

			mBuildNowBtn.Enabled = !string.IsNullOrEmpty(path);
		}


	}
}