﻿using System;
using System.Collections.Generic;
using System.IO;
using Oni.Collections;

namespace Oni
{
    internal sealed class InstanceFileManager
    {
        private readonly List<string> searchPaths = new List<string>();
        private readonly Dictionary<string, InstanceFile> loadedFiles = new Dictionary<string, InstanceFile>(StringComparer.OrdinalIgnoreCase);
        private Dictionary<string, string> files;

        public InstanceFile OpenFile(string filePath)
        {
            InstanceFile file;

            if (!loadedFiles.TryGetValue(filePath, out file))
            {
                try
                {
                    file = InstanceFile.Read(this, filePath);
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine("Error opening file {0}: {1}", filePath, ex.Message);
                    throw;
                }

                loadedFiles.Add(filePath, file);
            }

            return file;
        }

        public List<InstanceFile> OpenDirectories(string[] dirPaths)
        {
            var files = new List<InstanceFile>();
            var seenFileNames = new Set<string>(StringComparer.Ordinal);

            Array.Reverse(dirPaths);

            foreach (string dirPath in dirPaths)
            {
                var filePaths = FindFiles(dirPath);

                foreach (string filePath in filePaths)
                {
                    if (!seenFileNames.Contains(Path.GetFileName(filePath)))
                        files.Add(OpenFile(filePath));
                }

                foreach (string filePath in filePaths)
                    seenFileNames.Add(Path.GetFileName(filePath));
            }

            return files;
        }

        public List<InstanceFile> OpenDirectory(string dirPath)
        {
            var files = new List<InstanceFile>();

            foreach (string filePath in FindFiles(dirPath))
                files.Add(OpenFile(filePath));

            return files;
        }

        public void AddSearchPath(string path)
        {
            if (Directory.Exists(path))
                searchPaths.Add(path);
        }

        public InstanceFile FindInstance(string instanceName)
        {
            if (files == null)
            {
                files = new Dictionary<string, string>(StringComparer.Ordinal);

                foreach (string searchPath in searchPaths)
                {
                    foreach (string filePath in FindFiles(searchPath))
                    {
                        string name = Importer.DecodeFileName(filePath);

                        if (!files.ContainsKey(name))
                            files[name] = filePath;
                    }
                }
            }

            string instanceFilePath;

            if (!files.TryGetValue(instanceName, out instanceFilePath))
                return null;

            var file = OpenFile(instanceFilePath);

            if (file == null)
                return null;

            var descriptor = file.Descriptors[0];

            if (file.Header.Version != InstanceFileHeader.Version32)
                return null;

            if (descriptor == null || !descriptor.HasName || descriptor.FullName != instanceName)
                return null;

            return file;
        }

        public InstanceFile FindInstance(string instanceName, InstanceFile baseFile)
        {
            if (files == null)
            {
                files = new Dictionary<string, string>(StringComparer.Ordinal);

                foreach (string filePath in FindFiles(Path.GetDirectoryName(baseFile.FilePath)))
                    files[Importer.DecodeFileName(filePath)] = filePath;

                foreach (string searchPath in searchPaths)
                {
                    foreach (string filePath in FindFiles(searchPath))
                    {
                        string name = Importer.DecodeFileName(filePath);

                        if (!files.ContainsKey(name))
                            files[name] = filePath;
                    }
                }
            }

            string instanceFilePath;

            if (!files.TryGetValue(instanceName, out instanceFilePath))
            {
                if (instanceName.Length > 4)
                    instanceName = instanceName.Substring(4);

                if (!files.TryGetValue(instanceName, out instanceFilePath))
                {
                    string level0FilePath = Path.Combine(Path.GetDirectoryName(baseFile.FilePath), "level0_Final.dat");

                    if (!File.Exists(level0FilePath))
                        return null;

                    return OpenFile(level0FilePath);
                }
            }

            var file = OpenFile(instanceFilePath);

            if (file == null || file == baseFile)
                return null;

            var descriptor = file.Descriptors[0];

            if (file.Header.Version == InstanceFileHeader.Version32)
                return file;

            if (descriptor == null || !descriptor.HasName || descriptor.FullName != instanceName)
                return null;

            return file;
        }

        private static List<string> FindFiles(string dirPath)
        {
            var files = new List<string>();

            if (Directory.Exists(dirPath))
                FindFilesRecursive(dirPath, files);

            return files;
        }

        private static void FindFilesRecursive(string dirPath, List<string> files)
        {
            files.AddRange(Directory.GetFiles(dirPath, "*.oni"));

            foreach (string childDirPath in Directory.GetDirectories(dirPath))
            {
                string name = Path.GetFileName(childDirPath);

                if (!string.Equals(name, "_noimport", StringComparison.OrdinalIgnoreCase)
                    && !string.Equals(name, "noimport", StringComparison.OrdinalIgnoreCase))
                {
                    FindFilesRecursive(childDirPath, files);
                }
            }
        }
    }
}
