﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Oni
{
    internal sealed class InstanceFileOperations
    {
        private InstanceFileManager fileManager;
        private string destinationDir;
        private readonly Dictionary<string, string> fileNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private Dictionary<string, string> referencedFiles;
        private readonly Dictionary<string, string> instances = new Dictionary<string, string>(StringComparer.Ordinal);

        public void Copy(InstanceFileManager fileManager, List<string> sourceFiles, string destinationDir)
        {
            Initialize(fileManager, sourceFiles, destinationDir);

            foreach (KeyValuePair<string, string> pair in referencedFiles)
            {
                if (File.Exists(pair.Value))
                {
                    if (!Utils.AreFilesEqual(pair.Key, pair.Value))
                        Console.WriteLine("File {0} already exists at destination and it is different. File not copied.", pair.Value);
                }
                else
                {
                    File.Copy(pair.Key, pair.Value);
                }
            }
        }

        public void Move(InstanceFileManager fileManager, List<string> sourceFilePaths, string outputDirPath)
        {
            Initialize(fileManager, sourceFilePaths, outputDirPath);

            foreach (KeyValuePair<string, string> pair in referencedFiles)
            {
                if (File.Exists(pair.Value))
                {
                    if (Utils.AreFilesEqual(pair.Key, pair.Value))
                        File.Delete(pair.Key);
                    else
                        Console.WriteLine("File {0} already exists at destination and it is different. Source file not moved.", pair.Value);
                }
                else
                {
                    File.Move(pair.Key, pair.Value);
                }
            }
        }

        public void MoveOverwrite(InstanceFileManager fileManager, List<string> sourceFilePaths, string outputDirPath)
        {
            Initialize(fileManager, sourceFilePaths, outputDirPath);

            foreach (KeyValuePair<string, string> pair in referencedFiles)
            {
                if (File.Exists(pair.Value))
                    File.Delete(pair.Value);

                File.Move(pair.Key, pair.Value);
            }
        }

        public void MoveDelete(InstanceFileManager fileManager, List<string> sourceFilePaths, string outputDirPath)
        {
            Initialize(fileManager, sourceFilePaths, outputDirPath);

            foreach (KeyValuePair<string, string> pair in referencedFiles)
            {
                if (File.Exists(pair.Value))
                    File.Delete(pair.Key);
                else
                    File.Move(pair.Key, pair.Value);
            }
        }

        public void GetDependencies(InstanceFileManager fileManager, List<string> sourceFilePaths)
        {
            Initialize(fileManager, sourceFilePaths, null);

            foreach (string filePath in referencedFiles.Keys)
            {
                Console.WriteLine(filePath);
            }
        }

        private void Initialize(InstanceFileManager fileManager, List<string> inputFiles, string destinationDir)
        {
            this.fileManager = fileManager;
            this.destinationDir = destinationDir;
            this.referencedFiles = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            if (destinationDir != null)
            {
                if (Directory.Exists(destinationDir))
                {
                    //
                    // Get a list of existing files to avoid name conflicts due to instance names
                    // that differ only in case.
                    //

                    foreach (string existingFilePath in Directory.GetFiles(destinationDir, "*.oni"))
                    {
                        string fileName = Path.GetFileNameWithoutExtension(existingFilePath);
                        string instanceName = Importer.DecodeFileName(existingFilePath);

                        fileNames[fileName] = fileName;
                        instances[instanceName] = existingFilePath;
                    }
                }
                else
                {
                    Directory.CreateDirectory(destinationDir);
                }
            }

            var sourceFiles = new Dictionary<string, string>(StringComparer.Ordinal);
            string lastSourceDir = null;

            foreach (string inputFile in inputFiles)
            {
                string sourceDir = Path.GetDirectoryName(inputFile);

                if (sourceDir != lastSourceDir)
                {
                    lastSourceDir = sourceDir;
                    sourceFiles.Clear();

                    foreach (string file in Directory.GetFiles(sourceDir, "*.oni"))
                        sourceFiles[Importer.DecodeFileName(file)] = file;
                }

                GetReferencedFiles(inputFile, sourceFiles);
            }
        }

        private void GetReferencedFiles(string sourceFile, Dictionary<string, string> sourceFiles)
        {
            AddReferencedFile(sourceFile);

            var instanceFile = fileManager.OpenFile(sourceFile);

            foreach (var descriptor in instanceFile.GetPlaceholders())
            {
                string referencedSourceFile;

                if (!sourceFiles.TryGetValue(descriptor.FullName, out referencedSourceFile)
                    || referencedFiles.ContainsKey(referencedSourceFile))
                    continue;

                GetReferencedFiles(referencedSourceFile, sourceFiles);
            }
        }

        private void AddReferencedFile(string filePath)
        {
            if (referencedFiles.ContainsKey(filePath))
                return;

            string instanceName = Importer.DecodeFileName(filePath);
            string destinationFile;

            if (!instances.TryGetValue(instanceName, out destinationFile))
            {
                if (destinationDir != null)
                    destinationFile = Path.Combine(destinationDir, Importer.EncodeFileName(instanceName, fileNames) + ".oni");
            }

            referencedFiles.Add(filePath, destinationFile);
        }
    }
}
