using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace SourceGit.Models
{
    public interface IRepository
    {
        string FullPath { get; set; }
        string GitDir { get; set; }

        void RefreshBranches();
        void RefreshTags();
        void RefreshCommits();
        void RefreshSubmodules();
        void RefreshWorkingCopyChanges();
        void RefreshStashes();
    }

    public class Watcher : IDisposable
    {
        public Watcher(IRepository repo)
        {
            _repo = repo;

            _wcWatcher = new FileSystemWatcher();
            _wcWatcher.Path = _repo.FullPath;
            _wcWatcher.Filter = "*";
            _wcWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime;
            _wcWatcher.IncludeSubdirectories = true;
            _wcWatcher.Created += OnWorkingCopyChanged;
            _wcWatcher.Renamed += OnWorkingCopyChanged;
            _wcWatcher.Changed += OnWorkingCopyChanged;
            _wcWatcher.Deleted += OnWorkingCopyChanged;
            _wcWatcher.EnableRaisingEvents = true;

            // If this repository is a worktree repository, just watch the main repository's gitdir.
            var gitDirNormalized = _repo.GitDir.Replace("\\", "/");
            var worktreeIdx = gitDirNormalized.IndexOf(".git/worktrees/");
            var repoWatchDir = _repo.GitDir;
            if (worktreeIdx > 0)
                repoWatchDir = _repo.GitDir.Substring(0, worktreeIdx + 4);

            _repoWatcher = new FileSystemWatcher();
            _repoWatcher.Path = repoWatchDir;
            _repoWatcher.Filter = "*";
            _repoWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName;
            _repoWatcher.IncludeSubdirectories = true;
            _repoWatcher.Created += OnRepositoryChanged;
            _repoWatcher.Renamed += OnRepositoryChanged;
            _repoWatcher.Changed += OnRepositoryChanged;
            _repoWatcher.Deleted += OnRepositoryChanged;
            _repoWatcher.EnableRaisingEvents = true;

            _timer = new Timer(Tick, null, 100, 100);
        }

        public void SetEnabled(bool enabled)
        {
            if (enabled)
            {
                if (_lockCount > 0)
                    _lockCount--;
            }
            else
            {
                _lockCount++;
            }
        }

        public void MarkBranchDirtyManually()
        {
            _updateBranch = DateTime.Now.ToFileTime() - 1;
        }

        public void MarkWorkingCopyDirtyManually()
        {
            _updateWC = DateTime.Now.ToFileTime() - 1;
        }

        public void Dispose()
        {
            _repoWatcher.EnableRaisingEvents = false;
            _repoWatcher.Created -= OnRepositoryChanged;
            _repoWatcher.Renamed -= OnRepositoryChanged;
            _repoWatcher.Changed -= OnRepositoryChanged;
            _repoWatcher.Deleted -= OnRepositoryChanged;
            _repoWatcher.Dispose();
            _repoWatcher = null;

            _wcWatcher.EnableRaisingEvents = false;
            _wcWatcher.Created -= OnWorkingCopyChanged;
            _wcWatcher.Renamed -= OnWorkingCopyChanged;
            _wcWatcher.Changed -= OnWorkingCopyChanged;
            _wcWatcher.Deleted -= OnWorkingCopyChanged;
            _wcWatcher.Dispose();
            _wcWatcher = null;

            _timer.Dispose();
            _timer = null;
        }

        private void Tick(object sender)
        {
            if (_lockCount > 0)
                return;

            var now = DateTime.Now.ToFileTime();
            if (_updateBranch > 0 && now > _updateBranch)
            {
                _updateBranch = 0;
                _updateWC = 0;

                if (_updateTags > 0)
                {
                    _updateTags = 0;
                    Task.Run(() =>
                    {
                        _repo.RefreshTags();
                        _repo.RefreshBranches();
                        _repo.RefreshCommits();
                    });
                }
                else
                {
                    Task.Run(() =>
                    {
                        _repo.RefreshBranches();
                        _repo.RefreshCommits();
                    });
                }

                Task.Run(_repo.RefreshWorkingCopyChanges);
            }

            if (_updateWC > 0 && now > _updateWC)
            {
                _updateWC = 0;
                Task.Run(_repo.RefreshWorkingCopyChanges);
            }

            if (_updateSubmodules > 0 && now > _updateSubmodules)
            {
                _updateSubmodules = 0;
                _repo.RefreshSubmodules();
            }

            if (_updateStashes > 0 && now > _updateStashes)
            {
                _updateStashes = 0;
                _repo.RefreshStashes();
            }

            if (_updateTags > 0 && now > _updateTags)
            {
                _updateTags = 0;
                _repo.RefreshTags();
                _repo.RefreshCommits();
            }
        }

        private void OnRepositoryChanged(object o, FileSystemEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Name))
                return;

            var name = e.Name.Replace("\\", "/");
            if (name.StartsWith("modules", StringComparison.Ordinal))
            {
                _updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
            }
            else if (name.StartsWith("refs/tags", StringComparison.Ordinal))
            {
                _updateTags = DateTime.Now.AddSeconds(.5).ToFileTime();
            }
            else if (name.StartsWith("refs/stash", StringComparison.Ordinal))
            {
                _updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
            }
            else if (name.Equals("HEAD", StringComparison.Ordinal) ||
                name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
                name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
                (name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
            {
                _updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
            }
            else if (name.StartsWith("objects/", StringComparison.Ordinal) || name.Equals("index", StringComparison.Ordinal))
            {
                _updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
            }
        }

        private void OnWorkingCopyChanged(object o, FileSystemEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Name))
                return;

            var name = e.Name.Replace("\\", "/");
            if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal))
                return;
            _updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
        }

        private readonly IRepository _repo = null;
        private FileSystemWatcher _repoWatcher = null;
        private FileSystemWatcher _wcWatcher = null;
        private Timer _timer = null;
        private int _lockCount = 0;
        private long _updateWC = 0;
        private long _updateBranch = 0;
        private long _updateSubmodules = 0;
        private long _updateStashes = 0;
        private long _updateTags = 0;
    }
}
