using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Windows.Media.Media3D;
using System.Reflection;
using System.Diagnostics;

namespace Dialect
{
    /// <summary>
    /// This is the main form for the application that controls all 
    /// of the UI and form components of the application.
    /// </summary>
    public partial class Main : Form
    {
        public Main()
        {
            InitializeComponent();

            // Set Icon
            Icon = Properties.Resources.Icon;

            // Create Plugin Manager
            PluginManager = new PluginManager();

            // Create the main display panel.
            Display = new DisplayPanel();

            // Hookup display panel events.
            Display.DragDrop += new DragEventHandler(Display_DragDrop);
            Display.DragEnter += new DragEventHandler(Display_DragEnter);
            Display.MouseDown += new MouseEventHandler(Display_MouseDown);
            Display.PluginSelected += new EventHandler(Display_PluginSelected);
            Display.PluginDeselected += new EventHandler(Display_PluginDeselected);
            Display.Dragging += new EventHandler(Display_Dragging);

            // Allow property updates.
            syncingProperties = false;

            // Add the display to the form.
            grpDisplay.Controls.Add(Display);
            UpdateGUI();
        }

        /// <summary>
        /// Blocks certain update methods to avoid deadlock.
        /// </summary>
        bool syncingProperties;

        /// <summary>
        /// The manifest document that the application is
        /// working on. If null, no document is open.
        /// </summary>
        Manifest Manifest;

        /// <summary>
        /// The main plugin manager in charge of incoming plugins.
        /// </summary>
        PluginManager PluginManager;

        /// <summary>
        /// The main display in the interface where the plugins
        /// are displayed.
        /// </summary>
        DisplayPanel Display;

        /// <summary>
        /// Performs certain actions when the application is started.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void Main_Shown(object sender, EventArgs e)
        {
            // Get any plugins in the set directory.
            GetPlugins(Properties.Settings.Default.MosaicDirectory);

            // If none were found, ask about directory..
            if (PluginManager.Plugins.Count == 0)
            {
                if (MessageBox.Show(this, String.Format("No plugins were loaded from the preset location. Dialect needs to know the directory where the core Mosaic application is stored in order to function correctly.\n\nWould you like to set the directory now?",
                    Properties.Settings.Default.MosaicDirectory),
                    "Error Locating Plugins",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Warning) == DialogResult.Yes)
                {
                    String pluginLocation = Utils.FolderDialog(Messages.Folder_FindAppDirectory);

                    // Change the directory.
                    if (!String.IsNullOrEmpty(pluginLocation))
                    {
                        Properties.Settings.Default.MosaicDirectory = pluginLocation;
                        Properties.Settings.Default.Save();

                        // Try again to retrieve plugins.
                        GetPlugins(pluginLocation);
                    }
                    return;
                }
            }
        }

        /// <summary>
        /// Updates the rotation and position of a plugin being dragged.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void Display_Dragging(object sender, EventArgs e)
        {
            UpdateProperties(Property.Position);
            UpdateProperties(Property.Rotation);
        }

        /// <summary>
        /// Controls the link between the stored plugin attributes and the 
        /// property controls. Converts plugin values to property control
        /// values.
        /// </summary>
        /// <param name="property">The property value to update.</param>
        private void UpdateProperties(Property property)
        {
            if (Display.Active != null)
            {
                // Block updates.
                syncingProperties = true;

                switch (property)
                {
                    // Update position values.
                    case Property.Position:

                        numPositionX.Value = (decimal)Display.Active.Position.X;
                        numPositionY.Value = (decimal)Display.Active.Position.Y;
                        numPositionZ.Value = (decimal)Display.Active.Position.Z;

                        break;
                    // Update rotation values.
                    case Property.Rotation:

                        numRotationX.Value = (decimal)Display.Active.Rotation.X;
                        numRotationY.Value = (decimal)Display.Active.Rotation.Y;
                        numRotationZ.Value = (decimal)Display.Active.Rotation.Z;

                        break;
                    // Update plugin type values.
                    case Property.Type:

                        cmbType.SelectedIndex = Display.Active.Parent.ID;

                        break;
                    // Update spin values.
                    case Property.Spin:

                        Display.Active.Spin = Utils.Clamp(Display.Active.Spin, (double)numSpinX.Minimum, (double)numSpinX.Maximum);

                        numSpinX.Value = (decimal)Display.Active.Spin.X;
                        numSpinY.Value = (decimal)Display.Active.Spin.Y;
                        numSpinZ.Value = (decimal)Display.Active.Spin.Z;

                        break;
                    // Update resolution values.
                    case Property.Resolution:

                        String[] resolutionNames = Enum.GetNames(typeof(Resolution));

                        for (int i = 0; i < resolutionNames.Length; i++)
                            if (resolutionNames[i] == Display.Active.Resolution.ToString())
                            {
                                cmbResolution.SelectedIndex = i;
                                break;
                            }
                   
                        break;
                    // Update colour values.
                    case Property.Color:

                        radStaticColor.Checked = !Display.Active.CycleColor;
                        radCycleColor.Checked = Display.Active.CycleColor;
                        pnlStaticColor.Enabled = radStaticColor.Checked;

                        if (radCycleColor.Checked)
                            pnlColor.BackColor = Color.Gray;
                        else
                            pnlColor.BackColor = Display.Active.Color;

                        break;
                    // Update alpha values.
                    case Property.ColorAlpha:

                        numAlpha.Value = (decimal)Display.Active.Color.A;
                        pnlColor.BackColor = Color.FromArgb(Display.Active.Color.A, pnlColor.BackColor);

                        break;
                    // Update all values.
                    case Property.All:

                        UpdateProperties(Property.Position);
                        UpdateProperties(Property.Rotation);
                        UpdateProperties(Property.Type);
                        UpdateProperties(Property.Spin);
                        UpdateProperties(Property.Resolution);
                        UpdateProperties(Property.Color);
                        UpdateProperties(Property.ColorAlpha);

                        break;
                }

                // Reallow updates.
                syncingProperties = false;
            }
        }

        /// <summary>
        /// Controls the link between the stored plugin attributes and the 
        /// property controls. Converts property control
        /// values to plugin values.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void UpdatePlugin(object sender, EventArgs e)
        {
            // Don't update if we're updating the status properties too.
            if (!syncingProperties)
                if (Display.Active != null)
                {
                    // Set the type.
                    Display.Active.Parent = PluginManager.FindPlugin(cmbType.Items[cmbType.SelectedIndex].ToString());

                    // Set the colours.
                    Display.Active.CycleColor = radCycleColor.Checked;

                    if (!Display.Active.CycleColor)
                    {
                        Display.Active.Color = pnlColor.BackColor;
                        Display.Active.Color = Color.FromArgb((int)numAlpha.Value, Display.Active.Color);
                        pnlColor.BackColor = Color.FromArgb((int)numAlpha.Value, Display.Active.Color);
                    }

                    // Set the position.
                    Display.Active.Position = new Vector3D((double)numPositionX.Value,
                        (double)numPositionY.Value,
                        (double)numPositionZ.Value);

                    // Set the rotation.
                    Display.Active.Rotation = new Vector3D((double)numRotationX.Value,
                        (double)numRotationY.Value,
                        (double)numRotationZ.Value);

                    // Set the spin.
                    Display.Active.Spin = new Vector3D((double)numSpinX.Value,
                        (double)numSpinY.Value,
                        (double)numSpinZ.Value);

                    // Set the resolution.
                    Display.Active.Resolution = (Resolution)Enum.Parse(typeof(Resolution), cmbResolution.SelectedItem.ToString());

                    // Sort the display based on the plugin Z positions.
                    Display.Sort();

                    UpdateGUI();
                }
        }

        /// <summary>
        /// Triggered when a plugin is deselected.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void Display_PluginDeselected(object sender, EventArgs e)
        {
            // Hide property controls.
            grpSelectedPlugin.Visible = false;

            // Deselect everything from the plugin list.
            foreach (DataGridViewRow Row in lstPlugins.Rows)
                    Row.Selected = false;

            // Disable buttons.
            btnDuplicate.Enabled = false;
            btnRemove.Enabled = false;
        }

        /// <summary>
        /// Triggered when a plugin is selected.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void Display_PluginSelected(object sender, EventArgs e)
        {
            // Bail if display is cheating and nothing is selected.
            if (Display.Active == null)
                return;

            // Update all property controls.
            UpdateProperties(Property.All);

            // Show the property controls.
            grpSelectedPlugin.Visible = true;

            // Select the active plugin in the plugin list.
            foreach (DataGridViewRow Row in lstPlugins.Rows)
                if ((int)Row.Cells["colID"].Value == Display.Active.ID)
                    Row.Selected = true;

            // Enable buttons.
            btnDuplicate.Enabled = true;
            btnRemove.Enabled = true;
        }

        /// <summary>
        /// Updates all of the GUI components.
        /// </summary>
        void UpdateGUI()
        {
            // Hide objects if no manifest is active.
            pnlMain.Visible = (Manifest != null);
            mnuSaveAs.Enabled = (Manifest != null);
            mnuSave.Enabled = (Manifest != null);

            if (Manifest != null)
                mnuSave.Enabled = (!String.IsNullOrEmpty(Manifest.Path));

            // Set the back color.
            Display.BackColor = pnlManifestColor.BackColor;

            // Set the maximum values.
            numPositionZ.Maximum = (decimal)(Math.Abs(DisplayPanel.CameraZPosition) - 100);

            // Clear and recreate the plugin list.
            lstPlugins.Rows.Clear();
            for (int i = 0; i < Display.Plugins.Count; i++)
                lstPlugins.Rows.Add(Display.Plugins[i].ID, Display.Plugins[i].Name);

            // Ensure that the active plugin is selected.
            Display_PluginSelected(null, null);

            // Show the property controls if there is plugin selected.
            grpSelectedPlugin.Visible = (Display.Active != null);

            // Enable/Disable buttons.
            if (lstPlugins.Rows.Count <= 0 || lstPlugins.SelectedRows.Count <= 0)
            {
                btnDuplicate.Enabled = false;
                btnRemove.Enabled = false;
            }
            else
            {
                btnDuplicate.Enabled = true;
                btnRemove.Enabled = true;
            }

            // Set the resolution properties in the combo box.
            if (cmbResolution.Items.Count <= 0)
            {
                String[] resolutionNames = Enum.GetNames(typeof(Resolution));

                for (int i = 0; i < resolutionNames.Length; i++)
                    cmbResolution.Items.Add(resolutionNames[i]);

                cmbResolution.SelectedIndex = 0;
            }
        }

        /// <summary>
        /// Triggered whenever the mouse is clicked on the display.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void Display_MouseDown(object sender, MouseEventArgs e)
        {
            // Get the mouse position.
            Point mousePosition = new Point(e.X - Display.Size.Width / 2, e.Y - Display.Size.Height / 2);

            // If the mouse was right clicked.
            if (e.Button == MouseButtons.Right)
            {
                foreach (PluginControl Plugin in Display.Plugins)
                {
                    // If the click was on a plugin.
                    if (Plugin.InBounds(mousePosition))
                    {
                        // Select the plugin and show the context menu.
                        mnuPluginName.Text = Plugin.Parent.Name;
                        mnuPluginContext.Show(MousePosition);
                        Display.SetActive(Plugin);

                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Triggered whenever an object is dragged into the display.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void Display_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Copy;
        }

        /// <summary>
        /// Triggered whenever an object is dropped into the display.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        void Display_DragDrop(object sender, DragEventArgs e)
        {
            // Get the name from the dragging data.
            String pluginName = e.Data.GetData(DataFormats.StringFormat).ToString();

            // Create a new plugin control.
            PluginControl pluginControl = new PluginControl(Display.PluginCount, PluginManager.FindPlugin(pluginName), Display);
            
            // Set the location.
            pluginControl.SetLocation(Display.PointToClient(new Point(e.X - Display.Size.Width / 2, e.Y - Display.Size.Height / 2)));

            // Set the position offset.
            pluginControl.Offset = new PointF(Display.Size.Width / 2, Display.Size.Height / 2);

            // Add the plugin to the display and increment the total count.
            Display.Plugins.Add(pluginControl);
            Display.PluginCount++;

            // Activate the plugin.
            Display.SetActive(pluginControl);

            // Sort the display Z values.
            Display.Sort();

            // Update controls.
            UpdateGUI();
        }

        /// <summary>
        /// Imports plugins from a given path.
        /// </summary>
        /// <param name="path">The path to load the plugins from.</param>
        public void GetPlugins(String path)
        {
            // Bail if path does not exist.
            if (!Directory.Exists(path))
                return;

            // Get the folders in at the root path.
            String[] foundDirectories = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly);

            // For each folder in the path.
            for (int i = 0; i < foundDirectories.Length; i++)
            {
                try
                {
                    // Load any DLLs found in the folder.
                    PluginManager.Load(Directory.GetFiles(foundDirectories[i], "*.dll", SearchOption.TopDirectoryOnly));
                }
                catch (UnauthorizedAccessException)
                {
                    // If not allowed access to the folder, just continue on.
                    continue;
                }
            }

            // Clear some lists.
            lstAvailablePlugins.Rows.Clear();
            cmbType.Items.Clear();

            // Add the discovered plugins to the list.
            foreach (Plugin Plugin in PluginManager.Plugins)
            {
                lstAvailablePlugins.Rows.Add(Plugin.ID, Plugin.Name);
                cmbType.Items.Add(Plugin.Name);
            }
        }

        /// <summary>
        /// Resets the display update loop.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void tmrUpdate_Tick(object sender, EventArgs e)
        {
            if (Display.Active != null)
                if (Display.Active.IsSpinning)
                    UpdateProperties(Property.Rotation);

            Display.Refresh();
        }

        /// <summary>
        /// Event to remove the active plugin.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuRemovePlugin_Click(object sender, EventArgs e)
        {
            RemovePlugin();
        }

        /// <summary>
        /// Event to duplicate the active plugin.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuDuplicate_Click(object sender, EventArgs e)
        {
            DuplicatePlugin();
        }

        /// <summary>
        /// Removes the active plugin from the display.
        /// </summary>
        private void RemovePlugin()
        {
            // Only execute if there is an active plugin.
            if (Display.Active != null)
            {
                // Remove from display.
                Display.Plugins.Remove(Display.Active);
                Display.Mode = Mode.Default;
                
                UpdateGUI();

                // Deselect everything.
                Display_PluginDeselected(null, null);
            }
        }

        /// <summary>
        /// Duplicates the active plugin on the display.
        /// </summary>
        private void DuplicatePlugin()
        {
            // Only execute if there is an active plugin.
            if (Display.Active != null)
            {
                // Create a new object from the active plugin.
                PluginControl newPlugin = new PluginControl(Display.PluginCount, Display.Active.Parent, Display);

                // Set the position to zero.
                newPlugin.Position = new Vector3D(0.0, 0.0, 0.0);

                // Duplicate all of the object properties.

                newPlugin.Rotation = new Vector3D(Display.Active.Rotation.X,
                                Display.Active.Rotation.Y,
                                Display.Active.Rotation.Z);

                newPlugin.Spin = new Vector3D(Display.Active.Spin.X,
                                Display.Active.Spin.Y,
                                Display.Active.Spin.Z);

                newPlugin.Offset = Display.Active.Offset;

                newPlugin.CycleColor = Display.Active.CycleColor;
                newPlugin.Color = Display.Active.Color;

                newPlugin.Resolution = Display.Active.Resolution;

                // Add the new plugin to the display and update total count.
                Display.Plugins.Insert(0, newPlugin);
                Display.PluginCount++;

                // Make sure we're not rotating.
                Display.Mode = Mode.Default;

                // Set the new plugin as active.
                Display.SetActive(newPlugin);

                // Update UI objects.
                UpdateGUI();
            }
        }

        /// <summary>
        /// Sets the display control mode to rotation.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuRotationQuickEdit_Click(object sender, EventArgs e)
        {
            Display.Mode = Mode.Rotation;
        }

        /// <summary>
        /// Saves the current manifest.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuSave_Click(object sender, EventArgs e)
        {
            SaveManifest(Manifest.Path);
        }

        /// <summary>
        /// Updates manifest object from the interface controls.
        /// INTERFACE ---> MANIFEST
        /// </summary>
        private void UpdateManifest()
        {
            String oldPath = Manifest.Path;

            // Create new manifest.
            Manifest = new Manifest();

            // Set the title and other general settings.
            Manifest.Name = txtName.Text;
            Manifest.Path = oldPath;
            Manifest.Color = pnlManifestColor.BackColor;

            // Set the manifest plugins to the display plugins.
            Manifest.Plugins = Display.Plugins;
        }

        /// <summary>
        /// Creates a new manifest document.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuNew_Click(object sender, EventArgs e)
        {
            Manifest = new Manifest();
            ApplyManifest();
        }

        /// <summary>
        /// Finds the clicked plugin object in the available plugin list.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void lstPlugins_MouseDown(object sender, MouseEventArgs e)
        {
            // Find which plugin was clicked.
            DataGridView.HitTestInfo rowHit = lstAvailablePlugins.HitTest(e.X, e.Y);

            // Select the plugin in the list.
            if (rowHit.Type == DataGridViewHitTestType.Cell)
                ((DataGridViewRow)lstAvailablePlugins.Rows[rowHit.RowIndex]).Selected = true;

            // Add the drag-drop data (The plugin name).
            if (lstAvailablePlugins.SelectedRows.Count > 0)
                lstAvailablePlugins.DoDragDrop(lstAvailablePlugins.SelectedRows[0].Cells["colPlugin"].Value.ToString(), DragDropEffects.Copy);
        }

        /// <summary>
        /// Shows a color picker to set the active plugin color.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuColorPicker_Click(object sender, EventArgs e)
        {
            ColorDialog colorDialog = new ColorDialog();

            if (colorDialog.ShowDialog(this) != DialogResult.OK)
                return;

            // Set the active color.
            Display.Active.Color = colorDialog.Color;

            // Update the color properties.
            UpdateProperties(Property.Color);
            UpdateProperties(Property.ColorAlpha);
        }

        /// <summary>
        /// Sets the manifest background color.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void btnManifestSetColor_Click(object sender, EventArgs e)
        {
            ColorDialog colorDialog = new ColorDialog();

            if (colorDialog.ShowDialog(this) != DialogResult.OK)
                return;

            // Set the manifest color.
            pnlManifestColor.BackColor = colorDialog.Color;

            // Update UI objects.
            UpdateGUI();
        }

        /// <summary>
        /// Sets the plugin color.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void btnSetColor_Click(object sender, EventArgs e)
        {
            ColorDialog colorDialog = new ColorDialog();

            if (colorDialog.ShowDialog(this) != DialogResult.OK)
                return;

            // Set the plugin color.
            pnlColor.BackColor = colorDialog.Color;

            // Update the active plugin with the interface colors.
            UpdatePlugin(sender, e);
        }

        /// <summary>
        /// Triggered when the color radio buttons are modified.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void radColor_CheckedChanged(object sender, EventArgs e)
        {
            // Set the active plugin back color.
            if (Display.Active != null)
                if (radStaticColor.Checked)
                    pnlColor.BackColor = Display.Active.Color;

            // Update the active plugin and color interface control.
            UpdatePlugin(sender, e);
            UpdateProperties(Property.Color);
            
        }

        /// <summary>
        /// Loads a manifest file.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuLoad_Click(object sender, EventArgs e)
        {
            try
            {
                // Open a file.
                OpenFileDialog openDialog = new OpenFileDialog();
                openDialog.InitialDirectory = Properties.Settings.Default.MosaicDirectory;
                openDialog.Filter = "Mosaic Visualisation | *." + Properties.Settings.Default.VisualisationExtension;
                openDialog.Multiselect = false;

                if (openDialog.ShowDialog(this) != DialogResult.OK)
                    return;

                // Resets the selected tab to the designer.
                tabMain.SelectedTab = tabMain.TabPages["tabDesigner"];

                // Creates and reads a manifest object.
                Manifest = new Manifest();
                Manifest.Read(PluginManager, openDialog.FileName);

                bool pluginNotFound = true;

                // Make sure the visualisation doesn't contain any references to plugins Dialect doesn't know about.
                foreach (PluginControl Plugin in Manifest.Plugins)
                {
                    foreach (Plugin ExistingPlugin in PluginManager.Plugins)
                    {
                        if (ExistingPlugin.Name == Plugin.Name)
                        {
                            pluginNotFound = false;
                            break;
                        }
                    }

                    if (pluginNotFound)
                        throw new FileLoadException("The visualisation contains a reference to a plugin ('" + Plugin.Name + "')\nthat does not exist. The visualisation cannot be loaded.");
                }


                // Applies the manifest data to the interface.
                ApplyManifest();
            }
            catch (FileLoadException ef)
            {
                // Catch the exception if the manifest could not be parsed.
                MessageBox.Show(this,
                    ef.Message,
                    "Error Loading Visualisation",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
            catch (Exception ex)
            {
                // Catch the exception if the manifest could not be parsed.
                MessageBox.Show(this,
                    "The visualisation contains invalid data and could not be loaded.",
                    "Error Loading Visualisation",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Applies the manifest object settings to the interface.
        /// MANIFEST ---> INTERFACE
        /// </summary>
        void ApplyManifest()
        {
            // Clear some properties.
            Display.Plugins.Clear();
            Display.Active = null;

            // Set the name and background color.
            txtName.Text = Manifest.Name;
            pnlManifestColor.BackColor = Manifest.Color;

            // Add each plugin.
            foreach (PluginControl Plugin in Manifest.Plugins)
            {
                // Set the display and offset that couldn't be set in the plugin manager.
                Plugin.Container = Display;
                Plugin.Offset = new PointF(Display.Size.Width / 2, Display.Size.Height / 2);

                // Ensure that the values don't exceed any ranges.
                if (Plugin.Position.X > (double)numPositionX.Maximum)
                    Plugin.Position = new Vector3D((double)numPositionX.Maximum, Plugin.Position.Y, Plugin.Position.Z);

                if (Plugin.Position.X < (double)numPositionX.Minimum)
                    Plugin.Position = new Vector3D((double)numPositionX.Minimum, Plugin.Position.Y, Plugin.Position.Z);

                if (Plugin.Position.Y > (double)numPositionY.Maximum)
                    Plugin.Position = new Vector3D(Plugin.Position.Y, (double)numPositionY.Maximum, Plugin.Position.Z);

                if (Plugin.Position.Y < (double)numPositionY.Minimum)
                    Plugin.Position = new Vector3D(Plugin.Position.Y, (double)numPositionY.Minimum, Plugin.Position.Z);

                if (Plugin.Position.Z > (double)numPositionZ.Maximum)
                    Plugin.Position = new Vector3D(Plugin.Position.X, Plugin.Position.Y, (double)numPositionZ.Maximum);

                if (Plugin.Position.Z < (double)numPositionZ.Minimum)
                    Plugin.Position = new Vector3D(Plugin.Position.X, Plugin.Position.Y, (double)numPositionZ.Minimum);

                // Add and sort the plugins.
                Display.Plugins.Add(Plugin);
                Display.Sort();
            }

            // Update UI objects.
            UpdateGUI();
        }

        /// <summary>
        /// Sets the active plugin rotation to zero.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuRotationSetDefault_Click(object sender, EventArgs e)
        {
            if (Display.Active != null)
            {
                // Set to zero and update.
                Display.Active.Rotation = new Vector3D(0.0, 0.0, 0.0);
                UpdateProperties(Property.Rotation);
            }
        }

        /// <summary>
        /// Converts the design in the designer to the manifest script
        /// in the manifest editor.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void tabDesigner_Leave(object sender, EventArgs e)
        {
            // Bail if nothing is open.
            if (Manifest == null)
                return;

            // Update the manifest object from the interface and generate.
            UpdateManifest();
            Manifest.Generate();

            // Set the manifest to the generated code.
            txtManifest.Text = Manifest.Output;
        }

        /// <summary>
        /// Triggered when the plugin list is clicked to select a plugin
        /// in the designer.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void lstPlugins_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            // Get the active ID and set the active plugin.
            int activeID = (int)lstPlugins.SelectedRows[0].Cells["colID"].Value;
            Display.SetActive(activeID);
        }

        /// <summary>
        /// Closes the application.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Generates the required objects for a visualisation preview
        /// and launches Mosaic.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void btnPreview_Click(object sender, EventArgs e)
        {
            try
            {
                // Set some local values so the settings object doesn't need to be referenced all the time.
                String tempFolder = Properties.Settings.Default.PreviewFolderName;
                String tempAudio = Properties.Settings.Default.PreviewAudioName;
                String tempVis = Properties.Settings.Default.PreviewVisName;
                String tempConfig = Properties.Settings.Default.PreviewConfigName;

                // Attempts to find the Mosaicexecutable from the directory.
                String[] directoryFile = Directory.GetFiles(Properties.Settings.Default.MosaicDirectory, Properties.Settings.Default.MosaicExecutable, SearchOption.TopDirectoryOnly);
                String executablePath = String.Empty;

                // If it was found, set it locally..
                if (directoryFile.Length == 1)
                    executablePath = directoryFile[0];

                // .. if not, bail with exception.
                if (String.IsNullOrEmpty(executablePath))
                    throw new FileNotFoundException("The Mosaic executable could not be found in the application directory.", Properties.Settings.Default.MosaicDirectory);
                
                // Get the current directory from Windows.
                String currentPath = Path.GetDirectoryName(Application.ExecutablePath);

                // Find the resource stream of the sample audio file.
                Stream theResource = Assembly.GetExecutingAssembly().GetManifestResourceStream(
                    Assembly.GetExecutingAssembly().GetName().Name + ".Content.sample.mp3");

                // Create the temp folder that will hold the preview objects.
                if (!Directory.Exists(tempFolder))
                    Directory.CreateDirectory(tempFolder);

                // Write the resource to the temp folder.
                BinaryReader theReader = new BinaryReader(theResource);
                FileStream theWriter = new FileStream(String.Format("{0}\\{1}", tempFolder, tempAudio), FileMode.Create);
                byte[] theFileRead = new byte[theResource.Length];
                theResource.Read(theFileRead, 0, theFileRead.Length);
                theWriter.Write(theFileRead, 0, theFileRead.Length);
                theReader.Close();
                theWriter.Close();

                // Make sure the manifest object is updates.
                UpdateManifest();

                // Write the manifest to a visualisation in the temp folder.
                Manifest.Write(String.Format("{0}\\{1}", tempFolder, tempVis));

                // Create the config properties from the application settings.
                ConfigProperties configProperties = new ConfigProperties();
                configProperties.Size = new Size(Properties.Settings.Default.PreviewWidth,
                    Properties.Settings.Default.PreviewHeight);
                configProperties.VisualisationDirectory = String.Format("{0}\\{1}\\", currentPath, tempFolder);
                configProperties.IsFullscreen = Properties.Settings.Default.PreviewFullscreen;
                configProperties.ShowSplash = false;

                // Write the config to the temp folder.
                Manifest.CreateConfig(String.Format("{0}\\{1}", tempFolder, tempConfig), configProperties);

                Process mosaicLauncher = new Process();
                // Create the Mosaic process with the correct arguments.
                mosaicLauncher.StartInfo.FileName = executablePath;
                mosaicLauncher.StartInfo.Arguments = String.Format("\"{0}\" \"{1}\"",
                    String.Format("{0}\\{1}\\{2}", currentPath, tempFolder, tempAudio),
                    String.Format("{0}\\{1}\\{2}", currentPath, tempFolder, tempConfig));

                // Launch Mosaic.
                mosaicLauncher.Start();
            }
            catch (Exception ex)
            {
                // Catch any exceptions and bail.
                MessageBox.Show(this, String.Format(
                    "The preview failed to start because of an application exception. Ensure that the correct paths are set in the Options window. The exception that was thrown was..\n\n'{0}'", ex.Message,
                    Properties.Settings.Default.MosaicDirectory),
                    "Error Starting Preview",
                     MessageBoxButtons.OK,
                     MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Shows the Options Dialog.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuOptions_Click(object sender, EventArgs e)
        {
            // Save current Mosaic directory to check for changes.
            String oldLocation = Properties.Settings.Default.MosaicDirectory;
            Options optionsDialog = new Options();
            optionsDialog.ShowDialog(this);

            // If there are changes to the directory, attempt to load the plugins from that location.
            if (oldLocation != Properties.Settings.Default.MosaicDirectory)
                GetPlugins(Properties.Settings.Default.MosaicDirectory);
        }

        /// <summary>
        /// Deletes the temp preview folder from diskk if one was created.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (!Directory.Exists(Properties.Settings.Default.PreviewFolderName))
                return;

            // Delete the temp folder.
            Directory.Delete(Properties.Settings.Default.PreviewFolderName, true);
        }

        /// <summary>
        /// Disables the save button if the manifest has not been saved before.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuFile_DropDownOpening(object sender, EventArgs e)
        {
            if (Manifest != null)
                mnuSave.Enabled = (Manifest.Path != null);
        }

        /// <summary>
        /// Saves the manifest to a perticular file.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuSaveAs_Click(object sender, EventArgs e)
        {
            // Bail if nothing is open.
            if (Manifest == null)
                return;

            // Open the save dialog.
            SaveFileDialog saveDialog = new SaveFileDialog();
            saveDialog.InitialDirectory = Properties.Settings.Default.MosaicDirectory;
            saveDialog.Filter = "Mosaic Visualisation | *." + Properties.Settings.Default.VisualisationExtension;

            if (saveDialog.ShowDialog(this) != DialogResult.OK)
                return;

            // Save the manifest.
            SaveManifest(saveDialog.FileName);
        }

        /// <summary>
        /// Ensures that any changes made in the manifest editor are
        /// valid and aren't going the break the designer.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void tabMain_SelectedIndexChanged(object sender, EventArgs e)
        {
            // If the current tab is the designer.
            if (tabMain.SelectedTab == tabMain.TabPages["tabDesigner"])
            {
                try
                {
                    // Compile the manifest from the editor.
                    Manifest.Output = txtManifest.Text;
                    Manifest.Plugins = new List<PluginControl>();

                    Manifest.Read(PluginManager);

                    // Apply the manifest object to the interface.
                    ApplyManifest();

                    Display_PluginDeselected(null, null);
                }
                catch (Exception ex)
                {
                    // Bail if any parsing errors occurred.
                    MessageBox.Show(this,
                        "The manifest file contains invalid data and could not be\nparsed correctly. Ensure that manifest syntax is correct\nto alleviate this problem in future.\n\nThe design will be reverted to the last working version\nof the manifest document.",
                        "Error Parsing Manifest",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// Saves the manifest to a specified path.
        /// </summary>
        /// <param name="path">The location to save the manifest.</param>
        private void SaveManifest(String path)
        {
            try
            {
                if (Manifest == null)
                    return;

                // If the manifest editor is open, compile the changes.
                if (tabMain.SelectedTab == tabMain.TabPages["tabManifest"])
                {
                    Manifest.Output = txtManifest.Text;
                    Manifest.Plugins = new List<PluginControl>();

                    Manifest.Read(PluginManager);
                    ApplyManifest();
                }

                // Update the manifest with the interface.
                UpdateManifest();

                // Write the manifest.
                Manifest.Write(path);
            }
            catch (Exception e)
            {
                // Catch any errors so a manifest with invalid syntax is not saved.
                MessageBox.Show("The Manifest file contains syntax errors\nand could not be saved. Correct the errors and try again.", 
                    "Error Saving Manifest", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Triggered whenever an object is dragged into the plugin list.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void lstPlugins_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Copy;
        }

        /// <summary>
        /// Triggered whenever an object is dropped into the plugin list.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void lstPlugins_DragDrop(object sender, DragEventArgs e)
        {
            // Get the name from the dragging data.
            String pluginName = e.Data.GetData(DataFormats.StringFormat).ToString();

            // Create a new plugin control.
            PluginControl pluginControl = new PluginControl(Display.PluginCount, PluginManager.FindPlugin(pluginName), Display);
            
            // Apply location and positio noffset.
            pluginControl.SetLocation(new Point(0,0));
            pluginControl.Offset = new PointF(Display.Size.Width / 2, Display.Size.Height / 2);

            // Add the plugin to the display and increment the total count.
            Display.Plugins.Add(pluginControl);
            Display.PluginCount++;

            // Sort the plugins.
            Display.Sort();

            // UpdateUI objects.
            UpdateGUI();
        }

        /// <summary>
        /// Triggered whenever a plugin needs to be duplicated.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void btnDuplicate_Click(object sender, EventArgs e)
        {
            DuplicatePlugin();
        }

        /// <summary>
        /// Triggered whenever a plugin needs to be removed.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void btnRemove_Click(object sender, EventArgs e)
        {
            RemovePlugin();
        }

        /// <summary>
        /// Launches the about box.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuAbout_Click(object sender, EventArgs e)
        {
            About aboutDialog = new About();
            aboutDialog.ShowDialog(this);
        }

        /// <summary>
        /// Sets the active plugin to the default color.
        /// </summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event arguments.</param>
        private void mnuColorSetDefault_Click(object sender, EventArgs e)
        {
            Display.Active.Color = Color.Red;

            // Update color controls.
            UpdateProperties(Property.Color);
            UpdateProperties(Property.ColorAlpha);
        }

    }

    /// <summary>
    /// What control mode the display is in.
    /// </summary>
    public enum Mode
    {
        Default,
        Rotation,
    }

    /// <summary>
    /// The plugin resolution values.
    /// </summary>
    public enum Resolution
    {
        Simple,
        Medium,
        Detailed
    }

    /// <summary>
    /// The property control types.
    /// </summary>
    public enum Property
    {
        All,
        Type,
        Position,
        Rotation,
        Spin,
        Resolution,
        Color,
        ColorAlpha,
        Cycle
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Main());
        }
    }
}
