﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace Oni.Level
{
    using Metadata;
    using Xml;

    partial class LevelImporter
    {
        private class Film
        {
            public string Name;
            public Vector3 Position;
            public float Facing;
            public float DesiredFacing;
            public float HeadFacing;
            public float HeadPitch;
            public readonly string[] Animations = new string[2];
            public int Length;
            public readonly List<FilmFrame> Frames = new List<FilmFrame>();
        }

        private class FilmFrame
        {
            public Vector2 MouseDelta;
            public InstanceMetadata.FILMKeys Keys;
            public uint Time;
        }

        private void ReadFilms(XmlReader xml, string basePath)
        {
            if (!xml.IsStartElement("Films") || xml.SkipEmpty())
                return;

            xml.ReadStartElement("Films");

            while (xml.IsStartElement())
            {
                xml.ReadStartElement("Import");
                string filePath = Path.Combine(basePath, xml.ReadElementContentAsString());
                xml.ReadEndElement();

                if (!File.Exists(filePath))
                {
                    error.WriteLine("Could not find file '{0}'", filePath);
                    continue;
                }

                string extension = Path.GetExtension(filePath);
                string name = Path.GetFileNameWithoutExtension(filePath);

                if (string.Equals(extension, ".oni", StringComparison.OrdinalIgnoreCase))
                {
                    var outputFilePath = Path.Combine(outputDirPath, name + ".oni");

                    File.Copy(filePath, outputFilePath, true);
                }
                else if (string.Equals(extension, ".dat", StringComparison.OrdinalIgnoreCase))
                {
                    var film = ReadBinFilm(filePath);

                    var datWriter = new DatWriter();
                    WriteDatFilm(datWriter, film);
                    datWriter.Write(outputDirPath);
                }
                else if (string.Equals(extension, ".xml", StringComparison.OrdinalIgnoreCase))
                {
                    var film = ReadXmlFilm(filePath);

                    var datWriter = new DatWriter();
                    WriteDatFilm(datWriter, film);
                    datWriter.Write(outputDirPath);
                }
                else
                {
                    error.WriteLine("Unsupported film file type {0}", extension);
                }
            }

            xml.ReadEndElement();
        }

        private static Film ReadBinFilm(string filePath)
        {
            string name = Path.GetFileNameWithoutExtension(filePath);

            if (name.StartsWith("FILM", StringComparison.Ordinal))
                name = name.Substring(4);

            var film = new Film();
            film.Name = name;

            using (var reader = new BinaryReader(filePath, true))
            {
                film.Animations[0] = reader.ReadString(128);
                film.Animations[1] = reader.ReadString(128);
                film.Position = reader.ReadVector3();
                film.Facing = reader.ReadSingle();
                film.DesiredFacing = reader.ReadSingle();
                film.HeadFacing = reader.ReadSingle();
                film.HeadPitch = reader.ReadSingle();
                film.Length = reader.ReadInt32();
                reader.Skip(28);
                int numFrames = reader.ReadInt32();
                film.Frames.Capacity = numFrames;

                for (int i = 0; i < numFrames; i++)
                {
                    var frame = new FilmFrame();
                    frame.MouseDelta = reader.ReadVector2();
                    frame.Keys = (InstanceMetadata.FILMKeys)reader.ReadUInt64();
                    frame.Time = reader.ReadUInt32();
                    reader.Skip(4);
                    film.Frames.Add(frame);
                }
            }

            return film;
        }

        private static Film ReadXmlFilm(string filePath)
        {
            string name = Path.GetFileNameWithoutExtension(filePath);

            if (name.StartsWith("FILM", StringComparison.Ordinal))
                name = name.Substring(4);

            var film = new Film();
            film.Name = name;

            var settings = new XmlReaderSettings {
                IgnoreWhitespace = true,
                IgnoreProcessingInstructions = true,
                IgnoreComments = true
            };

            using (var xml = XmlReader.Create(filePath, settings))
            {
                xml.ReadStartElement("Oni");

                name = xml.GetAttribute("Name");

                if (!string.IsNullOrEmpty(name))
                    film.Name = name;

                xml.ReadStartElement("FILM");

                film.Position = xml.ReadElementContentAsVector3("Position");
                film.Facing = xml.ReadElementContentAsFloat("Facing", "");
                film.DesiredFacing = xml.ReadElementContentAsFloat("DesiredFacing", "");
                film.HeadFacing = xml.ReadElementContentAsFloat("HeadFacing", "");
                film.HeadPitch = xml.ReadElementContentAsFloat("HeadPitch", "");
                film.Length = xml.ReadElementContentAsInt("FrameCount", "");
                xml.ReadStartElement("Animations");
                film.Animations[0] = xml.ReadElementContentAsString("Link", "");
                film.Animations[1] = xml.ReadElementContentAsString("Link", "");
                xml.ReadEndElement();
                xml.ReadStartElement("Frames");

                while (xml.IsStartElement())
                {
                    var frame = new FilmFrame();

                    switch (xml.LocalName)
                    {
                        case "FILMFrame":
                            xml.ReadStartElement();
                            frame.MouseDelta.X = xml.ReadElementContentAsFloat("MouseDeltaX", "");
                            frame.MouseDelta.Y = xml.ReadElementContentAsFloat("MouseDeltaY", "");
                            frame.Keys = xml.ReadElementContentAsEnum<InstanceMetadata.FILMKeys>("Keys");
                            frame.Time = (uint)xml.ReadElementContentAsInt("Frame", "");
                            xml.ReadEndElement();
                            break;

                        case "Frame":
                            xml.ReadStartElement();
                            while (xml.IsStartElement())
                            {
                                switch (xml.LocalName)
                                {
                                    case "Time":
                                        frame.Time = (uint)xml.ReadElementContentAsInt();
                                        break;
                                    case "MouseDelta":
                                        frame.MouseDelta = xml.ReadElementContentAsVector2();
                                        break;
                                    case "Keys":
                                        frame.Keys = xml.ReadElementContentAsEnum<InstanceMetadata.FILMKeys>();
                                        break;
                                }
                            }
                            xml.ReadEndElement();
                            break;
                        default:
                            xml.Skip();
                            continue;
                    }

                    film.Frames.Add(frame);

                }

                xml.ReadEndElement();
                xml.ReadEndElement();
            }

            return film;
        }

        private static void WriteDatFilm(DatWriter filmWriter, Film film)
        {
            var descriptor = filmWriter.CreateInstance(TemplateTag.FILM, film.Name);

            var animations = new ImporterDescriptor[2];

            for (int i = 0; i < animations.Length; i++)
            {
                if (!string.IsNullOrEmpty(film.Animations[i]))
                    animations[i] = filmWriter.CreateInstance(TemplateTag.TRAM, film.Animations[i]);
            }

            using (var writer = descriptor.OpenWrite())
            {
                writer.Write(film.Position);
                writer.Write(film.Facing);
                writer.Write(film.DesiredFacing);
                writer.Write(film.HeadFacing);
                writer.Write(film.HeadPitch);
                writer.Write(film.Length);
                writer.Write(animations);
                writer.Skip(12);
                writer.Write(film.Frames.Count);

                foreach (var frame in film.Frames)
                {
                    writer.Write(frame.MouseDelta);
                    writer.Write((ulong)frame.Keys);
                    writer.Write(frame.Time);
                    writer.Skip(4);
                }
            }
        }
    }
}
