using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.ServiceProcess;
using System.Windows.Forms;
using System.Collections.Generic;

using Microsoft.Win32;

namespace EA.FileTalk
{
	class Installer
	{
		private const string mFileServerName = "FileTalkServer";
		private const string mFileTalkRegEntry = @"Software\Electronic Arts Inc.\" + mFileServerName;

		private const string mFileTalkServerServiceRegEntry = @"SYSTEM\CurrentControlSet\Services\" + mFileServerName;
		private string mInstallationDirectory = Application.CommonAppDataPath;
		private string mFileTalkVersion;

		private Installer()
		{
			mInstallationDirectory = GetInstallationPathForFileTalk();
		}

		#region Singleton Code
		class Nested
		{
			// Explicit static constructor to tell C# compiler
			// not to mark type as beforefieldinit
			static Nested()
			{
			}

			internal static readonly Installer instance = new Installer();
		}

		public static Installer Instance
		{
			get
			{
				return Nested.instance;
			}
		}
		#endregion

		/// <summary>
		/// Creation of the installation path is a copy/paste from GameTalk
		/// </summary>
		/// <returns></returns>
		private string GetInstallationPathForFileTalk()
		{
			Assembly asm = Assembly.GetExecutingAssembly();
			mFileTalkVersion = asm.GetName().Version.ToString();

			// Instead of using Application.LocalUserAppDataPath - we will try to set sInstallationDirectory
			// to a path that closely resembles Application.CommonAppDataPath (shared between users)
			string rootPathForCommonAppDataPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData);
			object[] objArray = asm.GetCustomAttributes(false);
			// Windows can handle a directory with two consecutive backslashes - so empty string will work
			string companyName = string.Empty;
			// Windows can handle a directory with two consecutive backslashes - so empty string will work
			string titleName = string.Empty;
			foreach (object obj in objArray)
			{
				AssemblyCompanyAttribute company = obj as AssemblyCompanyAttribute;
				if (company != null)
					companyName = company.Company;

				AssemblyTitleAttribute title = obj as AssemblyTitleAttribute;
				if (title != null)
					titleName = title.Title;
			}
			// For some reason by default Microsoft removes "." from the company string it returns the string
			// from Application.CommonAppDataPath - so we'll imitate that
			return rootPathForCommonAppDataPath + "\\" + companyName.Replace(".", "") + "\\" + titleName + "\\" + mFileTalkVersion;
		}

		/// <summary>
		/// This is strictly a debugging method - outputs a list of manifest resource names
		/// </summary>
		/// <param name="assembly"></param>
		private static void DebugAssembly(Assembly assembly)
		{
			string[] manifestResourceNames = assembly.GetManifestResourceNames();
			for (int i = 0; i < manifestResourceNames.Length; i++)
			{
				Trace.WriteLine("name: " + manifestResourceNames[i]);
			}
		}

		private ServiceController GetFileTalkServerService()
		{
			ServiceController[] services = ServiceController.GetServices();
			foreach (ServiceController service in services)
			{
				if (service.ServiceName.Equals(mFileServerName))
					return service;
			}
			return null;
		}

		private Process FileTalkServerRunningAsApplication()
		{
			string gameTalkServerInLowerCase = "filetalkserver";
			foreach (Process clsProcess in Process.GetProcesses())
			{
				if (clsProcess.ProcessName.ToLower().Contains(gameTalkServerInLowerCase))
				{
					return clsProcess;
				}
			}
			return null;
		}

		/// <summary>
		/// Just as the name implies - this will install the FileTalkServer if the
		/// currently installed version is older than this version
		/// It will also start the GameTalkServer (service)
		/// </summary>
		public void InstallServer(bool startServerAsApp)
		{
			bool serviceFound = false;
			Assembly asm = Assembly.GetExecutingAssembly();
			// The following is FileTalk's version (NOT the tool that happens to be running FileTalk)
			string fileTalkVersion = asm.GetName().Version.ToString();

			ServiceController fileTalkService = GetFileTalkServerService();
			Process fileTalkProcess = FileTalkServerRunningAsApplication();
			if (startServerAsApp)
			{
				if ((fileTalkService == null) && (fileTalkProcess != null))
					serviceFound = true;
			}
			else if (fileTalkService != null)
				serviceFound = true;

			string installedFileTalkVersion = GameTalkMessages.Configuration.Instance.GetInstalledFileTalkVersion(mFileTalkRegEntry);
			bool installNewVersion = EA.GameTalk.Installer.ShouldNewVersionBeInstalled(
				installedFileTalkVersion, fileTalkVersion);
			if ((!serviceFound) || (installNewVersion))
			{
				string fileTalkServerFilename = mFileServerName + ".exe";
				string fileTalkServerFullFilename = mInstallationDirectory + @"\" + fileTalkServerFilename;
				string installUtilFullFilename = mInstallationDirectory + @"\" + "InstallUtil.exe";

				ProcessStartInfo info = null;
				Process p = null;

				// We need to create the FileTalkServer and InstallUtil files to uninstall FileTalkServer
				if (!File.Exists(fileTalkServerFullFilename))
					EA.GameTalk.Installer.CreateFileFromResource("EA.FileTalk.FileTalkServer." + fileTalkServerFilename, fileTalkServerFullFilename, Assembly.GetExecutingAssembly());

				if (!File.Exists(installUtilFullFilename))
					EA.GameTalk.Installer.CreateFileFromResource("EA.FileTalk.FileTalkServer.InstallUtil.exe", installUtilFullFilename, Assembly.GetExecutingAssembly());

				// Stop and delete service
				info = new ProcessStartInfo();
				info.FileName = installUtilFullFilename;
				info.Arguments = "/u " + fileTalkServerFilename;
				info.WorkingDirectory = mInstallationDirectory;
				info.WindowStyle = ProcessWindowStyle.Hidden;
				// Next two statements are for debug only
				/*info.RedirectStandardOutput = true;
				info.UseShellExecute = false;*/
				p = Process.Start(info);
				// Next two statements are for debug only
				/*string output = p.StandardOutput.ReadToEnd();
				Trace.WriteLine("output: " + output);*/
				p.WaitForExit();

				try
				{
					if ((fileTalkProcess != null) && (!fileTalkProcess.HasExited))
						fileTalkProcess.Kill();
				}
				catch
				{
				}

				//DebugAssembly(Assembly.GetExecutingAssembly());

				// Create the necessary files
				EA.GameTalk.Installer.CreateFileFromResource("EA.FileTalk.FileTalkServer.InstallUtil.exe", installUtilFullFilename, Assembly.GetExecutingAssembly());
				EA.GameTalk.Installer.CreateFileFromResource("EA.FileTalk.FileTalkServer." + fileTalkServerFilename, fileTalkServerFullFilename, Assembly.GetExecutingAssembly());

				if (!startServerAsApp)
				{
					// Install service
					info = new ProcessStartInfo();
					info.FileName = installUtilFullFilename;
					info.Arguments = fileTalkServerFilename;
					info.WorkingDirectory = mInstallationDirectory;
					info.WindowStyle = ProcessWindowStyle.Hidden;
					// The four commented out lines are useful for debugging
					//info.RedirectStandardOutput = true;
					//info.UseShellExecute = false;
					p = Process.Start(info);
					//string output = p.StandardOutput.ReadToEnd();
					//Trace.WriteLine("output: "+output);
					p.WaitForExit();

					// The registry may have gotten corrupted by the InstallUtil - Microsoft bug
					EA.GameTalk.Installer.PatchRegistry(mFileTalkServerServiceRegEntry);

					// Start service after install
					info = new ProcessStartInfo();
					info.FileName = "sc.exe";
					info.Arguments = "start " + mFileServerName;
					info.WorkingDirectory = mInstallationDirectory;
					info.WindowStyle = ProcessWindowStyle.Hidden;
					// Two following lines are for debugging
					/*info.RedirectStandardOutput = true;
					info.UseShellExecute = false;*/
					p = Process.Start(info);
					// Two following lines are for debugging
					/*string output2 = p.StandardOutput.ReadToEnd();
					Trace.WriteLine("output: "+output2);*/
				}
				else // start it as an application - instead of as a service
				{
					info = new ProcessStartInfo();
					info.FileName = fileTalkServerFilename;
					info.Arguments = EA.GameTalkMessages.Configuration.RunServerAsAppCommandLineArgs;
					info.WorkingDirectory = mInstallationDirectory;
					info.WindowStyle = ProcessWindowStyle.Hidden;
					// The four commented out lines are useful for debugging
					//info.RedirectStandardOutput = true;
					//info.UseShellExecute = false;
					p = Process.Start(info);
					//string output = p.StandardOutput.ReadToEnd();
					//Trace.WriteLine("output: "+output);
				}
				GameTalkMessages.Configuration.Instance.SetInstalledFileTalkVersion(mFileTalkRegEntry, mFileTalkVersion);
			}
			else // If it is installed, but not running - start it
			{
				if (!startServerAsApp)
				{
					if ((fileTalkService != null) && (fileTalkService.Status != ServiceControllerStatus.Running))
						fileTalkService.Start();
				}
				else
					System.Diagnostics.Debug.Assert(false, "not sure how we couldn't get a reference to the FileTalkServer service and yet it is installed");
			}
		}

	}
}
