﻿using System;
using System.Collections.Generic;

namespace Oni.Particles
{
    internal class Particle
    {
        #region Private data
        private ParticleFlags1 flags1;
        private ParticleFlags2 flags2;
        private SpriteType spriteType;
        private DisableDetailLevel disableDetailLevel;
        private Value lifetime;
        private Value collisionRadius;
        private float aiDodgeRadius;
        private float aiAlertRadius;
        private string flybySoundName;
        private Appearance appearance;
        private Attractor attractor;
        private List<Variable> variables;
        private List<Emitter> emitters;
        private List<Event> events;
        #endregion

        #region private struct ActionRange

        private struct ActionRange
        {
            internal ActionRange(BinaryReader reader)
            {
                First = reader.ReadUInt16();
                Last = reader.ReadUInt16();
            }

            public bool IsEmpty => First == Last;

            public int First { get; set; }
            public int Last { get; set; }
        }

        #endregion

        public Particle()
        {
            lifetime = Value.FloatZero;
            collisionRadius = Value.FloatZero;
            appearance = new Appearance();
            attractor = new Attractor();
            variables = new List<Variable>();
            emitters = new List<Emitter>();
            events = new List<Event>();
        }

        public Particle(BinaryReader reader)
            : this()
        {
            appearance = new Appearance();
            attractor = new Attractor();

            reader.Skip(8);
            flags1 = (ParticleFlags1)reader.ReadInt32();
            flags2 = (ParticleFlags2)reader.ReadInt32();
            spriteType = (SpriteType)((int)(flags1 & ParticleFlags1.SpriteModeMask) >> 5);
            disableDetailLevel = (DisableDetailLevel)((int)(flags2 & ParticleFlags2.DisableLevelMask) >> 5);
            reader.Skip(4);

            int variableCount = reader.ReadUInt16();
            int actionCount = reader.ReadUInt16();
            int emitterCount = reader.ReadUInt16();
            reader.Skip(2);

            variables = new List<Variable>(variableCount);
            emitters = new List<Emitter>(actionCount);
            events = new List<Event>(emitterCount);

            ActionRange[] eventActions = new ActionRange[16];

            for (int i = 0; i < 16; i++)
                eventActions[i] = new ActionRange(reader);

            lifetime = Value.Read(reader);
            collisionRadius = Value.Read(reader);
            aiDodgeRadius = reader.ReadSingle();
            aiAlertRadius = reader.ReadSingle();
            flybySoundName = reader.ReadString(16);
            appearance = new Appearance(reader);
            attractor = new Attractor(reader);
            reader.Skip(12);

            for (int i = 0; i < variableCount; i++)
                variables.Add(new Variable(reader));

            EventAction[] actions = new EventAction[actionCount];

            for (int i = 0; i < actionCount; i++)
                actions[i] = new EventAction(reader);

            try
            {
                for (int i = 0; i < emitterCount; i++)
                    emitters.Add(new Emitter(reader));
            }
            catch (System.IO.EndOfStreamException)
            {
                // ignore corrupted particles that contain an invalid emitter count field
            }

            for (int i = 0; i < eventActions.Length; i++)
            {
                ActionRange range = eventActions[i];

                if (!range.IsEmpty)
                    events.Add(new Event((EventType)i, actions, range.First, range.Last - range.First));
            }
        }

        public void Write(BinaryWriter writer)
        {
            List<EventAction> actions = new List<EventAction>();
            ActionRange[] ranges = new ActionRange[16];

            for (int i = 0; i < ranges.Length; i++)
            {
                Event e = events.Find(x => (int)x.Type == i);

                ActionRange range = new ActionRange();
                range.First = actions.Count;
                range.Last = actions.Count + (e == null ? 0 : e.Actions.Count);
                ranges[i] = range;

                if (e != null)
                    actions.AddRange(e.Actions);
            }

            writer.Write((int)flags1 | ((int)spriteType << 5));
            writer.Write((int)flags2 | ((int)disableDetailLevel << 5));
            writer.Skip(4);
            writer.WriteUInt16(variables.Count);
            writer.WriteUInt16(actions.Count);
            writer.WriteUInt16(emitters.Count);
            writer.WriteUInt16(256);

            for (int i = 0; i < ranges.Length; i++)
            {
                writer.WriteUInt16(ranges[i].First);
                writer.WriteUInt16(ranges[i].Last);
            }

            lifetime.Write(writer);
            collisionRadius.Write(writer);
            writer.Write(aiDodgeRadius);
            writer.Write(aiAlertRadius);
            writer.Write(flybySoundName, 16);
            appearance.Write(writer);
            attractor.Write(writer);
            writer.Skip(12);

            foreach (Variable variable in variables)
                variable.Write(writer);

            foreach (EventAction action in actions)
                action.Write(writer);

            foreach (Emitter emitter in emitters)
                emitter.Write(writer);
        }

        public ParticleFlags1 Flags1
        {
            get
            {
                return (flags1 & ~ParticleFlags1.SpriteModeMask);
            }
            set
            {
                flags1 = value;
            }
        }

        public ParticleFlags2 Flags2
        {
            get
            {
                return (flags2 & ~ParticleFlags2.DisableLevelMask);
            }
            set
            {
                flags2 = value;
            }
        }

        public SpriteType SpriteType
        {
            get
            {
                return spriteType;
            }
            set
            {
                spriteType = value;
            }
        }

        public DisableDetailLevel DisableDetailLevel
        {
            get
            {
                return disableDetailLevel;
            }
            set
            {
                disableDetailLevel = value;
            }
        }

        public string FlyBySoundName
        {
            get
            {
                return flybySoundName;
            }
            set
            {
                flybySoundName = value;
            }
        }

        public Value Lifetime
        {
            get
            {
                return lifetime;
            }
            set
            {
                lifetime = value;
            }
        }

        public Value CollisionRadius
        {
            get
            {
                return collisionRadius;
            }
            set
            {
                collisionRadius = value;
            }
        }

        public float AIDodgeRadius
        {
            get
            {
                return aiDodgeRadius;
            }
            set
            {
                aiDodgeRadius = value;
            }
        }

        public float AIAlertRadius
        {
            get
            {
                return aiAlertRadius;
            }
            set
            {
                aiAlertRadius = value;
            }
        }

        public Appearance Appearance => appearance;

        public Attractor Attractor => attractor;

        public List<Variable> Variables => variables;

        public List<Emitter> Emitters => emitters;

        public List<Event> Events => events;
    }
}
