using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;

namespace TweetLib.Core.Systems.Startup{
    public 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(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);

                        if (MatchesCurrentProcess(foundProcess)){
                            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){
                    App.ErrorHandler.Log(e.ToString());
                    return false;
                }
            }

            return true;
        }

        // Locking process
        
        public bool RestoreLockingProcess(){
            return lockingProcess != null && App.LockHandler.RestoreProcess(lockingProcess);
        }

        public bool CloseLockingProcess(){
            if (lockingProcess != null && App.LockHandler.CloseProcess(lockingProcess)){
                lockingProcess = null;
                return true;
            }

            return false;
        }

        // Utilities

        private static int CurrentProcessID{
            get{
                using Process me = Process.GetCurrentProcess();
                return me.Id;
            }
        }

        [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
        private static bool MatchesCurrentProcess(Process process){
            using Process current = Process.GetCurrentProcess();
            return current.MainModule.FileVersionInfo.InternalName == process.MainModule.FileVersionInfo.InternalName;
        }
    }
}