﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Oni
{
    internal class DaeExporter : Exporter
    {
        private readonly bool noAnimation;
        private readonly List<string> animationNames = new List<string>();
        private readonly string geometryName;
        private readonly string fileType;

        public DaeExporter(string[] args, InstanceFileManager fileManager, string outputDirPath, string fileType)
            : base(fileManager, outputDirPath)
        {
            foreach (string arg in args)
            {
                if (arg == "-noanim")
                    noAnimation = true;
                else if (arg.StartsWith("-anim:", StringComparison.Ordinal))
                    animationNames.Add(arg.Substring(6));
                else if (arg.StartsWith("-geom:", StringComparison.Ordinal))
                    geometryName = arg.Substring(6);
            }

            this.fileType = fileType;
        }

        protected override void ExportFile(string sourceFilePath)
        {
            string extension = Path.GetExtension(sourceFilePath);

            if (string.Equals(extension, ".xml", StringComparison.OrdinalIgnoreCase))
            {
                var sceneExporter = new SceneExporter(InstanceFileManager, OutputDirPath);
                sceneExporter.ExportScene(sourceFilePath);
                return;
            }

            base.ExportFile(sourceFilePath);
        }

        protected override List<InstanceDescriptor> GetSupportedDescriptors(InstanceFile file)
        {
            var descriptors = new List<InstanceDescriptor>();
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.ONCC));
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.TRBS));
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.M3GM));
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.AKEV));
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.OBAN));
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.OFGA));
            descriptors.AddRange(file.GetNamedDescriptors(TemplateTag.ONWC));
            return descriptors;
        }

        protected override void ExportInstance(InstanceDescriptor descriptor)
        {
            var tag = descriptor.Template.Tag;

            if (tag == TemplateTag.AKEV)
            {
                var mesh = Akira.AkiraDatReader.Read(descriptor);
                Akira.AkiraDaeWriter.Write(mesh, descriptor.Name, OutputDirPath, fileType);
                return;
            }

            var scene = new Dae.Scene();
            scene.Name = descriptor.Name;

            var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath);
            var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter);
            var bodyWriter = new Totoro.BodyDaeWriter(geometryWriter);

            if (tag == TemplateTag.OFGA)
                ExportObjectGeometry(descriptor, scene, geometryWriter);
            else if (tag == TemplateTag.OBAN)
                ExportObjectAnimation(descriptor, scene, geometryWriter);
            else if (tag == TemplateTag.ONCC)
                ExportCharacterBody(descriptor, scene, bodyWriter);
            else if (tag == TemplateTag.TRBS)
                ExportCharacterBodySet(descriptor, scene, bodyWriter);
            else if (tag == TemplateTag.M3GM)
                ExportGeometry(descriptor, scene, geometryWriter);
            else if (tag == TemplateTag.ONWC)
                ExportWeaponGeometry(descriptor, scene, geometryWriter);

            if (scene.Nodes.Count > 0)
            {
                string filePath = Path.Combine(OutputDirPath, descriptor.Name + "." + fileType);

                Dae.Writer.WriteFile(filePath, scene);
            }
        }

        private void ExportObjectGeometry(InstanceDescriptor descriptor, Dae.Scene scene, Motoko.GeometryDaeWriter geometryWriter)
        {
            var geometry = Physics.ObjectDatReader.ReadObjectGeometry(descriptor);
            var root = new Dae.Node();

            foreach (var objNode in geometry.Geometries)
                root.Nodes.Add(geometryWriter.WriteNode(objNode.Geometry, objNode.Geometry.Name));

            scene.Nodes.Add(root);
        }

        private void ExportObjectAnimation(InstanceDescriptor descriptor, Dae.Scene scene, Motoko.GeometryDaeWriter geometryWriter)
        {
            var animation = Physics.ObjectDatReader.ReadAnimation(descriptor);
            Dae.Node node;

            if (geometryName == "camera")
            {
                node = new Dae.Node
                {
                    Name = descriptor.Name + "_camera",
                    Instances = {
                        new Dae.CameraInstance {
                            Target = new Dae.Camera {
                                XFov = 45.0f,
                                AspectRatio = 4.0f / 3.0f,
                                ZNear = 1.0f,
                                ZFar = 10000.0f
                            }
                        }}
                };
            }
            else if (geometryName != null)
            {
                var file = InstanceFileManager.OpenFile(geometryName);

                if (file == null)
                {
                    Console.Error.WriteLine("Cannot fine file {0}", geometryName);
                    node = new Dae.Node();
                }
                else
                {
                    var geom = Motoko.GeometryDatReader.Read(file.Descriptors[0]);
                    node = geometryWriter.WriteNode(geom, geom.Name);
                }
            }
            else
            {
                node = new Dae.Node();
            }

            scene.Nodes.Add(node);

            ExportAnimation(node, new List<Physics.ObjectAnimationKey>(animation.Keys));
        }

        private void ExportGeometry(InstanceDescriptor descriptor, Dae.Scene scene, Motoko.GeometryDaeWriter geometryWriter)
        {
            var animations = new List<Physics.ObjectAnimation>(animationNames.Count);

            foreach (string animationFilePath in animationNames)
            {
                var file = InstanceFileManager.OpenFile(animationFilePath);

                if (file == null)
                {
                    Console.Error.WriteLine("Cannot find animation {0}", animationFilePath);
                    continue;
                }

                animations.Add(Physics.ObjectDatReader.ReadAnimation(file.Descriptors[0]));
            }

            ExportGeometry(scene, geometryWriter, descriptor, animations);
        }

        private void ExportCharacterBodySet(InstanceDescriptor descriptor, Dae.Scene scene, Totoro.BodyDaeWriter bodyWriter)
        {
            var body = Totoro.BodyDatReader.Read(descriptor);
            var node = bodyWriter.Write(body, noAnimation, null);

            scene.Nodes.Add(node);
        }

        private void ExportCharacterBody(InstanceDescriptor descriptor, Dae.Scene scene, Totoro.BodyDaeWriter bodyWriter)
        {
            var animationName = animationNames.Count > 0 ? animationNames[0] : null;
            var characterClass = Game.CharacterClass.Read(descriptor, animationName);

            var body = Totoro.BodyDatReader.Read(characterClass.Body);
            var textures = characterClass.Textures;
            var pelvis = bodyWriter.Write(body, noAnimation, textures);

            scene.Nodes.Add(pelvis);

            var animation = noAnimation ? null : characterClass.Animation;

            if (animation != null)
            {
                var anim = Totoro.AnimationDatReader.Read(animation);

                Totoro.AnimationDaeWriter.Write(pelvis, anim);
            }
        }

        private void ExportWeaponGeometry(InstanceDescriptor descriptor, Dae.Scene scene, Motoko.GeometryDaeWriter geometryWriter)
        {
            var weaponClass = Game.WeaponClass.Read(descriptor);

            if (weaponClass.Geometry != null)
                ExportGeometry(weaponClass.Geometry, scene, geometryWriter);
        }

        private static void ExportGeometry(Dae.Scene scene, Motoko.GeometryDaeWriter geometryWriter, InstanceDescriptor m3gm, List<Physics.ObjectAnimation> animations)
        {
            var geometry = Motoko.GeometryDatReader.Read(m3gm);

            if (animations != null && animations.Count > 0)
            {
                geometry.HasTransform = true;
                geometry.Transform = Matrix.CreateScale(animations[0].Keys[0].Scale);
            }

            var node = geometryWriter.WriteNode(geometry, m3gm.Name);
            scene.Nodes.Add(node);

            if (animations != null && animations.Count > 0)
            {
                var frames = new List<Physics.ObjectAnimationKey>();
                int offset = 0;

                foreach (var animation in animations)
                {
                    foreach (var key in animation.Keys)
                    {
                        frames.Add(new Physics.ObjectAnimationKey
                        {
                            Translation = key.Translation,
                            Rotation = key.Rotation,
                            Time = key.Time + offset
                        });
                    }

                    offset += animation.Length;
                }

                ExportAnimation(node, frames);
            }
        }

        private static void ExportAnimation(Dae.Node node, List<Physics.ObjectAnimationKey> frames)
        {
            var times = new float[frames.Count];
            var interpolations = new string[times.Length];
            var positions = new Vector3[frames.Count];
            var angles = new Vector3[frames.Count];

            for (int i = 0; i < times.Length; ++i)
                times[i] = frames[i].Time / 60.0f;

            for (int i = 0; i < interpolations.Length; i++)
                interpolations[i] = "LINEAR";

            for (int i = 0; i < frames.Count; i++)
                positions[i] = frames[i].Translation;

            for (int i = 0; i < frames.Count; i++)
                angles[i] = frames[i].Rotation.ToEulerXYZ();

            var translate = node.Transforms.Translate("translate", positions[0]);
            var rotateX = node.Transforms.Rotate("rotX", Vector3.UnitX, angles[0].X);
            var rotateY = node.Transforms.Rotate("rotY", Vector3.UnitY, angles[0].Y);
            var rotateZ = node.Transforms.Rotate("rotZ", Vector3.UnitZ, angles[0].Z);

            WriteSampler(times, interpolations, i => positions[i].X, translate, "X");
            WriteSampler(times, interpolations, i => positions[i].Y, translate, "Y");
            WriteSampler(times, interpolations, i => positions[i].Z, translate, "Z");
            WriteSampler(times, interpolations, i => angles[i].X, rotateX, "ANGLE");
            WriteSampler(times, interpolations, i => angles[i].Y, rotateY, "ANGLE");
            WriteSampler(times, interpolations, i => angles[i].Z, rotateZ, "ANGLE");
        }

        private static void WriteSampler(float[] times, string[] interpolations, Func<int, float> getValue, Dae.Transform transform, string targetName)
        {
            var values = new float[times.Length];

            for (int i = 0; i < values.Length; ++i)
                values[i] = getValue(i);

            transform.BindAnimation(targetName, new Dae.Sampler
            {
                Inputs = {
                    new Dae.Input(Dae.Semantic.Input, new Dae.Source(times, 1)),
                    new Dae.Input(Dae.Semantic.Output, new Dae.Source(values, 1)),
                    new Dae.Input(Dae.Semantic.Interpolation, new Dae.Source(interpolations, 1))
                }
            });
        }
    }
}
