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

namespace Dialect
{
    /// <summary>
    /// The 3D representation of the plugin control.
    /// </summary>
    public class Model
    {
        public Model()
        {
            // Reset lists.
            Facets = new List<Facet>();

            Position = new Vector3D();
            Rotation = new Vector3D();

            CreateModel();
        }

        /// <summary>
        /// The position in 3D space.
        /// </summary>
        public Vector3D Position { get; set; }

        /// <summary>
        /// The rotation in 3D space.
        /// </summary>
        public Vector3D Rotation { get; set; }

        /// <summary>
        /// The plugin draw color.
        /// </summary>
        public Color Color { get; set; }

        /// <summary>
        /// The scaling factor.
        /// </summary>
        public Vector3D Scale { get; set; }

        /// <summary>
        /// If the plugin is selected.
        /// </summary>
        public bool IsSelected { get; set; }
        
        /// <summary>
        /// THe vertex data.
        /// </summary>
        public Point3D[] Vertices { get; set; }

        /// <summary>
        /// The index (or facet) data.
        /// </summary>
        public List<Facet> Facets { get; set; }

        /// <summary>
        /// The offset from the mouse position.
        /// </summary>
        public PointF Offset { get; set; }

        /// <summary>
        /// Creates the vertex and facet data for the plugin model.
        /// </summary>
        public void CreateModel()
        {
            // Creates the vertex data of the plugin model.
            SetVertices(
                new Point3D(-1.0, -1.0, 0.0),
                new Point3D(1.0, -1.0, 0.0),
                new Point3D(1.0, 1.0, 0.0),
                new Point3D(-1.0, 1.0, 0.0),

                new Point3D(0.0, 0.0, 1.5),

                new Point3D(0.0, 0.0, -2.0),
                new Point3D(0.0, 0.0, 1.5),

                new Point3D(-2.0, -2.0, 0.0),
                new Point3D(2.0, -2.0, 0.0),
                new Point3D(2.0, 2.0, 0.0),
                new Point3D(-2.0, 2.0, 0.0),

                new Point3D(-4.0, 0.0, 0.0),
                new Point3D(4.0, 0.0, 0.0),

                new Point3D(0.0, 4.0, 0.0),
                new Point3D(0.0, -4.0, 0.0)

                );

            // Creates the facet data and color of the plugin model.
            SetFacets(
                new Facet(Color.Gray, 2, 3, 4, 4),
                new Facet(Color.Gray, 0, 3, 4, 4),
                new Facet(Color.Gray, 1, 2, 4, 4),
                new Facet(Color.Gray, 0, 1, 2, 3),
                new Facet(Color.Red, 7, 8, 9, 10),
                new Facet(Color.FromArgb(60, Color.White), 11, 12),
                new Facet(Color.FromArgb(60, Color.White), 13, 14),
                new Facet(Color.FromArgb(60, Color.White), 5, 6));
        }

        /// <summary>
        /// Sets the vertex data from a list of 3D points.
        /// </summary>
        /// <param name="vertices">The points to set.</param>
        public void SetVertices(params Point3D[] vertices)
        {
            Vertices = vertices;
        }

        /// <summary>
        /// Sets the facet data from a list of 3D points.
        /// </summary>
        /// <param name="vertices">The points to set.</param>
        public void SetFacets(params Facet[] facets)
        {
            Facets.Clear();

            for (int i = 0; i < facets.Length; i++)
                Facets.Add(facets[i]);
        }

        /// <summary>
        /// Gets the center of the model in 3D space.
        /// </summary>
        /// <returns></returns>
        public Point3D GetCenter()
        {
            Point3D centerPoint = new Point3D(0.0, 0.0, 0.0);

            // Add all the vertices up..
            for (int i = 0; i < Vertices.Length; i++)
                centerPoint = new Point3D(centerPoint.X + Vertices[i].X, centerPoint.Y + Vertices[i].Y, centerPoint.Z + Vertices[i].Z);

            // ..and divide by the amount.
            centerPoint.X /= Vertices.Length;
            centerPoint.Y /= Vertices.Length;
            centerPoint.Z /= Vertices.Length;

            return centerPoint;
        }

        /// <summary>
        /// Renders the model to the display.
        /// </summary>
        /// <param name="graphics">The graphics device.</param>
        /// <param name="color">The color to draw the plugin.</param>
        public void Draw(Graphics graphics, Color color)
        {
            // Start off with a blank transformation matrix.
            Matrix3D Matrix = Matrix3D.Identity;

            // Create a temp list of vertices from the vertex list.
            Point3D[] vertices = new Point3D[Vertices.Length];

            for (int i = 0; i < Vertices.Length; i++)
                vertices[i] = new Point3D(Vertices[i].X, Vertices[i].Y, Vertices[i].Z);

            // Apply rotation transformations.
            Matrix.Rotate(new Quaternion(new Vector3D(1.0, 0.0, 0.0), Rotation.X));
            Matrix.Rotate(new Quaternion(new Vector3D(0.0, 1.0, 0.0), Rotation.Y));
            Matrix.Rotate(new Quaternion(new Vector3D(0.0, 0.0, 1.0), Rotation.Z));

            // Apply scaling transformations.
            Matrix.Scale(Scale);

            // Apply translation transformations.
            Vector3D Viewport = new Vector3D(Position.X, Position.Y, Position.Z + DisplayPanel.CameraZPosition);

            Matrix.Translate(Viewport);

            // Transform the vertices.
            Matrix.Transform(vertices);
            
            // Draw the edges of the model.
            foreach (Facet Facet in Facets)
            {
                for (int i = 0; i < Facet.Indices.Count; i++)
                {
                    int toPoint = 0;

                    // If this is the last index, connect to the first.
                    if (i < Facet.Indices.Count - 1)
                        toPoint = i + 1;

                    // Compute projection transformation to find start and end points in 2D space.
                    PointF startPoint = Utils.To2DF(vertices[Facet.Indices[i]]);
                    PointF endPoint = Utils.To2DF(vertices[Facet.Indices[toPoint]]);

                    // Draw the glow effect if the plugin is selected.
                    if (IsSelected)
                    {
                        graphics.DrawLine(new Pen(Color.FromArgb(40, color), 5f),
                            new PointF(startPoint.X + Offset.X, startPoint.Y + Offset.Y),
                            new PointF(endPoint.X + Offset.X, endPoint.Y + Offset.Y));

                        graphics.DrawLine(new Pen(Color.FromArgb(20, color), 10f),
                            new PointF(startPoint.X + Offset.X, startPoint.Y + Offset.Y),
                            new PointF(endPoint.X + Offset.X, endPoint.Y + Offset.Y));

                        graphics.DrawLine(new Pen(Color.FromArgb(10, color), 20f),
                            new PointF(startPoint.X + Offset.X, startPoint.Y + Offset.Y),
                            new PointF(endPoint.X + Offset.X, endPoint.Y + Offset.Y));

                        graphics.DrawLine(new Pen(Color.FromArgb(5, color), 40f),
                            new PointF(startPoint.X + Offset.X, startPoint.Y + Offset.Y),
                            new PointF(endPoint.X + Offset.X, endPoint.Y + Offset.Y));
                    }

                    // Draw the edge.
                    graphics.DrawLine(new Pen(color, 1.8f),
                        new PointF(startPoint.X + Offset.X, startPoint.Y + Offset.Y),
                        new PointF(endPoint.X + Offset.X, endPoint.Y + Offset.Y));
                }
            }

            // Draw the facets of the model.
            foreach (Facet Facet in Facets)
            {
                PointF[] facetPoints = new PointF[Facet.Indices.Count];

                for (int i = 0; i < Facet.Indices.Count; i++)
                {
                    int toPoint = 0;

                    // If this is the last index, connect to the first.
                    if (i < Facet.Indices.Count - 1)
                        toPoint = i + 1;

                    // Compute projection transformation to find facet point in 2D space.
                    PointF facetPoint = Utils.To2DF(vertices[Facet.Indices[toPoint]]);

                    facetPoints[i] = new PointF(facetPoint.X + Offset.X, facetPoint.Y + Offset.Y);
                }

                // Draw the filled facet.
                graphics.FillPolygon(new SolidBrush(Color.FromArgb((int)(Color.A/2.0f), Color)), facetPoints);
            }
        }
    }

    /// <summary>
    /// Defines the properties of a facet, including its
    /// indices and color.
    /// </summary>
    public class Facet
    {
        public Facet()
        {
            Indices = new List<int>();
        }

        public Facet(Color color, params int[] vertices)
        {
            Color = color;
            Indices = new List<int>();

            // Add the indices to the index list.
            for (int i = 0; i < vertices.Length; i++)
                Indices.Add(vertices[i]);
        }

        public Facet(List<int> vertices)
        {
            Indices = vertices;
        }

        /// <summary>
        /// The list of indices.
        /// </summary>
        public List<int> Indices { get; set; }

        /// <summary>
        /// The color of the facet.
        /// </summary>
        public Color Color { get; set; }
    }
}
