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

namespace SurfacePlot3D.Statics
{
    public static class ObjectUtil
    {
        public static Point3DCollection RotateObject(Point3DCollection point3DCollection, Point3D centerPoint)
        {
            Point3DCollection newPoint3DCollection = new Point3DCollection();

            foreach (Point3D point3D in point3DCollection)
            {
                Point3D newPoint3D = ObjectUtil.PerformRotate(point3D.X, point3D.Y, point3D.Z, new Vector3D(1, 0, 0), 90, centerPoint);
                newPoint3DCollection.Add(newPoint3D);
            }

            return newPoint3DCollection;
        }

        public static Point3DCollection RotateObjectByAxis(Point3DCollection point3DCollection, String axis, Double angle, Point3D centerPoint)
        {
            Point3DCollection newPoint3DCollection = new Point3DCollection();

            foreach (Point3D point3D in point3DCollection)
            {
                switch (axis)
                {
                    case "X":
                        Point3D newPoint3Dx = PerformRotate(point3D.X, point3D.Y, point3D.Z, new Vector3D(1, 0, 0), angle, centerPoint);
                        newPoint3DCollection.Add(newPoint3Dx);
                        break;
                    case "Y":
                        Point3D newPoint3Dy = PerformRotate(point3D.X, point3D.Y, point3D.Z, new Vector3D(0, 1, 0), angle, centerPoint);
                        newPoint3DCollection.Add(newPoint3Dy);
                        break;
                    case "Z":
                        Point3D newPoint3Dz = PerformRotate(point3D.X, point3D.Y, point3D.Z, new Vector3D(0, 0, 1), angle, centerPoint);
                        newPoint3DCollection.Add(newPoint3Dz);
                        break;
                }
            }

            return newPoint3DCollection;
        }

        public static Point3D PerformRotate(Double x, Double y, Double z, Vector3D vector3D, Double angle, Point3D centerPoint)
        {
            Point3D inM = new Point3D(x, y, z);
            Matrix3D matrix3D = Matrix3D.Identity;
            if (centerPoint != new Point3D(0, 0, 0)) { }
            matrix3D.RotateAt(new Quaternion(vector3D, angle), centerPoint);
            Point3D outM = Point3D.Multiply(inM, matrix3D);
            return new Point3D(outM.X, outM.Y, outM.Z);
        }

        public static Point3DCollection TT(Point3DCollection point3DCollection, Double distance)
        {
            Point3DCollection newPoint3DCollection = new Point3DCollection();

            foreach (Point3D point3D in point3DCollection)
            {
                Point3D newPoint3D = new Point3D(point3D.X - distance, point3D.Y, point3D.Z); ;
                newPoint3DCollection.Add(newPoint3D);
            }

            return newPoint3DCollection;
        }

        public static Point3DCollection TT(Point3DCollection point3DCollection, Double distance, String axis)
        {
            Point3DCollection newPoint3DCollection = new Point3DCollection();

            foreach (Point3D point3D in point3DCollection)
            {
                switch (axis)
                {
                    case "X":
                        Point3D newPoint3DX = new Point3D(point3D.X + distance, point3D.Y, point3D.Z);
                        newPoint3DCollection.Add(newPoint3DX);
                        break;
                    case "Y":
                        Point3D newPoint3DY = new Point3D(point3D.X, point3D.Y + distance, point3D.Z);
                        newPoint3DCollection.Add(newPoint3DY);
                        break;
                    case "Z":
                        Point3D newPoint3DZ = new Point3D(point3D.X, point3D.Y, point3D.Z + distance);
                        newPoint3DCollection.Add(newPoint3DZ);
                        break;
                }

            }

            return newPoint3DCollection;
        }

        #region Mesh Transformations

        public static Point3D FindCenterPoint(List<Point3D> points)
        {
            return new Point3D(points.Average(p => p.X),
                points.Average(p => p.Y),
                points.Average(p => p.Z));
        }

        public static Point3D GetCenterPoint(MeshGeometry3D mesh)
        {
            Double x = 0.0D;
            Double y = 0.0D;
            Double z = 0.0D;

            int positions = mesh.Positions.Count;
            foreach (var m in mesh.Positions)
            {
                x += m.X;
                y += m.Y;
                z += m.Z;
            }

            return new Point3D((x == 0 ? positions : x) / positions, (y == 0 ? positions : y), (z == 0 ? positions : z) / positions);
        }

        public static Point3D CalculateCentroid(List<Point3D> verticies)
        {
            var s = new Vector3D();
            var areaTotal = 0.0;

            var p1 = verticies[0];
            var p2 = verticies[1];

            for (var i = 2; i < verticies.Count; i++)
            {
                var p3 = verticies[i];
                var edge1 = p3 - p1;
                var edge2 = p3 - p2;

                var crossProduct = Vector3D.CrossProduct(edge1, edge2);
                var area = crossProduct.Length / 2;

                s.X += area * (p1.X + p2.X + p3.X) / 3;
                s.Y += area * (p1.Y + p2.Y + p3.Y) / 3;
                s.Z += area * (p1.Z + p2.Z + p3.Z) / 3;

                areaTotal += area;
                p2 = p3;
            }

            var point = new Point3D
            {
                X = s.X / areaTotal,
                Y = s.Y / areaTotal,
                Z = s.Z / areaTotal
            };


            return point;
        }

        public static MeshGeometry3D CenterMesh(MeshGeometry3D mesh)
        {
            var minX = (from c in mesh.Positions select c).Min(c => c.X);
            var maxX = (from c in mesh.Positions select c).Max(c => c.X);

            if (Math.Abs(maxX) > Math.Abs(minX))
            {
                var movmentValue = (Math.Abs(maxX) - Math.Abs(minX)) / 2;

                Point3DCollection newPoint3DCollection = ObjectUtil.TT(mesh.Positions, movmentValue);
                return new MeshGeometry3D()
                {
                    Positions = newPoint3DCollection,
                    TriangleIndices = mesh.TriangleIndices,
                    TextureCoordinates = mesh.TextureCoordinates,
                    Normals = mesh.Normals
                };
            }
            else
            {
                var movmentValue = (Math.Abs(minX) - Math.Abs(maxX)) / 2;

                Point3DCollection newPoint3DCollection = ObjectUtil.TT(mesh.Positions, movmentValue);
                return new MeshGeometry3D()
                {
                    Positions = newPoint3DCollection,
                    TriangleIndices = mesh.TriangleIndices,
                    TextureCoordinates = mesh.TextureCoordinates,
                    Normals = mesh.Normals
                };
            }
        }

        #endregion
    }
}