using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using TweetDuck.Core.Utils;

namespace TweetDuck.Configuration{
    sealed class LockManager{
        private const int RetryDelay = 250;

        public enum Result{
            Success, HasProcess, Fail
        }

        private readonly string file;
        private FileStream lockStream;
        private Process lockingProcess;

        public LockManager(string file){
            this.file = file;
        }

        // Lock file

        private bool ReleaseLockFileStream(){
            if (lockStream != null){
                lockStream.Dispose();
                lockStream = null;
                return true;
            }
            else{
                return false;
            }
        }

        private Result TryCreateLockFile(){
            void CreateLockFileStream(){
                lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
                lockStream.Write(BitConverter.GetBytes(WindowsUtils.CurrentProcessID), 0, sizeof(int));
                lockStream.Flush(true);
            }

            try{
                CreateLockFileStream();
                return Result.Success;
            }catch(DirectoryNotFoundException){
                try{
                    CreateLockFileStream();
                    return Result.Success;
                }catch{
                    ReleaseLockFileStream();
                    return Result.Fail;
                }
            }catch(IOException){
                return Result.HasProcess;
            }catch{
                ReleaseLockFileStream();
                return Result.Fail;
            }
        }

        // Lock management

        public Result Lock(){
            if (lockStream != null){
                return Result.Success;
            }

            Result initialResult = TryCreateLockFile();

            if (initialResult == Result.HasProcess){
                try{
                    int pid;

                    using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
                        byte[] bytes = new byte[sizeof(int)];
                        fileStream.Read(bytes, 0, bytes.Length);
                        pid = BitConverter.ToInt32(bytes, 0);
                    }

                    try{
                        Process foundProcess = Process.GetProcessById(pid);

                        using(Process currentProcess = Process.GetCurrentProcess()){
                            if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
                                lockingProcess = foundProcess;
                            }
                            else{
                                foundProcess.Close();
                            }
                        }
                    }catch{
                        // GetProcessById throws ArgumentException if the process is missing
                        // Process.MainModule can throw exceptions in some cases
                    }

                    return lockingProcess == null ? Result.Fail : Result.HasProcess;
                }catch{
                    return Result.Fail;
                }
            }

            return initialResult;
        }

        public Result LockWait(int timeout){
            for(int elapsed = 0; elapsed < timeout; elapsed += RetryDelay){
                Result result = Lock();

                if (result == Result.HasProcess){
                    Thread.Sleep(RetryDelay);
                }
                else{
                    return result;
                }
            }

            return Lock();
        }

        public bool Unlock(){
            if (ReleaseLockFileStream()){
                try{
                    File.Delete(file);
                }catch(Exception e){
                    Program.Reporter.Log(e.ToString());
                    return false;
                }
            }

            return true;
        }

        // Locking process

        public bool RestoreLockingProcess(int failTimeout){
            if (lockingProcess != null && lockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
                NativeMethods.BroadcastMessage(Program.WindowRestoreMessage, (uint)lockingProcess.Id, 0);

                if (WindowsUtils.TrySleepUntil(() => CheckLockingProcessExited() || (lockingProcess.MainWindowHandle != IntPtr.Zero && lockingProcess.Responding), failTimeout, RetryDelay)){
                    return true;
                }
            }

            return false;
        }

        public bool CloseLockingProcess(int closeTimeout, int killTimeout){
            if (lockingProcess != null){
                try{
                    if (lockingProcess.CloseMainWindow()){
                        WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, RetryDelay);
                    }

                    if (!lockingProcess.HasExited){
                        lockingProcess.Kill();
                        WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, RetryDelay);
                    }

                    if (lockingProcess.HasExited){
                        lockingProcess.Dispose();
                        lockingProcess = null;
                        return true;
                    }
                }catch(Exception ex) when (ex is InvalidOperationException || ex is Win32Exception){
                    if (lockingProcess != null){
                        bool hasExited = CheckLockingProcessExited();
                        lockingProcess.Dispose();
                        return hasExited;
                    }
                }
            }

            return false;
        }

        private bool CheckLockingProcessExited(){
            lockingProcess.Refresh();
            return lockingProcess.HasExited;
        }
    }
}