using HelixToolkit.Wpf;
using SurfacePlot3D.Common;
using SurfacePlot3D.Members;
using System;
using System.Collections.Generic;
using System.Windows.Input;
using System.Windows.Media.Media3D;
using System.Windows.Media;
using System.Windows;
using System.Xml.Serialization;
using SurfacePlot3D.Extensions;

namespace SurfacePlot3D.MeshGenerators
{
    public partial class BoxMeshGenerator : MeshGeneratorBase
    {
        #region Constructor(s)

        public BoxMeshGenerator()
        {
            PropertyChanged(new DependencyPropertyChangedEventArgs());
        }

        #endregion

        #region Public Structs

        public struct Fields
        {
            public const String Width = "Width";
            public const String Height = "Height";
            public const String Depth = "Depth";
            public const String Slices = "Slices";
            public const String Stacks = "Stacks";
            public const String Layers = "Layers";

            public const String Front = "Front";
            public const String Back = "Back";
            public const String Left = "Left";
            public const String Right = "Right";
            public const String Top = "Top";
            public const String Bottom = "Bottom";
        }

        #endregion

        public static IList<String> Properties = new List<String>() { "Width", "Height", "Depth", "Layers", "Slices", "Stacks" };
        private ICommand _decreaseWidthCommandIc; [XmlIgnore] public ICommand DecreaseWidthCommandIc => _decreaseWidthCommandIc ?? (_decreaseWidthCommandIc = new RelayCommand<Object>(DecreaseWidthCommandIcExecuted));
        private ICommand _increaseWidthCommandIc; [XmlIgnore] public ICommand IncreaseWidthCommandIc => _increaseWidthCommandIc ?? (_increaseWidthCommandIc = new RelayCommand<Object>(IncreaseWidthCommandIcExecuted));
        private ICommand _decreaseHeightCommandIc; [XmlIgnore] public ICommand DecreaseHeightCommandIc => _decreaseHeightCommandIc ?? (_decreaseHeightCommandIc = new RelayCommand<Object>(DecreaseHeightCommandIcExecuted));
        private ICommand _increaseHeightCommandIc; [XmlIgnore] public ICommand IncreaseHeightCommandIc => _increaseHeightCommandIc ?? (_increaseHeightCommandIc = new RelayCommand<Object>(IncreaseHeightCommandIcExecuted));
        private ICommand _decreaseDepthCommandIc; [XmlIgnore] public ICommand DecreaseDepthCommandIc => _decreaseDepthCommandIc ?? (_decreaseDepthCommandIc = new RelayCommand<Object>(DecreaseDepthCommandIcExecuted));
        private ICommand _increaseDepthCommandIc; [XmlIgnore] public ICommand IncreaseDepthCommandIc => _increaseDepthCommandIc ?? (_increaseDepthCommandIc = new RelayCommand<Object>(IncreaseDepthCommandIcExecuted));

        public void DecreaseWidthCommandIcExecuted(Object sender) { Width -= MovementValue; } //OnLastActionChanged(new LastActionEventArgs(ActionType.DecreaseBoxWidth));
        public void IncreaseWidthCommandIcExecuted(Object sender) { Width += MovementValue; } //OnLastActionChanged(new LastActionEventArgs(ActionType.IncreaseBoxWidth));
        public void DecreaseHeightCommandIcExecuted(Object sender) { Height -= MovementValue; } //OnLastActionChanged(new LastActionEventArgs(ActionType.DecreaseBoxHeight));
        public void IncreaseHeightCommandIcExecuted(Object sender) { Height += MovementValue; } //OnLastActionChanged(new LastActionEventArgs(ActionType.IncreaseBoxWidth));
        public void DecreaseDepthCommandIcExecuted(Object sender) { Depth -= MovementValue; } // OnLastActionChanged(new LastActionEventArgs(ActionType.DecreaseBoxDepth));
        public void IncreaseDepthCommandIcExecuted(Object sender) { Depth += MovementValue; } //OnLastActionChanged(new LastActionEventArgs(ActionType.IncreaseBoxDepth)); 

        public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(Fields.Width, typeof(Double), typeof(BoxMeshGenerator), new PropertyMetadata(1.0D, PropertyChanged));
        public static readonly DependencyProperty HeightProperty = DependencyProperty.Register(Fields.Height, typeof(Double), typeof(BoxMeshGenerator), new PropertyMetadata(1.0D, PropertyChanged));
        public static readonly DependencyProperty DepthProperty = DependencyProperty.Register(Fields.Depth, typeof(Double), typeof(BoxMeshGenerator), new PropertyMetadata(1.0D, PropertyChanged));
        public static readonly DependencyProperty SlicesProperty = DependencyProperty.Register(Fields.Slices, typeof(int), typeof(BoxMeshGenerator), new PropertyMetadata(1, PropertyChanged), ValidateDivisions);
        public static readonly DependencyProperty StacksProperty = DependencyProperty.Register(Fields.Stacks, typeof(int), typeof(BoxMeshGenerator), new PropertyMetadata(1, PropertyChanged), ValidateDivisions);
        public static readonly DependencyProperty LayersProperty = DependencyProperty.Register(Fields.Layers, typeof(int), typeof(BoxMeshGenerator), new PropertyMetadata(1, PropertyChanged), ValidateDivisions);

        public static readonly DependencyProperty FrontProperty = DependencyProperty.Register(Fields.Front, typeof(Boolean), typeof(BoxMeshGenerator), new PropertyMetadata(true, PropertyChanged));
        public static readonly DependencyProperty BackProperty = DependencyProperty.Register(Fields.Back, typeof(Boolean), typeof(BoxMeshGenerator), new PropertyMetadata(true, PropertyChanged));
        public static readonly DependencyProperty LeftProperty = DependencyProperty.Register(Fields.Left, typeof(Boolean), typeof(BoxMeshGenerator), new PropertyMetadata(true, PropertyChanged));
        public static readonly DependencyProperty RightProperty = DependencyProperty.Register(Fields.Right, typeof(Boolean), typeof(BoxMeshGenerator), new PropertyMetadata(true, PropertyChanged));
        public static readonly DependencyProperty TopProperty = DependencyProperty.Register(Fields.Top, typeof(Boolean), typeof(BoxMeshGenerator), new PropertyMetadata(true, PropertyChanged));
        public static readonly DependencyProperty BottomProperty = DependencyProperty.Register(Fields.Bottom, typeof(Boolean), typeof(BoxMeshGenerator), new PropertyMetadata(true, PropertyChanged));

        public BoxMeshGenerator Duplicate()
        {
            BoxMeshGenerator meshGenerator = new BoxMeshGenerator();

            meshGenerator.Width = Width;
            meshGenerator.Height = Height;
            meshGenerator.Depth = Depth;
            meshGenerator.Slices = Slices;
            meshGenerator.Stacks = Stacks;
            meshGenerator.Layers = Layers;

            meshGenerator.Front = Front;
            meshGenerator.Back = Back;
            meshGenerator.Top = Top;
            meshGenerator.Bottom = Bottom;
            meshGenerator.Left = Left;
            meshGenerator.Right = Right;

            return meshGenerator;
        }

        public Boolean Front { set { SetValue(FrontProperty, value); } get { return (Boolean)GetValue(FrontProperty); } }
        public Boolean Back { set { SetValue(BackProperty, value); } get { return (Boolean)GetValue(BackProperty); } }
        public Boolean Left { set { SetValue(LeftProperty, value); } get { return (Boolean)GetValue(LeftProperty); } }
        public Boolean Right { set { SetValue(RightProperty, value); } get { return (Boolean)GetValue(RightProperty); } }
        public Boolean Top { set { SetValue(TopProperty, value); } get { return (Boolean)GetValue(TopProperty); } }
        public Boolean Bottom { set { SetValue(BottomProperty, value); } get { return (Boolean)GetValue(BottomProperty); } }


        public Double Width { set { SetValue(WidthProperty, value); } get { return (Double)GetValue(WidthProperty); } }
        public Double Height { set { SetValue(HeightProperty, value); } get { return (Double)GetValue(HeightProperty); } }
        public Double Depth { set { SetValue(DepthProperty, value); } get { return (Double)GetValue(DepthProperty); } }
        public int Slices { set { SetValue(SlicesProperty, value); } get { return (int)GetValue(SlicesProperty); } }
        public int Stacks { set { SetValue(StacksProperty, value); } get { return (int)GetValue(StacksProperty); } }
        public int Layers { set { SetValue(LayersProperty, value); } get { return (int)GetValue(LayersProperty); } }

        static bool ValidateDivisions(object obj) { return (int)obj > 0; }

        protected override void Triangulate(DependencyPropertyChangedEventArgs args, Point3DCollection vertices, Vector3DCollection normals, Int32Collection indices, PointCollection textures)
        {
            vertices.Clear();
            normals.Clear();
            indices.Clear();
            textures.Clear();

            //BoxFaces boxFaces = BoxFaces.All;
            //      Top         Bottom      Left        Right       Front       Back  
            //All = PositiveZ | NegativeZ | NegativeY | PositiveY | PositiveX | NegativeX

            //if (Top == true) { boxFaces = boxFaces & BoxFaces.PositiveZ; } //else boxFaces = boxFaces | BoxFaces.PositiveZ;
            //if (Bottom == true) { boxFaces = boxFaces & BoxFaces.NegativeZ; } //else boxFaces = boxFaces | BoxFaces.NegativeZ;
            //if (Left == true) { boxFaces = boxFaces & BoxFaces.NegativeY; } //else boxFaces = boxFaces | BoxFaces.NegativeY;
            //if (Right == true) { boxFaces = boxFaces & BoxFaces.PositiveY; } //else boxFaces = boxFaces | BoxFaces.PositiveY;
            //if (Front == true) { boxFaces = boxFaces & BoxFaces.PositiveX; } //else boxFaces = boxFaces | BoxFaces.PositiveX;
            //if (Back == true) { boxFaces = boxFaces & BoxFaces.NegativeX; } //else boxFaces = boxFaces | BoxFaces.NegativeX;

            BoxFaces? boxFaces = null;
            if (Front) { if (boxFaces == null) boxFaces = BoxFaces.Front; else boxFaces = boxFaces.Add(BoxFaces.Front); } else if (boxFaces.Has(BoxFaces.Front)) { boxFaces.Remove(BoxFaces.Front); }
            if (Back) { if (boxFaces == null) boxFaces = BoxFaces.Back; else boxFaces = boxFaces.Add(BoxFaces.Back); } else if (boxFaces.Has(BoxFaces.Back)) { boxFaces.Remove(BoxFaces.Back); }
            if (Left) { if (boxFaces == null) boxFaces = BoxFaces.Left; else boxFaces = boxFaces.Add(BoxFaces.Left); } else if (boxFaces.Has(BoxFaces.Left)) { boxFaces.Remove(BoxFaces.Left); }
            if (Right) { if (boxFaces == null) boxFaces = BoxFaces.Right; else boxFaces = boxFaces.Add(BoxFaces.Right); } else if (boxFaces.Has(BoxFaces.Right)) { boxFaces.Remove(BoxFaces.Right); }
            if (Top) { if (boxFaces == null) boxFaces = BoxFaces.Top; else boxFaces = boxFaces.Add(BoxFaces.Top); } else if (boxFaces.Has(BoxFaces.Top)) { boxFaces.Remove(BoxFaces.Top); }
            if (Bottom) { if (boxFaces == null) boxFaces = BoxFaces.Bottom; else boxFaces = boxFaces.Add(BoxFaces.Bottom); } else if (boxFaces.Has(BoxFaces.Bottom)) { boxFaces.Remove(BoxFaces.Bottom); }

            if (boxFaces == null) boxFaces = BoxFaces.All;

            CustomMeshBuilder meshBuilder = new CustomMeshBuilder();
            meshBuilder.AddBox(new Point3D(0, 0, 0), Width, Depth, Height);

            vertices = meshBuilder.Positions;
            normals = meshBuilder.Normals;
            indices = meshBuilder.TriangleIndices;
            textures = meshBuilder.TextureCoordinates;

            Geometry = new MeshGeometry3D() { Positions = vertices, Normals = normals, TriangleIndices = indices, TextureCoordinates = textures };

            meshBuilder = null;

            OnMeshChanged(new MeshEventArgs(Geometry as MeshGeometry3D));

            //double x, y, z;
            //int indexBase = 0;

            //// Front side.
            //// -----------
            //z = Depth / 2;

            //// Fill the vertices, normals, textures collections.
            //for (int stack = 0; stack <= Stacks; stack++)
            //{
            //    y = Height / 2 - stack * Height / Stacks;

            //    for (int slice = 0; slice <= Slices; slice++)
            //    {
            //        x = -Width / 2 + slice * Width / Slices;
            //        Point3D point = new Point3D(x, y, z);
            //        vertices.Add(point);

            //        normals.Add(point - new Point3D(x, y, 0));
            //        textures.Add(new Point((double)slice / Slices,
            //                               (double)stack / Stacks));
            //    }
            //}

            //// Fill the indices collection.
            //for (int stack = 0; stack < Stacks; stack++)
            //{
            //    for (int slice = 0; slice < Slices; slice++)
            //    {
            //        indices.Add((stack + 0) * (Slices + 1) + slice);
            //        indices.Add((stack + 1) * (Slices + 1) + slice);
            //        indices.Add((stack + 0) * (Slices + 1) + slice + 1);

            //        indices.Add((stack + 0) * (Slices + 1) + slice + 1);
            //        indices.Add((stack + 1) * (Slices + 1) + slice);
            //        indices.Add((stack + 1) * (Slices + 1) + slice + 1);
            //    }
            //}

            //// Rear side.
            //// -----------
            //indexBase = vertices.Count;
            //z = -Depth / 2;

            //// Fill the vertices, normals, textures collections.
            //for (int stack = 0; stack <= Stacks; stack++)
            //{
            //    y = Height / 2 - stack * Height / Stacks;

            //    for (int slice = 0; slice <= Slices; slice++)
            //    {
            //        x = Width / 2 - slice * Width / Slices;
            //        Point3D point = new Point3D(x, y, z);
            //        vertices.Add(point);

            //        normals.Add(point - new Point3D(x, y, 0));
            //        textures.Add(new Point((double)slice / Slices,
            //                               (double)stack / Stacks));
            //    }
            //}

            //// Fill the indices collection.
            //for (int stack = 0; stack < Stacks; stack++)
            //{
            //    for (int slice = 0; slice < Slices; slice++)
            //    {
            //        indices.Add(indexBase + (stack + 0) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (stack + 1) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (stack + 0) * (Slices + 1) + slice + 1);

            //        indices.Add(indexBase + (stack + 0) * (Slices + 1) + slice + 1);
            //        indices.Add(indexBase + (stack + 1) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (stack + 1) * (Slices + 1) + slice + 1);
            //    }
            //}

            //// Left side.
            //// -----------
            //indexBase = vertices.Count;
            //x = -Width / 2;

            //// Fill the vertices, normals, textures collections.
            //for (int stack = 0; stack <= Stacks; stack++)
            //{
            //    y = Height / 2 - stack * Height / Stacks;

            //    for (int layer = 0; layer <= Layers; layer++)
            //    {
            //        z = -Depth / 2 + layer * Depth / Layers;
            //        Point3D point = new Point3D(x, y, z);
            //        vertices.Add(point);

            //        normals.Add(point - new Point3D(0, y, z));
            //        textures.Add(new Point((double)layer / Layers,
            //                               (double)stack / Stacks));
            //    }
            //}

            //// Fill the indices collection.
            //for (int stack = 0; stack < Stacks; stack++)
            //{
            //    for (int layer = 0; layer < Layers; layer++)
            //    {
            //        indices.Add(indexBase + (stack + 0) * (Layers + 1) + layer);
            //        indices.Add(indexBase + (stack + 1) * (Layers + 1) + layer);
            //        indices.Add(indexBase + (stack + 0) * (Layers + 1) + layer + 1);

            //        indices.Add(indexBase + (stack + 0) * (Layers + 1) + layer + 1);
            //        indices.Add(indexBase + (stack + 1) * (Layers + 1) + layer);
            //        indices.Add(indexBase + (stack + 1) * (Layers + 1) + layer + 1);
            //    }
            //}

            //// Right side.
            //// -----------
            //indexBase = vertices.Count;
            //x = Width / 2;

            //// Fill the vertices, normals, textures collections.
            //for (int stack = 0; stack <= Stacks; stack++)
            //{
            //    y = Height / 2 - stack * Height / Stacks;

            //    for (int layer = 0; layer <= Layers; layer++)
            //    {
            //        z = Depth / 2 - layer * Depth / Layers;
            //        Point3D point = new Point3D(x, y, z);
            //        vertices.Add(point);

            //        normals.Add(point - new Point3D(0, y, z));
            //        textures.Add(new Point((double)layer / Layers,
            //                               (double)stack / Stacks));
            //    }
            //}

            //// Fill the indices collection.
            //for (int stack = 0; stack < Stacks; stack++)
            //{
            //    for (int layer = 0; layer < Layers; layer++)
            //    {
            //        indices.Add(indexBase + (stack + 0) * (Layers + 1) + layer);
            //        indices.Add(indexBase + (stack + 1) * (Layers + 1) + layer);
            //        indices.Add(indexBase + (stack + 0) * (Layers + 1) + layer + 1);

            //        indices.Add(indexBase + (stack + 0) * (Layers + 1) + layer + 1);
            //        indices.Add(indexBase + (stack + 1) * (Layers + 1) + layer);
            //        indices.Add(indexBase + (stack + 1) * (Layers + 1) + layer + 1);
            //    }
            //}

            //// Top side.
            //// -----------
            //indexBase = vertices.Count;
            //y = Height / 2;

            //// Fill the vertices, normals, textures collections.
            //for (int layer = 0; layer <= Layers; layer++)
            //{
            //    z = -Depth / 2 + layer * Depth / Layers;

            //    for (int slice = 0; slice <= Slices; slice++)
            //    {
            //        x = -Width / 2 + slice * Width / Slices;
            //        Point3D point = new Point3D(x, y, z);
            //        vertices.Add(point);

            //        normals.Add(point - new Point3D(x, 0, z));
            //        textures.Add(new Point((double)slice / Slices,
            //                               (double)layer / Layers));
            //    }
            //}

            //// Fill the indices collection.
            //for (int layer = 0; layer < Layers; layer++)
            //{
            //    for (int slice = 0; slice < Slices; slice++)
            //    {
            //        indices.Add(indexBase + (layer + 0) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (layer + 1) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (layer + 0) * (Slices + 1) + slice + 1);

            //        indices.Add(indexBase + (layer + 0) * (Slices + 1) + slice + 1);
            //        indices.Add(indexBase + (layer + 1) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (layer + 1) * (Slices + 1) + slice + 1);
            //    }
            //}

            //// Bottom side.
            //// -----------
            //indexBase = vertices.Count;
            //y = -Height / 2;

            //// Fill the vertices, normals, textures collections.
            //for (int layer = 0; layer <= Layers; layer++)
            //{
            //    z = Depth / 2 - layer * Depth / Layers;

            //    for (int slice = 0; slice <= Slices; slice++)
            //    {
            //        x = -Width / 2 + slice * Width / Slices;
            //        Point3D point = new Point3D(x, y, z);
            //        vertices.Add(point);

            //        normals.Add(point - new Point3D(x, 0, z));
            //        textures.Add(new Point((double)slice / Slices,
            //                               (double)layer / Layers));
            //    }
            //}

            //// Fill the indices collection.
            //for (int layer = 0; layer < Layers; layer++)
            //{
            //    for (int slice = 0; slice < Slices; slice++)
            //    {
            //        indices.Add(indexBase + (layer + 0) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (layer + 1) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (layer + 0) * (Slices + 1) + slice + 1);

            //        indices.Add(indexBase + (layer + 0) * (Slices + 1) + slice + 1);
            //        indices.Add(indexBase + (layer + 1) * (Slices + 1) + slice);
            //        indices.Add(indexBase + (layer + 1) * (Slices + 1) + slice + 1);
            //    }
            //}
        }
    }
}