﻿using System;
using System.Collections.Generic;
using System.Xml;
using Oni.Metadata;

namespace Oni.Objects
{
    internal class PatrolPath : ObjectBase
    {
        private string name;
        private PatrolPathPoint[] points;
        private int patrolId;
        private int returnToNearest;

        public PatrolPath()
        {
            TypeId = ObjectType.PatrolPath;
        }

        protected override void WriteOsd(BinaryWriter writer)
        {
            writer.Write(name, 32);
            writer.Write(points.Length);
            writer.WriteUInt16(patrolId);
            writer.WriteUInt16(returnToNearest);

            foreach (var point in points)
            {
                writer.Write((int)point.Type);

                switch (point.Type)
                {
                    case PatrolPathPointType.Loop:
                    case PatrolPathPointType.Stop:
                    case PatrolPathPointType.StopLooking:
                    case PatrolPathPointType.StopScanning:
                    case PatrolPathPointType.FreeFacing:
                        break;

                    case PatrolPathPointType.LoopFrom:
                        writer.Write((int)point.Attributes["From"]);
                        break;

                    case PatrolPathPointType.IgnorePlayer:
                        writer.WriteByte((byte)point.Attributes["Value"]);
                        break;

                    case PatrolPathPointType.ForkScript:
                    case PatrolPathPointType.CallScript:
                        writer.Write((short)point.Attributes["ScriptId"]);
                        break;

                    case PatrolPathPointType.MoveToFlag:
                    case PatrolPathPointType.LookAtFlag:
                    case PatrolPathPointType.MoveAndFaceFlag:
                        writer.Write((short)point.Attributes["FlagId"]);
                        break;

                    case PatrolPathPointType.MovementMode:
                        writer.Write((int)(PatrolPathMovementMode)point.Attributes["Mode"]);
                        break;

                    case PatrolPathPointType.LockFacing:
                        writer.Write((int)(PatrolPathFacing)point.Attributes["Facing"]);
                        break;

                    case PatrolPathPointType.Pause:
                        writer.Write((int)point.Attributes["Frames"]);
                        break;

                    case PatrolPathPointType.GlanceAtFlagFor:
                        writer.Write((short)point.Attributes["FlagId"]);
                        writer.Write((int)point.Attributes["Frames"]);
                        break;

                    case PatrolPathPointType.Scan:
                        writer.Write((short)point.Attributes["Frames"]);
                        writer.Write((float)point.Attributes["Rotation"]);
                        break;

                    case PatrolPathPointType.MoveThroughFlag:
                    case PatrolPathPointType.MoveNearFlag:
                        writer.Write((short)point.Attributes["FlagId"]);
                        writer.Write((float)point.Attributes["Distance"]);
                        break;

                    case PatrolPathPointType.MoveToFlagLookAndWait:
                        writer.Write((short)point.Attributes["Frames"]);
                        writer.Write((short)point.Attributes["FlagId"]);
                        writer.Write((float)point.Attributes["Rotation"]);
                        break;

                    case PatrolPathPointType.FaceToFlagAndFire:
                        writer.Write((short)point.Attributes["FlagId"]);
                        writer.Write((short)point.Attributes["Frames"]);
                        writer.Write((float)point.Attributes["Spread"]);
                        break;

                    case PatrolPathPointType.LookAtPoint:
                    case PatrolPathPointType.MoveToPoint:
                        writer.Write((Vector3)point.Attributes["Point"]);
                        break;

                    case PatrolPathPointType.MoveThroughPoint:
                        writer.Write((Vector3)point.Attributes["Point"]);
                        writer.Write((float)point.Attributes["Distance"]);
                        break;

                    default:
                        throw new NotSupportedException(string.Format("Unsupported path point type {0}", point.Type));
                }
            }
        }

        protected override void ReadOsd(BinaryReader reader)
        {
            throw new NotImplementedException();
        }

        protected override void WriteOsd(XmlWriter xml)
        {
            throw new NotImplementedException();
        }

        protected override void ReadOsd(XmlReader xml, ObjectLoadContext context)
        {
            name = xml.ReadElementContentAsString("Name", "");
            patrolId = xml.ReadElementContentAsInt("PatrolId", "");
            returnToNearest = xml.ReadElementContentAsInt("ReturnToNearest", "");
            bool isEmpty = xml.IsEmptyElement;
            xml.ReadStartElement("Points");

            if (isEmpty)
            {
                points = new PatrolPathPoint[0];
                return;
            }

            var pointList = new List<PatrolPathPoint>();
            int loopStart = -1;
            bool inLoop = false;

            for (int index = 0; xml.IsStartElement() || inLoop; index++)
            {
                if (!xml.IsStartElement())
                {
                    xml.ReadEndElement();
                    inLoop = false;
                    continue;
                }

                if (xml.LocalName == "Loop")
                {
                    if (xml.SkipEmpty())
                        continue;

                    inLoop = true;
                    loopStart = index;
                    xml.ReadStartElement();
                    continue;
                }

                var point = new PatrolPathPoint(MetaEnum.Parse<PatrolPathPointType>(xml.LocalName));

                switch (point.Type)
                {
                    case PatrolPathPointType.Stop:
                    case PatrolPathPointType.StopLooking:
                    case PatrolPathPointType.StopScanning:
                    case PatrolPathPointType.FreeFacing:
                        break;

                    case PatrolPathPointType.IgnorePlayer:
                        point.Attributes["Value"] = (byte)(xml.GetAttribute("Value") == "Yes" ? 1 : 0);
                        break;

                    case PatrolPathPointType.ForkScript:
                    case PatrolPathPointType.CallScript:
                        point.Attributes["ScriptId"] = XmlConvert.ToInt16(xml.GetAttribute("ScriptId"));
                        break;

                    case PatrolPathPointType.MoveToFlag:
                    case PatrolPathPointType.LookAtFlag:
                    case PatrolPathPointType.MoveAndFaceFlag:
                        point.Attributes["FlagId"] = XmlConvert.ToInt16(xml.GetAttribute("FlagId"));
                        break;

                    case PatrolPathPointType.MovementMode:
                        point.Attributes["Mode"] = Convert.ToInt32(MetaEnum.Parse<PatrolPathMovementMode>(xml.GetAttribute("Mode")));
                        break;

                    case PatrolPathPointType.LockFacing:
                        point.Attributes["Facing"] = Convert.ToInt32(MetaEnum.Parse<PatrolPathFacing>(xml.GetAttribute("Facing")));
                        break;

                    case PatrolPathPointType.Pause:
                        point.Attributes["Frames"] = XmlConvert.ToInt32(xml.GetAttribute("Frames"));
                        break;

                    case PatrolPathPointType.GlanceAtFlagFor:
                        point.Attributes["FlagId"] = XmlConvert.ToInt16(xml.GetAttribute("FlagId"));
                        point.Attributes["Frames"] = XmlConvert.ToInt32(xml.GetAttribute("Frames"));
                        break;

                    case PatrolPathPointType.Scan:
                        point.Attributes["Frames"] = XmlConvert.ToInt16(xml.GetAttribute("Frames"));
                        point.Attributes["Rotation"] = XmlConvert.ToSingle(xml.GetAttribute("Rotation"));
                        break;

                    case PatrolPathPointType.MoveThroughFlag:
                    case PatrolPathPointType.MoveNearFlag:
                        point.Attributes["FlagId"] = XmlConvert.ToInt16(xml.GetAttribute("FlagId"));
                        point.Attributes["Distance"] = XmlConvert.ToSingle(xml.GetAttribute("Distance"));
                        break;

                    case PatrolPathPointType.MoveToFlagLookAndWait:
                        point.Attributes["Frames"] = XmlConvert.ToInt16(xml.GetAttribute("Frames"));
                        point.Attributes["FlagId"] = XmlConvert.ToInt16(xml.GetAttribute("FlagId"));
                        point.Attributes["Rotation"] = XmlConvert.ToSingle(xml.GetAttribute("Rotation"));
                        break;

                    case PatrolPathPointType.FaceToFlagAndFire:
                        point.Attributes["FlagId"] = XmlConvert.ToInt16(xml.GetAttribute("FlagId"));
                        point.Attributes["Frames"] = XmlConvert.ToInt16(xml.GetAttribute("Frames"));
                        point.Attributes["Spread"] = XmlConvert.ToSingle(xml.GetAttribute("Spread"));
                        break;

                    case PatrolPathPointType.LookAtPoint:
                    case PatrolPathPointType.MoveToPoint:
                        point.Attributes["Point"] = new Vector3(
                            XmlConvert.ToSingle(xml.GetAttribute("X")),
                            XmlConvert.ToSingle(xml.GetAttribute("Y")),
                            XmlConvert.ToSingle(xml.GetAttribute("Z")));
                        break;

                    case PatrolPathPointType.MoveThroughPoint:
                        point.Attributes["Point"] = new Vector3(
                            XmlConvert.ToSingle(xml.GetAttribute("X")),
                            XmlConvert.ToSingle(xml.GetAttribute("Y")),
                            XmlConvert.ToSingle(xml.GetAttribute("Z")));
                        point.Attributes["Distance"] = XmlConvert.ToSingle(xml.GetAttribute("Distance"));
                        break;

                    default:
                        throw new NotSupportedException(string.Format("Unsupported path point type {0}", point.Type));
                }

                xml.Skip();
                pointList.Add(point);
            }

            xml.ReadEndElement();

            if (loopStart == 0)
            {
                pointList.Add(new PatrolPathPoint(PatrolPathPointType.Loop));
            }
            else if (loopStart > 0)
            {
                var point = new PatrolPathPoint(PatrolPathPointType.LoopFrom);
                point.Attributes["From"] = loopStart;
                pointList.Add(point);
            }

            points = pointList.ToArray();
        }
    }
}
