﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace Oni.Level
{
    internal partial class LevelImporter : Importer
    {
        private readonly TextWriter info;
        private readonly TextWriter error;
        private bool debug;
        private string outputDirPath;
        private LevelDatWriter.DatLevel level;
        private string sharedPath;
        private InstanceFileManager sharedManager;
        private Dictionary<string, InstanceDescriptor> sharedCache;
        private Dictionary<string, Dae.Scene> sceneCache;

        public LevelImporter()
        {
            info = Console.Out;
            error = Console.Error;
        }

        public bool Debug
        {
            get { return debug; }
            set { debug = value; }
        }

        public override void Import(string filePath, string outputDirPath)
        {
            this.outputDirPath = outputDirPath;
            this.textureImporter = new Motoko.TextureImporter3(outputDirPath);

            Read(filePath);

            WriteLevel();
            WriteObjects();
        }

        private void Read(string filePath)
        {
            level = new LevelDatWriter.DatLevel();
            level.name = Path.GetFileNameWithoutExtension(filePath);

            string basePath = Path.GetDirectoryName(filePath);

            var settings = new XmlReaderSettings {
                IgnoreWhitespace = true,
                IgnoreProcessingInstructions = true,
                IgnoreComments = true
            };

            using (var xml = XmlReader.Create(filePath, settings))
            {
                xml.ReadStartElement("Oni");
                ReadLevel(xml, basePath);
                xml.ReadEndElement();
            }

            ImportModel(basePath);
        }

        private void ReadLevel(XmlReader xml, string basePath)
        {
            string path = xml.GetAttribute("SharedPath");

            if (!string.IsNullOrEmpty(path))
                sharedPath = Path.GetFullPath(Path.Combine(basePath, path));
            else
                sharedPath = Path.GetFullPath(Path.Combine(basePath, "classes"));

            sharedManager = new InstanceFileManager();
            sharedManager.AddSearchPath(sharedPath);

            string name = xml.GetAttribute("Name");

            if (!string.IsNullOrEmpty(name))
                level.name = name;

            xml.ReadStartElement("Level");

            ReadModel(xml, basePath);
            ReadSky(xml, basePath);
            ReadObjects(xml, basePath);
            ReadFilms(xml, basePath);
            ReadCameras(xml, basePath);

            xml.ReadEndElement();
        }

        private void WriteLevel()
        {
            BeginImport();
            LevelDatWriter.Write(this, level);
            Write(outputDirPath);
            textureImporter.Write();
        }

        private Dae.Scene LoadScene(string filePath)
        {
            if (sceneCache == null)
            {
                sceneCache = new Dictionary<string, Dae.Scene>(StringComparer.OrdinalIgnoreCase);
            }

            filePath = Path.GetFullPath(filePath);
            Dae.Scene scene;

            if (!sceneCache.TryGetValue(filePath, out scene))
            {
                scene = Dae.Reader.ReadFile(filePath);
                sceneCache.Add(filePath, scene);
            }

            return scene;
        }

        private InstanceDescriptor FindSharedInstance(TemplateTag tag, string name)
        {
            if (sharedCache == null)
                sharedCache = new Dictionary<string, InstanceDescriptor>(StringComparer.Ordinal);

            string fullName = tag.ToString() + name;
            InstanceDescriptor descriptor;

            if (!sharedCache.TryGetValue(fullName, out descriptor))
            {
                var file = sharedManager.FindInstance(fullName);

                if (file == null)
                    error.WriteLine("Could not find {0} instance {1}", tag, name);
                else if (file.Descriptors[0].Template.Tag != tag)
                    error.WriteLine("Found '{0}' but its type {1} doesn't match the expected type {2}", name, file.Descriptors[0].Template.Tag, tag);
                else
                    descriptor = file.Descriptors[0];

                sharedCache.Add(fullName, descriptor);
            }

            return descriptor;
        }
    }
}
