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

namespace Dialect
{
    /// <summary>
    /// Special application message strings.
    /// </summary>
    public static class Messages
    {
        public static String Folder_FindAppDirectory = "Navigate to the Mosaic application directory. The chosen directory should be the root destination of the application, where the main executable and config files are located.";
    }

    /// <summary>
    /// Special application utilities.
    /// </summary>
    public static class Utils
    {
        /// <summary>
        /// Computes distance between two 2D points.
        /// </summary>
        /// <param name="pointA">The start point.</param>
        /// <param name="pointB">The end point.</param>
        /// <returns>The distance.</returns>
        public static double Distance(PointF pointA, PointF pointB)
        {
            return Math.Sqrt((pointA.X - pointB.X) * (pointA.X - pointB.X) + (pointA.Y - pointB.Y) * (pointA.Y - pointB.Y));
        }

        /// <summary>
        /// Computes distance between two 3D points.
        /// </summary>
        /// <param name="pointA">The start point.</param>
        /// <param name="pointB">The end point.</param>
        /// <returns>The distance.</returns>
        public static double Distance(Point3D pointA, Point3D pointB)
        {
            return Math.Sqrt((pointA.X - pointB.X) * (pointA.X - pointB.X) + (pointA.Y - pointB.Y) * (pointA.Y - pointB.Y) + (pointA.Z - pointB.Z) * (pointA.Z - pointB.Z));
        }

        /// <summary>
        /// Converts Point3D to Vector3D.
        /// </summary>
        /// <param name="point">The Point3D to convert.</param>
        /// <returns>The output Vector3D.</returns>
        public static Vector3D P2V(Point3D point)
        {
            return new Vector3D(point.X, point.Y, point.Z);
        }

        /// <summary>
        /// Converts Vector3D to Point3D.
        /// </summary>
        /// <param name="point">The Vector3D to convert.</param>
        /// <returns>The output Point3D.</returns>
        public static Point3D V2P(Vector3D vector)
        {
            return new Point3D(vector.X, vector.Y, vector.Z);
        }

        /// <summary>
        /// Clamps a vector to certain ranges.
        /// </summary>
        /// <param name="vector">The Vector3D to clamp.</param>
        /// <param name="lowerRange">The lower bound.</param>
        /// <param name="upperRange">The upper bound.</param>
        /// <returns>The clamped Vector3D.</returns>
        public static Vector3D Clamp(Vector3D vector, double lowerRange, double upperRange)
        {
            if (vector.X > upperRange)
                vector.X = lowerRange;
            if (vector.X < lowerRange)
                vector.X = upperRange;

            if (vector.Y > upperRange)
                vector.Y = lowerRange;
            if (vector.Y < lowerRange)
                vector.Y = upperRange;

            if (vector.Z > upperRange)
                vector.Z = lowerRange;
            if (vector.Z < lowerRange)
                vector.Z = upperRange;

            return vector;
        }

        /// <summary>
        /// Snaps a Vector3D to a specific axis.
        /// </summary>
        /// <param name="vector">The Vector3D to snap.</param>
        /// <param name="axis">The axis to snap to.</param>
        /// <param name="distance">The snapping distance.</param>
        /// <returns>The snapped Vector3D.</returns>
        public static Vector3D Snap(Vector3D vector, double axis, double distance)
        {
            if (vector.X < axis + distance)
                if (vector.X > axis - distance)
                    vector = new Vector3D(axis, vector.Y, vector.Z);

            if (vector.Y < axis + distance)
                if (vector.Y > axis - distance)
                    vector = new Vector3D(vector.X, axis, vector.Z);

            if (vector.Z < axis + distance)
                if (vector.Z > axis - distance)
                    vector = new Vector3D(vector.X, vector.Y, axis);

            return vector;
        }

        /// <summary>
        /// Computes a cycled color effect.
        /// </summary>
        /// <param name="color">The color to cycle.</param>
        /// <param name="time">The update time.</param>
        /// <param name="speed">The speed of cycle.</param>
        /// <returns>The cycled color.</returns>
        public static Color Cycle(Color color, float time, float speed)
        {
            byte max = 255;
            float stages = 6.0f;
            float timeModSpeed = (time % speed);
            float speedDivStages = speed / stages;
            float transitionSpeed = 10;

            // Increase green component.
            if (timeModSpeed < speedDivStages)
            {
                if (color.G < max - transitionSpeed)
                    color = Color.FromArgb(
                        (int)color.R,
                        (int)((float)color.G + transitionSpeed),
                        (int)color.B);
                else
                    color = Color.FromArgb(color.R, max, color.B);
            }
            // Decrease red component.
            else if (timeModSpeed < speedDivStages * 2.0f) 
            {
                if (color.R > transitionSpeed)
                    color = Color.FromArgb(
                        (int)((float)color.R - transitionSpeed),
                        (int)color.G,
                        (int)color.B);
                else
                    color = Color.FromArgb(0, color.G, color.B);
            }
            // Increase blue component.
            else if (timeModSpeed < speedDivStages * 3.0f)
            {
                if (color.B < max - transitionSpeed)
                    color = Color.FromArgb(
                        (int)color.R, 
                        (int)color.G, 
                        (int)((float)color.B + transitionSpeed));
                else
                    color = Color.FromArgb(color.R, color.G, max);
            }
            // Decrease green component.
            else if (timeModSpeed < speedDivStages * 4.0f)
            {
                if (color.G > transitionSpeed)
                    color = Color.FromArgb(
                        (int)color.R, 
                        (int)((float)color.G - transitionSpeed), 
                        (int)color.B);
                else
                    color = Color.FromArgb(color.R, 0, color.B);
            }
            // Increase red component.
            else if (timeModSpeed < speedDivStages * 5.0f)
            {
                if (color.R < max - transitionSpeed)
                    color = Color.FromArgb(
                        (int)((float)color.R + transitionSpeed),
                        (int)color.G,
                        (int)color.B);
                else
                    color = Color.FromArgb(max, color.G, color.B);
            }
            // Decrease blue component.
            else if (timeModSpeed < speed)
            {
                if (color.B > transitionSpeed)
                    color = Color.FromArgb(
                        (int)color.R,
                        (int)color.G,
                        (int)((float)color.B - transitionSpeed));
                else
                    color = Color.FromArgb(color.R, color.G, 0);
            }

            return color;
        }

        /// <summary>
        /// Checks if a color is considered dark or not.
        /// </summary>
        /// <param name="color">The color to check.</param>
        /// <returns>The the color is dark.</returns>
        public static bool IsDark(Color color)
        {
            // Get average of all color components.
            float averageColor = ((float)color.R + (float)color.G + (float)color.B) / 3.0f;

            // If the value is less than half.
            if (averageColor < 127.5f)
                return true;

            return false;
        }

        /// <summary>
        /// Shows a folder selecter dialog.
        /// </summary>
        /// <returns>The selected folder.</returns>
        public static String FolderDialog()
        {
            return FolderDialog(String.Empty);
        }


        /// <summary>
        /// Shows a folder selecter dialog with description.
        /// </summary>
        /// <param name="description">The dialog description.</param>
        /// <returns>The selected folder.</returns>
        public static String FolderDialog(String description)
        {
            FolderBrowserDialog folderDialog = new FolderBrowserDialog();
            folderDialog.Description = description;
            folderDialog.SelectedPath = Environment.CurrentDirectory;
            folderDialog.ShowNewFolderButton = false;

            if (folderDialog.ShowDialog() != DialogResult.OK)
                return String.Empty;

            return folderDialog.SelectedPath;
        }

        /// <summary>
        /// Converts Color float to byte.
        /// </summary>
        /// <param name="input">The color float.</param>
        /// <returns>The color byte.</returns>
        public static byte ToColorByte(float input)
        {
            return (byte)(255.0f * input);
        }

        /// <summary>
        /// Converts Color string to byte.
        /// </summary>
        /// <param name="input">The color string.</param>
        /// <returns>The color byte.</returns>
        public static byte ToColorByte(String input)
        {
            return ToColorByte(float.Parse(input));
        }

        /// <summary>
        /// Converts Color byte to float.
        /// </summary>
        /// <param name="input">The color byte.</param>
        /// <returns>The color float.</returns>
        public static float ToColorFloat(byte input)
        {
            return ((float)input / 255.0f);
        }

        /// <summary>
        /// Converts Color string to float.
        /// </summary>
        /// <param name="input">The color string.</param>
        /// <returns>The color float.</returns>
        public static float ToColorFloat(String input)
        {
            return ToColorFloat(byte.Parse(input));
        }

        /// <summary>
        /// Converts an angle in degrees to radians.
        /// </summary>
        /// <param name="angle">The angle in radians.</param>
        /// <returns>The angle in degrees.</returns>
        public static double DegreeToRadians(double angle)
        {
            return (double)(Math.PI * angle / 180.0);
        }

        /// <summary>
        /// Converts an rotation Vector3D in degrees to radians.
        /// </summary>
        /// <param name="vector">The rotation degree Vector3D.</param>
        /// <returns>The rotation radian Vector3D</returns>
        public static Vector3D DegreeToRadians(Vector3D vector)
        {
            vector = new Vector3D(DegreeToRadians(vector.X), DegreeToRadians(vector.Y), DegreeToRadians(vector.Z));
            return vector;
        }

        /// <summary>
        /// Converts an angle in radians to degrees.
        /// </summary>
        /// <param name="angle">The angle in degrees.</param>
        /// <returns>The angle in radians.</returns>
        public static double RadiansToDegree(double angle)
        {
            return (double)(angle * 180.0 / Math.PI);
        }

        /// <summary>
        /// Converts a rotation Vector3D in radians to degrees.
        /// </summary>
        /// <param name="vector">The rotation Vector3D in degrees.</param>
        /// <returns>The rotation Vector3D in radians.</returns>
        public static Vector3D RadiansToDegree(Vector3D vector)
        {
            vector = new Vector3D(RadiansToDegree(vector.X), RadiansToDegree(vector.Y), RadiansToDegree(vector.Z));
            return vector;
        }

        /// <summary>
        /// Computes a random integer.
        /// </summary>
        /// <param name="seed">The seed value.</param>
        /// <param name="multiplier">The number multiplier.</param>
        /// <returns>A random integer.</returns>
        public static int RandomInt(int seed, int multiplier)
        {
            Random randomNumber = new Random(seed);
            return randomNumber.Next(multiplier);
        }

        /// <summary>
        /// Computes a random float.
        /// </summary>
        /// <param name="multiplier">The number multiplier.</param>
        /// <returns>A random float.</returns>
        public static float RandomFloat(float multiplier)
        {
            Random randomNumber = new Random();
            return (float)randomNumber.NextDouble() * multiplier;
        }

        /// <summary>
        /// Converts Vector3D to include camera position.
        /// </summary>
        /// <param name="vector">The Vector3D to convert.</param>
        /// <returns>The converted Vector3D</returns>
        public static Vector3D PositionWithCameraZ(Vector3D vector)
        {
            return new Vector3D(vector.X, vector.Y, vector.Z + DisplayPanel.CameraZPosition);
        }

        /// <summary>
        /// Converts Point3D to include camera position.
        /// </summary>
        /// <param name="vector">The Point3D to convert.</param>
        /// <returns>The converted Point3D</returns>
        public static Point3D PositionWithCameraZ(Point3D point)
        {
            return new Point3D(point.X, point.Y, point.Z + DisplayPanel.CameraZPosition);
        }

        /// <summary>
        /// Projects a point in 3D space into 2D space.
        /// </summary>
        /// <param name="point">The 3D point to project.</param>
        /// <returns>The projected 2D point.</returns>
        public static PointF To2DF(Point3D point)
        {
            return new PointF((float)(point.X * DisplayPanel.CameraPerspective / point.Z), (float)(point.Y * DisplayPanel.CameraPerspective / point.Z));
        }

        /// <summary>
        /// Projects a vector in 3D space into 2D space.
        /// </summary>
        /// <param name="vector">The 3D vector to project.</param>
        /// <returns>The projected 2D point.</returns>
        public static PointF To2DF(Vector3D vector)
        {
            return new PointF((float)(vector.X * DisplayPanel.CameraPerspective / vector.Z), (float)(vector.Y * DisplayPanel.CameraPerspective / vector.Z));
        }

        /// <summary>
        /// Projects a point in 3D space into 2D space.
        /// </summary>
        /// <param name="point">The 3D point to project.</param>
        /// <returns>The projected 2D point.</returns>
        public static Point To2D(Point3D point)
        {
            return new Point((int)(point.X * DisplayPanel.CameraPerspective / point.Z), (int)(point.Y * DisplayPanel.CameraPerspective / point.Z));
        }

        /// <summary>
        /// Projects a vector in 3D space into 2D space.
        /// </summary>
        /// <param name="vector">The 3D vector to project.</param>
        public static Point To2D(Vector3D vector)
        {
            return new Point((int)((float)vector.X * (float)DisplayPanel.CameraPerspective / (float)vector.Z), (int)((float)vector.Y * (float)DisplayPanel.CameraPerspective / (float)vector.Z));
        }

        /// <summary>
        /// Projects a point in 2D space into 3D space.
        /// </summary>
        /// <param name="point">The 2D point to project.</param>
        /// <returns>The projected 3D point.</returns>
        public static Point3D To3D(Point point)
        {
            return new Point3D(point.X / (DisplayPanel.CameraPerspective / (DisplayPanel.CameraZPosition)),
                point.Y / (DisplayPanel.CameraPerspective / (DisplayPanel.CameraZPosition)),
                DisplayPanel.CameraZPosition);
        }

        /// <summary>
        /// Converts a bool to a string.
        /// </summary>
        /// <param name="boolean">The bool.</param>
        /// <returns>The converted string.</returns>
        public static String BoolToString(bool boolean)
        {
            if (boolean)
                return "1";

            return "0";
        }

        /// <summary>
        /// Gets the relative path from an absolute path.
        /// </summary>
        /// <param name="absoluteFilePath">The absolute path.</param>
        /// <param name="startPath">The relative location.</param>
        /// <returns>The relative path.</returns>
        public static String GetRelativePath(String absoluteFilePath, String startPath)
        {
            String[] firstPathParts = startPath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);
            String[] secondPathParts = absoluteFilePath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);

            int sameCounter = 0;
            for (int i = 0; i < Math.Min(firstPathParts.Length, secondPathParts.Length); i++)
            {
                if (!firstPathParts[i].ToLower().Equals(secondPathParts[i].ToLower()))
                {
                    break;
                }
                sameCounter++;
            }

            if (sameCounter == 0)
            {
                return absoluteFilePath;
            }

            String newPath = String.Empty;
            for (int i = sameCounter; i < firstPathParts.Length; i++)
            {
                if (i > sameCounter)
                {
                    newPath += Path.DirectorySeparatorChar;
                }
                newPath += "..";
            }
            if (newPath.Length == 0)
            {
                newPath = ".";
            }
            for (int i = sameCounter; i < secondPathParts.Length; i++)
            {
                newPath += Path.DirectorySeparatorChar;
                newPath += secondPathParts[i];
            }

            if (newPath[newPath.Length - 1] != Path.DirectorySeparatorChar)
            {
                newPath += Path.DirectorySeparatorChar;
            }

            return newPath;
        }

        /// <summary>
        /// Checks if a path is a valid directory.
        /// </summary>
        /// <param name="location">The path to check.</param>
        /// <returns>If the directory is valid.</returns>
        public static bool DirectoryChecker(String location)
        {
            if (!Directory.Exists(location))
            {
                try
                {
                    Directory.CreateDirectory(location);
                    Directory.Delete(location);

                    return true;
                }
                catch (Exception)
                {
                    MessageBox.Show("The supplied Mosaic application path is in an invalid format.\nEnsure that the application path is valid and try again.", "Directory Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
            }
            else
            {
                return true;
            }
        }
    }
}
