using System;
using System.Xml;
using Microsoft.Xna.Framework;
using System.ComponentModel;

namespace Innovation
{
    // The base class of any component that will be used by the engine.
    // Provides Update(), Draw(), etc. methods for Engine to work with.
    public class Component
    {
        private GameScreen parent;
        private bool initialized = false;
        private bool visible = true;
        private int drawOrder = 1;
        private string name;
        private static int componentCount = 0;
        bool serialize = true;

        // The GameScreen object that owns this component
        [Category("Component")]
        public GameScreen Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        // Whether or not this component has been initialized
        [Browsable(false)]
        public bool Initialized
        {
            get { return initialized; }
        }

        // Whether or not the GameScreen that owns the component
        // should draw it
        [Category("Component")]
        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        // The draw order of the component. Lower values draw first
        [Category("Component")]
        public int DrawOrder
        {
            get { return drawOrder; }
            set
            {
                // Save a copy of the old value and set the new one
                int last = drawOrder;
                drawOrder = value;

                // Fire DrawOrderChanged
                if (DrawOrderChanged != null)
                    DrawOrderChanged(this,
                        new ComponentDrawOrderChangedEventArgs(
                            this, last, this.Parent.Components));
            }
        }

        // Component's name- should be unique
        [Category("Component")]
        public string Name
        {
            get { return name; }
            set 
            { 
                string lastName = name; 
                name = value;

                if (NameChanged != null)
                    NameChanged(this, new ComponentNameChangedEventArgs(
                        this, lastName));
            }
        }

        // Whether or not the component should be serialized. For example,
        // we wouldn't want the scene camera in the editor to be serialized
        [Browsable(false)]
        public bool Serialize
        {
            get { return serialize; }
            set { serialize = value; }
        }

        // Draw order changed event
        public event ComponentDrawOrderChangedEventHandler DrawOrderChanged;

        // Name changed event
        public event ComponentNameChangedEventHandler NameChanged;

        // This overload allows will set the parent to the default
        // GameScreen
        public Component()
        {
            InitializeComponent(Engine.DefaultScreen);
        }

        // This is called by the constructor to initialize the
        // component. This allows us to only have to override this
        // method instead of both constructors
        protected virtual void InitializeComponent(GameScreen Parent)
        {
            // Check if the engine has been initialized before setting
            // up the component, or the Engine will crash when the
            // component tries to add itself to the list.
            if (!Engine.IsInitialized)
                throw new Exception("Engine must be initialized with \"SetupEngine()\""
                    + "before components can be initialized");

            // Set the name, ++componentCount will cause the number to be increased
            // before it is added to the string, automatically incrementing for
            // each new component
            this.name = "Component " + ++componentCount + " - " + this.GetType().Name;

            // Add the component to the parent and let others know we are intialized
            Parent.Components.Add(this);
            initialized = true;
        }

        // Updates the component - This is called by the owner
        public virtual void Update()
        {
        }

        // Draws the component - This is called by the owner
        public virtual void Draw()
        {
        }

        // Unregisters the component with its parent
        public virtual void DisableComponent()
        {
            if (Parent != null)
                Parent.Components.Remove(this);
        }

        // Override ToString() to return the component's name
        public override string ToString()
        {
            return name;
        }

        // Returns a SerializationData a Serializer can use to save the state
        // of the object to an Xml file
        public SerializationData GetSerializationData(Serializer Serializer, XmlWriter Writer)
        {
            // Create a new SerializationData
            SerializationData data = new SerializationData(Serializer, Writer);

            // Add the basic Component values
            data.AddData("Component.DrawOrder", DrawOrder);
            data.AddData("Component.ParentScreen", Parent.Name);
            data.AddData("Component.Visible", Visible);
            data.AddData("Component.Name", this.Name);

            if (this is I2DComponent)
                data.AddData("Component.I2D.Rectangle", ((I2DComponent)this).Rectangle);

            if (this is I3DComponent)
            {
                I3DComponent this3D = (I3DComponent)this;

                data.AddData("Component.I3D.Position", this3D.Position);
                data.AddData("Component.I3D.Rotation", this3D.EulerRotation);
                data.AddData("Component.I3D.Scale", this3D.Scale);
            }

            // Tell the serializer that it will need to know the type of
            // component
            data.AddDependency(this.GetType());

            // Construct a ServiceData
            ServiceData sd = new ServiceData();

            // If this object is a service, find out what the
            // provider type is (the type used to look up the service)
            Type serviceType;
            if (Engine.Services.IsService(this, out serviceType))
            {
                // Tell the serializer about the provider type
                data.AddDependency(serviceType);

                // Set the data to the ServiceData
                sd.IsService = true;
                sd.Type = serviceType.FullName;
            }

            // Add the ServiceData to the SerializationData
            data.AddData("Component.ServiceData", sd);

            // Call the overridable function that allows components to provide data
            SaveSerializationData(data);

            return data;
        }

        // Reconstructs the Component from SerializationData
        public void RecieveSerializationData(SerializationData Data)
        {
            // Set the basic Component values
            this.DrawOrder = Data.GetData<int>("Component.DrawOrder");
            this.Visible = Data.GetData<bool>("Component.Visible");
            this.Name = Data.GetData<string>("Component.Name");

            if (this is I2DComponent)
                ((I2DComponent)this).Rectangle = Data.GetData<Rectangle>("Component.I2D.Rectangle");

            if (this is I3DComponent)
            {
                ((I3DComponent)this).Position = Data.GetData<Vector3>("Component.I3D.Position");
                ((I3DComponent)this).EulerRotation = Data.GetData<Vector3>("Component.I3D.Rotation");
                ((I3DComponent)this).Scale = Data.GetData<Vector3>("Component.I3D.Scale");
            }

            // Get the ServiceData from the data
            ServiceData sd = Data.GetData<ServiceData>("Component.ServiceData");

            // If the component was a service
            if (sd.IsService)
            {
                // Get the type back from the serializer
                Type t = Data.GetTypeFromDependency(sd.Type);

                // Add the service to the Engine
                if (!Engine.Services.ContainsService(t))
                    Engine.Services.AddService(t, this);
            }

            // Set the owner GameScreen
            string parent = Data.GetData<string>("Component.ParentScreen");
            this.Parent = Engine.GameScreens[parent];

            // Call the overridable function that allow components to load from data
            LoadFromSerializationData(Data);
        }

        // Overridable function to allow components to save data during serialization
        public virtual void SaveSerializationData(SerializationData Data)
        {
        }

        // Overridable function to allow components to load data during deserialization
        public virtual void LoadFromSerializationData(SerializationData Data)
        {
        }
    }

    // Event arguments for draw order change on a component
    public class ComponentDrawOrderChangedEventArgs : EventArgs
    {
        // Component that was modified
        public Component Component;

        // The old draw order
        public int LastDrawOrder;

        // The collection that owns the component
        public ComponentCollection ParentCollection;

        public ComponentDrawOrderChangedEventArgs(Component Component,
            int LastDrawOrder, ComponentCollection ParentCollection)
        {
            this.Component = Component;
            this.LastDrawOrder = LastDrawOrder;
            this.ParentCollection = ParentCollection;
        }
    }

    // Event handler for draw order change on a component
    public delegate void ComponentDrawOrderChangedEventHandler(
        object sender, ComponentDrawOrderChangedEventArgs e);

    public class ComponentNameChangedEventArgs : EventArgs
    {
        Component component;
        string lastName;
        string newName;

        public Component Component
        {
            get { return component; }
        }

        public string LastName
        {
            get { return lastName; }
        }

        public string NewName
        {
            get { return newName; }
        }

        public ComponentNameChangedEventArgs(Component Component, string LastName)
        {
            this.component = Component;
            this.lastName = LastName;
            this.newName = Component.Name;
        }
    }

    public delegate void ComponentNameChangedEventHandler(
        object sender, ComponentNameChangedEventArgs e);
}