﻿using System;
using Oni.Motoko;

namespace Oni.Totoro
{
    internal static class BodyDatReader
    {
        public static Body Read(InstanceDescriptor source)
        {
            InstanceDescriptor trcm = ReadTRCM(source);

            InstanceDescriptor trga;
            InstanceDescriptor trta;
            InstanceDescriptor tria;

            using (var reader = trcm.OpenRead(84))
            {
                trga = reader.ReadInstance();
                trta = reader.ReadInstance();
                tria = reader.ReadInstance();
            }

            var geometries = ReadTRGA(trga);
            var translations = ReadTRTA(trta);
            var indices = ReadTRIA(tria);

            var nodes = new BodyNode[geometries.Length];

            for (int i = 0; i < nodes.Length; i++)
            {
                nodes[i] = new BodyNode
                {
                    Name = BodyNode.Names[i],
                    Index = i,
                    Geometry = geometries[i],
                    Translation = translations[i],
                };
            }

            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];

                for (int j = indices[i].FirstChildIndex; j != 0; j = indices[j].SiblingIndex)
                {
                    nodes[j].Parent = node;
                    node.Nodes.Add(nodes[j]);
                }
            }

            var body = new Body();
            body.Nodes.AddRange(nodes);
            return body;
        }

        private static InstanceDescriptor ReadTRCM(InstanceDescriptor source)
        {
            if (source.Template.Tag == TemplateTag.TRCM)
                return source;

            if (source.Template.Tag == TemplateTag.ONCC)
                source = Game.CharacterClass.Read(source).Body;

            if (source.Template.Tag != TemplateTag.TRBS)
                throw new InvalidOperationException(string.Format("Invalid body source type {0}", source.Template.Tag));

            return ReadTRBS(source).Last();
        }

        private static InstanceDescriptor[] ReadTRBS(InstanceDescriptor trbs)
        {
            using (var reader = trbs.OpenRead())
                return reader.ReadInstanceArray(5);
        }

        private static Geometry[] ReadTRGA(InstanceDescriptor trga)
        {
            InstanceDescriptor[] descriptors;

            using (var reader = trga.OpenRead(22))
                descriptors = reader.ReadInstanceArray(reader.ReadInt16());

            var geometries = new Geometry[descriptors.Length];

            for (int i = 0; i < descriptors.Length; i++)
                geometries[i] = GeometryDatReader.Read(descriptors[i]);

            return geometries;
        }

        private static Vector3[] ReadTRTA(InstanceDescriptor trta)
        {
            using (var reader = trta.OpenRead(22))
                return reader.ReadVector3Array(reader.ReadInt16());
        }

        private struct NodeIndices
        {
            public byte ParentIndex;
            public byte FirstChildIndex;
            public byte SiblingIndex;
        }

        private static NodeIndices[] ReadTRIA(InstanceDescriptor tria)
        {
            using (var reader = tria.OpenRead(22))
            {
                var indices = new NodeIndices[reader.ReadInt16()];

                for (int i = 0; i < indices.Length; i++)
                {
                    indices[i].ParentIndex = reader.ReadByte();
                    indices[i].FirstChildIndex = reader.ReadByte();
                    indices[i].SiblingIndex = reader.ReadByte();
                    reader.Skip(1);
                }

                return indices;
            }
        }
    }
}
