using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Media.Media3D;


namespace Dialect
{
    /// <summary>
    /// The display panel is the main drag-and-drop interface that holds
    /// and displays all of the plugins in the visualisation.
    /// </summary>
    public partial class DisplayPanel : Panel
    {
        /// <summary>
        /// In Mosaic, the camera is set back by this amount. This
        /// value is set to mimic the camera position.
        /// </summary>
        public const float CameraZPosition = -450.0f;

        /// <summary>
        /// The amount of perspective on the camera.
        /// </summary>
        public const float CameraPerspective = 800.0f;

        public DisplayPanel()
        {
            // Clear the plugin list.
            Plugins = new List<PluginControl>();

            // Ensures the panel will be updated properly.
            this.SetStyle(ControlStyles.DoubleBuffer |
                        ControlStyles.UserPaint |
                        ControlStyles.AllPaintingInWmPaint,
                        true);

            // Sets some properties.
            Dock = DockStyle.Fill;
            BorderStyle = BorderStyle.Fixed3D;
            BackColor = Color.Silver;
            AllowDrop = true;
            OldSize = Size;

            // Hooks up some events.
            MouseDown += new MouseEventHandler(DisplayPanel_MouseDown);
            MouseUp += new MouseEventHandler(DisplayPanel_MouseUp);
            MouseMove += new MouseEventHandler(DisplayPanel_MouseMove);
            SizeChanged += new EventHandler(DisplayPanel_SizeChanged);

            Paint += new PaintEventHandler(DisplayPanel_Paint);

            // Sets more properties.
            PluginCount = 0;
            OldMousePosition = MousePosition;

            this.UpdateStyles();
        }

        /// <summary>
        /// Triggered when a plugin is selected.
        /// </summary>
        public event EventHandler PluginSelected;

        /// <summary>
        /// Triggered when a plugin is deselected.
        /// </summary>
        public event EventHandler PluginDeselected;

        /// <summary>
        /// Triggered when a plugin is being dragged.
        /// </summary>
        public event EventHandler Dragging;

        /// <summary>
        /// The list of plugins in the scene.
        /// </summary>
        public List<PluginControl> Plugins;

        /// <summary>
        /// The size before the display is resized.
        /// </summary>
        public Size OldSize { get; set; }

        /// <summary>
        /// The old mouse position of the last update.
        /// </summary>
        public Point OldMousePosition { get; set; }

        /// <summary>
        /// The number of plugins that have been in the scene.
        /// </summary>
        public int PluginCount { get; set; }

        /// <summary>
        /// The control mode.
        /// </summary>
        public Mode Mode { get; set; }

        /// <summary>
        /// If the mouse is down or not.
        /// </summary>
        public bool IsMouseDown { get; set; }

        /// <summary>
        /// The currently selected plugin.
        /// </summary>
        public PluginControl Active { get; set; }

        /// <summary>
        /// Called on every update loop, refreshes the display.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void DisplayPanel_Paint(object sender, PaintEventArgs e)
        {
            // Toggles anti-aliasing.
            if (Properties.Settings.Default.ShowAntiAliasing)
                e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            else
                e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;

            // Sets the helper grid color.
            Color gridColor = Color.Black;

            if (Utils.IsDark(BackColor))
                gridColor = Color.White;

            gridColor = Color.FromArgb(40, gridColor);

            // Draws the helper grid.
            int gridSize = 2;
            for (int i = 1; i < gridSize; i++)
            {
                e.Graphics.DrawLine(new Pen(gridColor), i * (Size.Width / gridSize), 0, i * (Size.Width / gridSize), Size.Height);
                e.Graphics.DrawLine(new Pen(gridColor), 0, i * (Size.Height / gridSize), Size.Width, i * (Size.Height / gridSize));
            }

            // Draws the plugins with the graphics object.
            foreach (PluginControl Plugin in Plugins)
                Plugin.Draw(e.Graphics);
        }

        /// <summary>
        /// Controls the freezing of plugin positions around the origin if the
        /// display is resized.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void DisplayPanel_SizeChanged(object sender, EventArgs e)
        {
            foreach (PluginControl Plugin in Plugins)
                Plugin.Offset = new PointF(Plugin.Offset.X + (Size.Width - OldSize.Width) / 2,
                    Plugin.Offset.Y + (Size.Height - OldSize.Height) / 2);

            OldSize = Size;
        }
        
        /// <summary>
        /// Sets a specified plugin as active.
        /// </summary>
        /// <param name="plugin">The plugin to select.</param>
        public void SetActive(PluginControl plugin)
        {
            // Deselect everything.
            foreach (PluginControl Plugin in Plugins)
                Plugin.IsSelected = false;

            // Bail if the plugin doesn't exist.
            if (plugin == null)
            {
                PluginDeselected(this, new EventArgs());
                Active = null;
                return;
            }

            // Select the plugin.
            plugin.IsSelected = true;
            Active = plugin;
            PluginSelected(this, new EventArgs());
        }

        /// <summary>
        /// Sets a specified plugin as active from an ID.
        /// </summary>
        /// <param name="plugin">The plugin ID to select.</param>
        public void SetActive(int id)
        {
            foreach (PluginControl Plugin in Plugins)
                if (Plugin.ID == id)
                {
                    SetActive(Plugin);
                    return;
                }
        }

        /// <summary>
        /// Sorts the plugins in the list by Z position.
        /// </summary>
        public void Sort()
        {
            Plugins.Sort(delegate(PluginControl p1, PluginControl p2)
            {
                return p1.Position.Z.CompareTo(p2.Position.Z);
            });
        }

        /// <summary>
        /// Controls the display when the mouse is moving.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void DisplayPanel_MouseMove(object sender, MouseEventArgs e)
        {
            // Get the mouse position.
            Point mousePosition = new Point(e.X - Size.Width / 2, e.Y - Size.Height / 2);

            switch (Mode)
            {
                    // If rotating a plugin.
                case Mode.Rotation:

                    if (IsMouseDown)
                    {
                        // Bail if nothing is active.
                        if (Active == null)
                            return;

                        // Left button down.
                        if (e.Button == MouseButtons.Left)
                        {
                            // Sets X and Y rotation from difference in current and new mouse.
                            Active.Rotation = new Vector3D(Active.Rotation.X + (MousePosition.Y - OldMousePosition.Y), 
                               Active.Rotation.Y + (OldMousePosition.X - MousePosition.X), Active.Rotation.Z);
                        } 
                            // Middle button down.
                        else if (e.Button == MouseButtons.Middle)
                        {
                            // Sets Z rotation from difference in current and new mouse.
                            Active.Rotation = new Vector3D(Active.Rotation.X, Active.Rotation.Y, 
                               Active.Rotation.Z + (MousePosition.X - OldMousePosition.X));
                        }

                        // Clamps the rotation between 0 and 360.
                        Active.Rotation = Utils.Clamp(Active.Rotation, 0.0, 360.0);
                        Dragging(this, new EventArgs());

                        OldMousePosition = MousePosition;
                    }

                    break;
                    // If doing anything else.
                default:
                    if (IsMouseDown)
                    {
                        if (e.Button == MouseButtons.Left)
                        {
                            // Drag the selected plugin to the mouse location.
                            foreach (PluginControl Plugin in Plugins)
                                if (Plugin.IsSelected)
                                    Plugin.SetLocation(new Point(mousePosition.X + Plugin.DragOffset.X, mousePosition.Y + Plugin.DragOffset.Y));

                            Dragging(this, new EventArgs());
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Triggered on MouseUp; Sets the mouse down switch to false.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void DisplayPanel_MouseUp(object sender, MouseEventArgs e)
        {
            IsMouseDown = false;
        }

        /// <summary>
        /// Triggered on MouseDown, controls the selection of the plugins.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void DisplayPanel_MouseDown(object sender, MouseEventArgs e)
        {
            // Get the mouse position.
            Point mousePosition = new Point(e.X - Size.Width / 2, e.Y - Size.Height / 2);

            switch (Mode)
            {
                    // If rotating a plugin.
                case Mode.Rotation:

                    if (Active == null)
                        return;

                    // Cancel rotating if the mouse is down outside plugin bounds.
                    if (e.Button == MouseButtons.Left)
                    {
                        if (!Active.InBounds(mousePosition))
                        {
                            SetActive(null);
                            Mode = Mode.Default;
                        }
                    }
                    break;
                    // If doing anything else.
                default:
                    if (e.Button == MouseButtons.Left)
                    {
                        SetActive(null);

                        List<PluginControl> SelectablePlugins = new List<PluginControl>();
                        PluginControl SelectPlugin = null;

                        // Find a list of plugins that are in the click bounds.
                        foreach (PluginControl Plugin in Plugins)
                            if (Plugin.InBounds(mousePosition))
                                SelectablePlugins.Add(Plugin);

                        // Bail if nothing is found.
                        if (SelectablePlugins.Count > 0)
                            SelectPlugin = SelectablePlugins[0];
                        else
                            return;

                        // Select the closest plugin to the camera.
                        if (SelectablePlugins.Count > 1)
                            foreach (PluginControl Plugin in SelectablePlugins)
                                if (Plugin.Position.Z > SelectPlugin.Position.Z)
                                    SelectPlugin = Plugin;
                        
                        // Set the plugin position in 2D space.
                        Point pluginPosition = Utils.To2D(Utils.PositionWithCameraZ(SelectPlugin.Position));
                        SelectPlugin.DragOffset = new Point(pluginPosition.X - mousePosition.X, pluginPosition.Y - mousePosition.Y);

                        SelectPlugin.SetLocation(new Point(mousePosition.X + SelectPlugin.DragOffset.X, mousePosition.Y + SelectPlugin.DragOffset.Y));
                        
                        // Set the plugin as active.
                        SetActive(SelectPlugin);
                    }
                    break;
            }

            IsMouseDown = true;
        }
    }
}
