using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Beefy;

namespace IDE
{
    public class WakaTime
    {
        String mApiKey ~ delete _;

        String mPythonLocation ~ delete _;
        String mCurFilePath = new String() ~ delete _;
        int32 mLastSendTicks;
		Monitor mMonitor = new Monitor() ~ delete _;

        public this(String key)
        {
            mApiKey = new String(key);            
        }

        public void Dispose()
        {            
        }

        public void QueueFile(String filePath, String projectName, bool isWrite)
        {
			String useFilePath = filePath;
			String useProjectName = projectName;

            bool doSend = false;
            if (mCurFilePath != useFilePath)
            {
                doSend = true;                
            }
            else if (mLastSendTicks > 120*60)
            {                
                doSend = true;
            }

            if (isWrite)
                doSend = true;
            mCurFilePath.Set(useFilePath);

            if (doSend)
            {
				useFilePath = new String(useFilePath);
				useProjectName = new String(useProjectName);
				var me = this;
                ThreadPool.QueueUserWorkItem(new () => me.SendFile(useFilePath, useProjectName, isWrite) ~ { delete useFilePath; delete useProjectName; });
                mLastSendTicks = 0;
            }
        }

        public void Update()
        {
            mLastSendTicks++;
        }
        
        public void getPythonDir(String pyDir)
        {
			getCurrentDirectory(pyDir);
            pyDir.Append("\\Python");
        }

        public void getPython(String outPath)
        {

			doesPythonExist();
			//return;


            if (mPythonLocation != null)
			{
                outPath.Append(mPythonLocation);
				return;
			}

            String[] locations = scope
            	.(
					//"c:\\Python27\\python.exe",

                    "pythonw.exe",
                    "python",
                    "\\Python37\\pythonw",
                    "\\Python36\\pythonw",
                    "\\Python35\\pythonw",
                    "\\Python34\\pythonw",
                    "\\Python33\\pythonw",
                    "\\Python32\\pythonw",
                    "\\Python31\\pythonw",
                    "\\Python30\\pythonw",
                    "\\Python27\\pythonw",
                    "\\Python26\\pythonw",
                    "\\python37\\pythonw",
                    "\\python36\\pythonw",
                    "\\python35\\pythonw",
                    "\\python34\\pythonw",
                    "\\python33\\pythonw",
                    "\\python32\\pythonw",
                    "\\python31\\pythonw",
                    "\\python30\\pythonw",
                    "\\python27\\pythonw",
                    "\\python26\\pythonw",
                    "\\Python37\\python",
                    "\\Python36\\python",
                    "\\Python35\\python",
                    "\\Python34\\python",
                    "\\Python33\\python",
                    "\\Python32\\python",
                    "\\Python31\\python",
                    "\\Python30\\python",
                    "\\Python27\\python",
                    "\\Python26\\python",
                    "\\python37\\python",
                    "\\python36\\python",
                    "\\python35\\python",
                    "\\python34\\python",
                    "\\python33\\python",
                    "\\python32\\python",
                    "\\python31\\python",
                    "\\python30\\python",
                    "\\python27\\python",
                    "\\python26\\python"
                );
            for (String location in locations)
            {
                if (location.Contains('\\'))
				{
					String dirName = scope String();
					Path.GetDirectoryPath(location, dirName);
                    if (!Directory.Exists(dirName))
                        continue;
				}

                ProcessStartInfo procInfo = scope ProcessStartInfo();
                procInfo.UseShellExecute = false;
                procInfo.RedirectStandardError = true;
				procInfo.RedirectStandardOutput = true;
                procInfo.SetFileName(location);
                procInfo.CreateNoWindow = true;
                procInfo.SetArguments("--version");

				Debug.WriteLine("ProcStartInfo {0} Verb: {1}", procInfo, procInfo.[Friend]mVerb);

				/*Process process = null;
				if (!case .Ok(out process) = Process.Start(procInfo))
					continue;
				defer(scope) delete process;
                String errors = scope String();
                if (case .Err = process.StandardError.ReadToEnd(errors))
					continue;*/

				String resultStr = scope String();
				SpawnedProcess process = scope SpawnedProcess();
				if (process.Start(procInfo) case .Err)
					continue;

				FileStream fileStream = scope FileStream();
				process.AttachStandardError(fileStream);
				StreamReader streamReader = scope StreamReader(fileStream, null, false, 4096);
				streamReader.ReadToEnd(resultStr).IgnoreError();

				if (resultStr.IsEmpty)
				{
					FileStream fileStreamOut = scope FileStream();
					process.AttachStandardOutput(fileStreamOut);
					StreamReader streamReaderOut = scope StreamReader(fileStreamOut, null, false, 4096);
					streamReaderOut.ReadToEnd(resultStr).IgnoreError();
				}

				//TODO: This 'errors' check is not correct, but also it seems that we're getting stdout data in stderr... (?)
                if ((resultStr != null) && (resultStr != ""))
                {
                    mPythonLocation = new String(location);
					outPath.Append(mPythonLocation);
                    return;
                }
            }

			mPythonLocation = new String("");// Give up

            return;
        }

        void GetCLI(String cliPath)
        {
            cliPath.Append(BFApp.sApp.mInstallDir, "wakatime-master/wakatime/cli.py");
        }

		SpawnedProcess mProcess ~ delete _;

		public void SendFile(String fileName, String projectName, bool isWrite)
		{
			using (mMonitor.Enter())
			{
                DoSendFile(fileName, projectName, isWrite);
			}
		}

        public void DoSendFile(String fileName, String projectName, bool isWrite)
        {
            Debug.WriteLine("WakaTime: {0} proj: {1} isWrite: {2} ThreadId: {3} Tick: {4}", fileName, projectName, isWrite, Thread.CurrentThread.Id, (int32)Platform.BfpSystem_TickCount());

            String arguments = scope String();
            arguments.Append("\"");
            GetCLI(arguments);
            arguments.Append("\" --key=\"", mApiKey, "\"",
                                " --file=\"", fileName, "\"",
                                " --plugin=\"Beef\"");

            if (!String.IsNullOrWhiteSpace(projectName))
                arguments.Append(" --project=\"", projectName, "\"");

            if (isWrite)
                arguments.Append(" --write");

            ProcessStartInfo procInfo = scope ProcessStartInfo();
            procInfo.UseShellExecute = false;
			String pyPath = scope String();
			getPython(pyPath);
			if (String.IsNullOrEmpty(pyPath))
				return;
            procInfo.SetFileName(pyPath);
            procInfo.CreateNoWindow = true;
            procInfo.SetArguments(arguments);



			//for (int i = 0; i < 10; i++)
			{
				//Debug.WriteLine("ProcStartInfo {0} Dir: {1} Verb: {2}", procInfo, procInfo.mDirectory, procInfo.mVerb);

				delete mProcess;
				mProcess = new SpawnedProcess();
				if (mProcess.Start(procInfo) case .Err)
				{
					delete mProcess;																																													    
					mProcess = null;
				}
			}
        }

        /// Check if wakatime command line exists or not
        bool doesCLIExist()
        {
			String path = scope String();
			GetCLI(path);
            if (File.Exists(path))
            {
                return true;
            }
            return false;
        }

        /// Check if bundled python installation exists
        bool doesPythonExist()
        {
			String path = scope String();
			getPythonDir(path);
			path.Append("\\pythonw.exe");
            if (File.Exists(path))
            {
                return true;
            }
            return false;
        }


        /// Check if python is installed
        bool isPythonInstalled()
        {
			String pyPath = scope String();
			getPython(pyPath);
            if (String.IsNullOrEmpty(pyPath))
            {
                return true;
            }
            return false;
        }

        /// Returns current working dir
        static public void getCurrentDirectory(String outDir)
        {
            var exePath = scope String();
            Environment.GetExecutableFilePath(exePath);
            Path.GetDirectoryPath(exePath, outDir);
        }
    }
}
