using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;

namespace DataOrganizer
{
    class Snapshot
    {
        public Folder Root { get; private set; }
        public Dictionary<string, FileSnapshot> Files { get; private set; } = new Dictionary<string, FileSnapshot>();
        public Dictionary<string, Folder> Directories { get; private set; } = new Dictionary<string, Folder>();
        public Dictionary<string, Duplicate> FileDuplicates { get; private set; } = new Dictionary<string, Duplicate>();
        //Contains files associated with particular, first level folders.
        //So if the root folder group is X:\Anime and we have a folder path of x:\Anime\Death Note, then indexing this dictonary
        //with the text "Death Note" would return all files in that folder.
        public Dictionary<string, List<FileSnapshot>> OrganizationFolders { get; private set; } = new Dictionary<string, List<FileSnapshot>>();

        public Snapshot(List<DB.FileSnapshot> fileRecords, DB.FolderGroup folderGroup)
        {
            Root = new Folder(folderGroup.Name, folderGroup.Path, null);
            Directories.Add(Root.FullPath, Root);

            if (folderGroup.Type == DB.OrganizationType.SingleLevel)
                ProcessFilesAsOneLevel(fileRecords, folderGroup);
            //else if (folderGroup.Type == DB.OrganizationType.Flat)
            //    ProcessFilesAsFlat(fileRecords, folderGroup);
        }

        public Snapshot(List<FileInfo> files, DB.FolderGroup folderGroup)
        {
            Root = new Folder(folderGroup.Name, folderGroup.Path, null);
            Directories.Add(Root.FullPath, Root);

            if (folderGroup.Type == DB.OrganizationType.SingleLevel)
                ProcessFilesAsOneLevel(files, folderGroup);
        }

        private void ProcessFilesAsOneLevel(List<DB.FileSnapshot> files, DB.FolderGroup folderGroup)
        {
            Folder firstLevelFolder = null;

            foreach (var file in files)
            {
                var folders = file.PathNoRoot.Split(new char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
                var orgFolder = Path.Combine(folderGroup.Path, folders[0]);
                var currentPath = Path.Combine(folderGroup.Path, folders[0]);

                if(firstLevelFolder == null || firstLevelFolder.Name != folders[0])
                {
                    firstLevelFolder = new Folder(folders[0], Path.Combine(folderGroup.Path, folders[0]), Root);
                    Directories.Add(orgFolder, firstLevelFolder);
                    OrganizationFolders.Add(orgFolder, new List<FileSnapshot>());
                    Root.AddDirectory(firstLevelFolder);
                }

                for(var i = 1; i < folders.Length - 1; i++)
                {
                    var parentFolder = currentPath;
                    currentPath = Path.Combine(currentPath, folders[i]);
                    if(!Directories.ContainsKey(currentPath))
                    {
                        var newFolder = new Folder(folders[i], currentPath, Directories[parentFolder]);
                        Directories.Add(currentPath, newFolder);
                        Directories[parentFolder].AddDirectory(newFolder);
                    }
                }

                //var duplicates = OrganizationFolders[orgFolder].Where(x => x.Size == file.Size && x.Id != file.Id);

                //foreach(var dup in duplicates)
                //{
                //    if(FileDuplicates.ContainsKey(file.PathNoRoot))
                //    {
                //        FileDuplicates[file.PathNoRoot].Duplicates.Add(dup);
                //    }
                //    else FileDuplicates.Add(file.PathNoRoot, new Duplicate() { File = file, Duplicates = new List<DB.FileSnapshot>() { dup } });
                //}
                var snap = new FileSnapshot(file, firstLevelFolder, Directories[currentPath], folderGroup.Path);

                OrganizationFolders[orgFolder].Add(snap);
                Directories[currentPath].AddFile(snap);
                Files.Add(Path.Combine(folderGroup.Path, file.PathNoRoot), snap);
            }
        }

        private void ProcessFilesAsOneLevel(List<FileInfo> files, DB.FolderGroup folderGroup)
        {
            Folder firstLevelFolder = null;

            foreach (var file in files)
            {
                //Add one to the length to remove the trailing slash in the root path.
                var pathNoRoot = file.FullName.Remove(0, folderGroup.Path.Length + 1);
                var folders = pathNoRoot.Split(new char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
                var orgFolder = Path.Combine(folderGroup.Path, folders[0]);
                var currentPath = Path.Combine(folderGroup.Path, folders[0]);

                if (firstLevelFolder == null || firstLevelFolder.Name != folders[0])
                {
                    firstLevelFolder = new Folder(folders[0], Path.Combine(folderGroup.Path, folders[0]), Root);
                    Directories.Add(orgFolder, firstLevelFolder);
                    OrganizationFolders.Add(orgFolder, new List<FileSnapshot>());
                    Root.AddDirectory(firstLevelFolder);
                }

                for (var i = 1; i < folders.Length - 1; i++)
                {
                    var parentFolder = currentPath;
                    currentPath = Path.Combine(currentPath, folders[i]);
                    if (!Directories.ContainsKey(currentPath))
                    {
                        var newFolder = new Folder(folders[i], currentPath, Directories[parentFolder]);
                        Directories.Add(currentPath, newFolder);
                        Directories[parentFolder].AddDirectory(newFolder);
                    }
                }

                //var duplicates = OrganizationFolders[orgFolder].Where(x => x.Size == file.Size && x.Id != file.Id);

                //foreach(var dup in duplicates)
                //{
                //    if(FileDuplicates.ContainsKey(file.PathNoRoot))
                //    {
                //        FileDuplicates[file.PathNoRoot].Duplicates.Add(dup);
                //    }
                //    else FileDuplicates.Add(file.PathNoRoot, new Duplicate() { File = file, Duplicates = new List<DB.FileSnapshot>() { dup } });
                //}
                var snap = new FileSnapshot(file, firstLevelFolder, Directories[currentPath], folderGroup.Path);

                OrganizationFolders[orgFolder].Add(snap);
                Directories[currentPath].AddFile(snap);
                Files.Add(snap.FullPath, snap);
            }
        }

        private void ProcessFilesAsFlat(List<DB.FileSnapshot> files, DB.FolderGroup folderGroup)
        {

        }

        public void DeleteFile(FileSnapshot file)
        {
            if (Files.ContainsKey(file.FullPath)) Files.Remove(file.FullPath);
            Root.DeleteFile(file);
            file.ParentFolder.DeleteFile(file);
        }

        public void DeleteFolder(Folder folder)
        {
            ClearSubFolders(folder);
        }

        private void ClearSubFolders(Folder folder)
        {
            if (Directories.ContainsKey(folder.FullPath)) Directories.Remove(folder.FullPath);
            if (OrganizationFolders.ContainsKey(folder.FullPath)) OrganizationFolders.Remove(folder.FullPath);
            Root.DeleteFolder(folder);
            folder.Parent.DeleteFolder(folder);

            foreach (var dir in folder.Folders)
                ClearSubFolders(dir.Value);
        }

        public void MergeSnapshot(Snapshot snapshot)
        {
            //TODO: build this out
            var newFiles = Files.Values.Except(snapshot.Files.Values);
            var newFolders = Directories.Values.Except(snapshot.Directories.Values);
            var newOrgFolders = OrganizationFolders.Values.Except(snapshot.OrganizationFolders.Values);
            var existingFiles = Files.Values.Except(snapshot.Files.Values);

            foreach (var existingFile in existingFiles)
            {
                if (!snapshot.Files.ContainsKey(existingFile.FullPath))
                {
                    existingFile.Status = FileSnapshot.FileStatus.PendingDelete;
                    //deletedFiles.Add(existingFile);
                    //pendingChanges++;
                }
                else if (existingFile.ModifiedDate != snapshot.Files[existingFile.FullPath].ModifiedDate)
                {
                    existingFile.ModifiedDate = snapshot.Files[existingFile.FullPath].ModifiedDate;
                    //modifiedFiles.Add(snapShot.Files[existingFile.FullPath]);
                    //pendingChanges++;
                }
            }

            foreach(var newFile in newFiles)
            {
                if(!Files.ContainsKey(newFile.FullPath)) Files.Add(newFile.FullPath, newFile);
                Folder folder = newFile.ParentFolder;

                while(folder != null)
                {
                    if (!Directories.ContainsKey(folder.FullPath))
                    {
                        Directories.Add(folder.FullPath, folder);
                    }

                    if (folder.Parent == null && !OrganizationFolders.ContainsKey(folder.FullPath)) OrganizationFolders.Add(folder.FullPath, folder.Files.Values.ToList());

                    folder = folder.Parent;
                }
            }
        }

        public struct Duplicate
        {
            public DB.FileSnapshot File;
            public List<FileSnapshot> Duplicates;
        }
    }

    public class Folder
    {
        public string Name { get; private set; }
        public string FullPath { get; private set; }
        public Folder Parent { get; private set; }
        public Dictionary<string, FileSnapshot> Files { get; private set; } = new Dictionary<string, FileSnapshot>();
        public Dictionary<string, Folder> Folders { get; private set; } = new Dictionary<string, Folder>();
        public bool IsEmpty
        {
            get { return !(Files.Count > 0 || Folders.Count > 0); }
        }
        public bool IsRootFolder
        {
            get { return Parent == null; }
        }

        public Folder(string name, string fullPath, Folder parent)
        {
            Name = name;
            FullPath = fullPath;
            Parent = parent;
        }

        public void AddFile(FileSnapshot file)
        {
            Files.Add(file.FullPath, file);
        }

        public void AddDirectory(Folder folder)
        {
            Folders.Add(folder.FullPath, folder);
        }

        public List<FileSnapshot> GetAllPendingChanges()
        {
            var files = new List<FileSnapshot>();

            files.AddRange(Files.Values.Where(x => x.Status != FileSnapshot.FileStatus.None));

            foreach (var folder in Folders.Values)
                files.AddRange(folder.GetAllPendingChanges());

            return files;
        }

        public void DeleteFile(FileSnapshot file)
        {
            if (Files.ContainsKey(file.FullPath)) Files.Remove(file.FullPath);
        }

        public void DeleteFolder(Folder folder)
        {
            if (Folders.ContainsKey(folder.FullPath)) Folders.Remove(folder.FullPath);
        }
    }

    public class FileSnapshot
    {
        public Folder OrganizationalFolder { get; }
        public Folder ParentFolder { get; }
        public string PathNoRoot { get; }
        private string Root { get; }
        private int _FileId { get; set; }
        private string _Name { get; set; }
        private DateTime _ModifiedDate { get; set; }
        public DateTime CreatedDate { get; }
        public long Size { get; }
        private string _NamingPattern { get; set; }
        public FileStatus Status { get; set; }

        public int FileId
        {
            get { return _FileId; }
            set
            {
                //Since this file is getting an ID, that means we've been commited to the database, so change the Status to reflect this.
                Status = FileStatus.None;
                FileId = value;
            }
        }

        public DateTime ModifiedDate
        {
            get { return _ModifiedDate; }
            set
            {
                if (Status != FileStatus.PendingInsert) Status = FileStatus.PendingUpdate;
                _ModifiedDate = value;
            }
        }

        public string NamingPattern
        {
            get { return _NamingPattern; }
            set
            {
                if (Status != FileStatus.PendingInsert) Status = FileStatus.PendingUpdate;
                _NamingPattern = value;
            }
        }

        public string Name
        {
            get { return _Name; }
        }

        public FileSnapshot(DB.FileSnapshot file, Folder organizationalFolder, Folder parentFolder, string rootPath)
        {
            OrganizationalFolder = organizationalFolder;
            ParentFolder = parentFolder;

            var db = new DB();

            if (file.PatternId != 0) _NamingPattern = db.GetNamingPatternById(file.PatternId);

            PathNoRoot = file.PathNoRoot;
            Root = rootPath;
            _Name = file.Name;
            _ModifiedDate = file.ModifiedDate;
            CreatedDate = file.CreationDate;
            Size = file.Size;
            _FileId = file.Id;
            Status = FileStatus.None;
        }

        public FileSnapshot(FileInfo file, Folder organizationalFolder, Folder parentFolder, string rootPath)
        {
            OrganizationalFolder = organizationalFolder;
            ParentFolder = parentFolder;

            _Name = file.Name;
            //Add one to the length to remove the trailing slash in the root path.
            PathNoRoot = file.FullName.Remove(0, rootPath.Length + 1);
            Root = rootPath;
            _ModifiedDate = file.LastWriteTimeUtc;
            CreatedDate = file.CreationTimeUtc;
            Size = file.Length;
            _FileId = 0;
            Status = FileStatus.PendingInsert;
        }

        public bool HasOwnNamingPattern
        {
            get
            {
                return !string.IsNullOrEmpty(NamingPattern);
            }
        }

        public string FullPath
        {
            get
            {
                return Path.Combine(Root, PathNoRoot);
            }
        }

        public bool HasPendingChanges
        {
            get { return Status != FileStatus.None; }
        } 

        public enum FileStatus
        {
            PendingInsert,
            PendingUpdate,
            PendingDelete,
            Deleted,
            None
        }
    }

    public class SnapshotComparsion
    {

    }
}
