﻿using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;

namespace Oni.Level
{
    using Akira;
    using Metadata;
    using Motoko;
    using Physics;
    using Xml;

    partial class LevelImporter
    {
        private void ReadPhysics(XmlReader xml, string basePath)
        {
            if (xml.SkipEmpty())
                return;

            xml.ReadStartElement("Physics");

            while (xml.IsStartElement())
                ReadObjectSetup(xml, basePath);

            xml.ReadEndElement();
        }

        private void ReadObjectSetup(XmlReader xml, string basePath)
        {
            var scriptId = -1;
            var name = xml.GetAttribute("Name");
            var position = Vector3.Zero;
            var rotation = Quaternion.Identity;
            var scale = 1.0f;
            var flags = ObjectSetupFlags.None;
            var physicsType = ObjectPhysicsType.None;
            var particles = new List<ObjectParticle>();
            var nodes = new List<ObjectNode>();
            string geometryName = null;
            string animationName = null;

            xml.ReadStartElement("Object");

            while (xml.IsStartElement())
            {
                switch (xml.LocalName)
                {
                    case "Name":
                        name = xml.ReadElementContentAsString();
                        break;
                    case "ScriptId":
                        scriptId = xml.ReadElementContentAsInt();
                        break;
                    case "Flags":
                        flags = xml.ReadElementContentAsEnum<ObjectSetupFlags>() & ~ObjectSetupFlags.InUse;
                        break;
                    case "Position":
                        position = xml.ReadElementContentAsVector3();
                        break;
                    case "Rotation":
                        rotation = xml.ReadElementContentAsEulerXYZ();
                        break;
                    case "Scale":
                        scale = xml.ReadElementContentAsFloat();
                        break;
                    case "Physics":
                        physicsType = xml.ReadElementContentAsEnum<ObjectPhysicsType>();
                        break;
                    case "Particles":
                        particles.AddRange(ReadParticles(xml, basePath));
                        break;

                    case "Geometry":
                        geometryName = xml.ReadElementContentAsString();
                        if (nodes.Count > 0)
                        {
                            error.WriteLine("Geometry cannot be used together with Import, ignoring");
                            break;
                        }
                        break;

                    case "Animation":
                        animationName = xml.ReadElementContentAsString();
                        if (nodes.Count > 0)
                        {
                            error.WriteLine("Animation cannot be used together with Import, ignoring");
                            break;
                        }
                        break;

                    case "Import":
                        if (geometryName != null || animationName != null)
                        {
                            error.WriteLine("Import cannot be used together with Geometry and Animation, ignoring");
                            break;
                        }
                        nodes.AddRange(ImportObjectGeometry(xml, basePath));
                        break;

                    default:
                        error.WriteLine("Unknown physics object element {0}", xml.LocalName);
                        xml.Skip();
                        break;
                }
            }

            xml.ReadEndElement();

            if (geometryName != null)
            {
                var m3gm = FindSharedInstance(TemplateTag.M3GM, geometryName, objectLoadContext);
                var geometry = GeometryDatReader.Read(m3gm);
                var animation = new ObjectAnimation[0];

                if (animationName != null)
                {
                    var oban = FindSharedInstance(TemplateTag.OBAN, animationName, objectLoadContext);
                    animation = new[] { ObjectDatReader.ReadAnimation(oban) };
                }

                nodes.Add(new ObjectNode(new[] { new ObjectGeometry(geometry) }) {
                    FileName = Path.GetFileName(geometryName),
                    Name = m3gm.Name,
                    ScriptId = scriptId,
                    Flags = flags,
                    Animations = animation
                });
            }

            for (int i = 0; i < nodes.Count; i++)
            {
                var node = nodes[i];

                var setup = new ObjectSetup {
                    Name = node.Name,
                    FileName = node.FileName,
                    ScriptId = scriptId++,
                    Flags = flags,
                    PhysicsType = physicsType,
                };

                setup.Particles.AddRange(particles);

                setup.Geometries = node.Geometries
                    .Where(n => (n.Flags & GunkFlags.Invisible) == 0)
                    .Select(n => n.Geometry.Name).ToArray();

                foreach (var nodeGeometry in node.Geometries.Where(g => (g.Flags & GunkFlags.Invisible) == 0))
                {
                    var writer = new DatWriter();
                    GeometryDatWriter.Write(nodeGeometry.Geometry, writer.ImporterFile);
                    writer.Write(outputDirPath);
                }

                setup.Position = position;
                setup.Orientation = rotation;
                setup.Scale = scale;
                setup.Origin = Matrix.CreateFromQuaternion(setup.Orientation)
                    * Matrix.CreateScale(setup.Scale)
                    * Matrix.CreateTranslation(setup.Position);

                foreach (var animation in node.Animations)
                {
                    if (nodes.Count > 1)
                        animation.Name += i.ToString("d2", CultureInfo.InvariantCulture);

                    if ((animation.Flags & ObjectAnimationFlags.Local) != 0)
                    {
                        //animation.Scale = Matrix.CreateScale(setup.Scale);

                        foreach (var key in animation.Keys)
                        {
                            key.Rotation = setup.Orientation * key.Rotation;
                            key.Translation += setup.Position;
                        }
                    }

                    if ((animation.Flags & ObjectAnimationFlags.AutoStart) != 0)
                    {
                        setup.Animation = animation;
                        setup.PhysicsType = ObjectPhysicsType.Animated;
                    }

                    var writer = new DatWriter();
                    ObjectDatWriter.WriteAnimation(animation, writer);
                    writer.Write(outputDirPath);
                }

                if (setup.Animation == null && node.Animations.Length > 0)
                {
                    setup.Animation = node.Animations[0];
                }

                if (setup.Animation != null)
                {
                    var frame0 = setup.Animation.Keys[0];

                    setup.Scale = frame0.Scale.X;
                    setup.Orientation = frame0.Rotation;
                    setup.Position = frame0.Translation;
                }

                level.physics.Add(setup);
            }
        }

        private IEnumerable<ObjectNode> ImportObjectGeometry(XmlReader xml, string basePath)
        {
            var filePath = xml.GetAttribute("Path");

            if (filePath == null)
                filePath = xml.GetAttribute("Url");

            var scene = LoadScene(Path.Combine(basePath, filePath));
            var animClips = new List<ObjectAnimationClip>();

            if (!xml.SkipEmpty())
            {
                xml.ReadStartElement();

                while (xml.IsStartElement())
                {
                    switch (xml.LocalName)
                    {
                        case "Animation":
                            animClips.Add(ReadAnimationClip(xml));
                            break;

                        default:
                            error.WriteLine("Unknown element {0}", xml.LocalName);
                            xml.Skip();
                            break;
                    }
                }

                xml.ReadEndElement();
            }

            var importer = new ObjectDaeImporter(textureImporter, null);
            importer.Import(scene);
            return importer.Nodes;
        }
    }
}
