﻿/// ------------------------------- ///
/// Juan Francisco Mora Palomino    ///
///                                 ///
/// Twitter: @JfranMora             ///
/// Contact: jfranmp@outlook.com    ///
/// ------------------------------- ///

using Algorithms.Utils;
using System;

namespace Algorithms.Math
{

    [System.Serializable]
    public class Matrix2D<T>
    {

        public int width
        {
            get;
            private set;
        }

        public int height
        {
            get;
            private set;
        }

        public T[,] data
        {
            get;
            private set;
        }

        public Matrix2D(int width, int height) : this(width, height, default(T)) { }

        public Matrix2D(int width, int height, T initialValue)
        {
            this.width = width;
            this.height = height;
            this.data = new T[width, height];

            IterationUtils.IterateDouble(width, height, (x, y) => data[x, y] = initialValue);
        }

        private int GetLoopedValue(int value, int max)
        {
            return value >= 0 ? value % max : max + (value % max);
        }

        private int GetReflectedValue(int value, int max)
        {
            return (max - 1) - value;
        }

        public T Get(int x, int y)
        {
            return data[x, y];
        }

        public T Get(Vector2D position)
        {
            return data[position.x, position.y];
        }

        public T GetLooped(int x, int y)
        {
            return Get(GetLoopedValue(x, width), GetLoopedValue(y, height));
        }

        public T GetLooped(Vector2D position)
        {
            return GetLooped(position.x, position.y);
        }

        private T GetReflectedX(int x, int y)
        {
            UnityEngine.Debug.Log(x + " -> " + GetReflectedValue(x, width));
            return Get(GetReflectedValue(x, width), y);
        }

        private T GetReflectedX(Vector2D position)
        {
            return GetReflectedX(position.x, position.y);
        }

        private T GetReflectedY(int x, int y)
        {
            return Get(x, GetReflectedValue(y, height));
        }

        private T GetReflectedY(Vector2D position)
        {
            return GetReflectedY(position.x, position.y);
        }

        private T GetReflectedXY(int x, int y)
        {
            return Get(GetReflectedValue(x, width), GetReflectedValue(y, height));
        }

        private T GetReflectedXY(Vector2D position)
        {
            return GetReflectedXY(position.x, position.y);
        }

        public void Set(int x, int y, T value)
        {
            if (IsValidPosition(x,y))
                data[x, y] = value;
        }

        public void Set(Vector2D pos, T value)
        {
            Set(pos.x, pos.y, value);
        }

        public void Set(Func<int, int, T> function)
        {
            IterationUtils.IterateDouble(width, height, (x, y) => data[x, y] = function(x, y));
        }

        public bool IsValidPosition(int x, int y)
        {
            return (0 <= x && x < width) && (0 <= y && y < height);
        }

        public bool IsValidPosition(Vector2D position)
        {
            return IsValidPosition(position.x, position.y);
        }

        public void DoAction(Action<int, int> action)
        {
            IterationUtils.IterateDouble(width, height, action);
        }

        public void DoFunction(Func<int, int, T> function)
        {
            IterationUtils.IterateDouble(width, height, (x, y) => data[x, y] = function(x, y));
        }

        public override string ToString()
        {
            String str = "";

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    str += data[x, y] + " ";
                }
                str += "\n";
            }

            return str;
        }

        #region static

        public static Matrix2D<T> ReflectedX(Matrix2D<T> input)
        {
            Matrix2D<T> output = new Matrix2D<T>(input.width, input.height);
            output.DoFunction((x, y) => input.GetReflectedX(x, y));
            return output;
        }

        public static Matrix2D<T> ReflectedY(Matrix2D<T> input)
        {
            Matrix2D<T> output = new Matrix2D<T>(input.width, input.height);
            output.DoFunction((x, y) => input.GetReflectedY(x, y));
            return output;
        }

        public static Matrix2D<T> ReflectedXY(Matrix2D<T> input)
        {
            Matrix2D<T> output = new Matrix2D<T>(input.width, input.height);
            output.DoFunction((x, y) => input.GetReflectedXY(x, y));
            return output;
        }

        public static Matrix2D<T> SubMatrix(Matrix2D<T> input, int x, int y, int width, int height)
        {
            Matrix2D<T> output = new Matrix2D<T>(width, height);
            IterationUtils.IterateDouble(x, x + width, y, y + height, (xx, yy) => output.Set(xx - x, yy - y, input.Get(xx, yy)));
            return output;
        }

        public static Matrix2D<T> SubMatrixLooped(Matrix2D<T> input, int x, int y, int width, int height)
        {
            Matrix2D<T> output = new Matrix2D<T>(width, height);
            IterationUtils.IterateDouble(x, x + width, y, y + height, (xx, yy) => output.Set(xx - x, yy - y, input.GetLooped(xx, yy)));
            return output;
        }

        #endregion

    }


}