using System;
using System.Collections.Generic;
using System.Text;

namespace Intemi
{
    public class MDDataAugmentationConfig : IConfiguration
    {
        public void Configure(ConfigBuilder confBuilder)
        {
            confBuilder.DeclareInput("Dataset",
                new Type[] { typeof(IDataTable) }, false);
            confBuilder.DeclareOutput("DataSet", "FilledDataTable", 
                new Type[] { typeof(IDataTable) });
        }
        public object Clone()
        {
            MDDataAugmentationConfig c = new MDDataAugmentationConfig();
            return c;
        }
    }

    [Machine("MDDataAugmentation", typeof(MDDataAugmentationConfig))]
    public class MDDataAugmentation : IMachine, IDataSetTransformer
    {
        IMachineBase modelBase;
        FeatureStat[] fs;
        float[,] values;
        bool[,] missing;
        IDataTable dataTable;
        int numberOfUniqPatterns;
        bool[,] Smatrix;
        List<int>[] uniqPatternCounter;
        List<int>[] OMatrix;
        List<int>[] MMatrix;
        float[, ,] TMatrix;
        float[,] TobsMatrix;
        float[,] TethaMatrix;
        IDataTable filledDataTable;

        public IDataSet FilledDataTable
        {
            get { return filledDataTable; }
        }

        public float calculateMean(int i)
        {
            int counter = 0;
            float sum = 0;
            for (int j = 0; j < dataTable.InstanceCount; j++)
            {
                if (missing[j, i] == false)
                {
                    sum += values[j, i];
                    counter++;
                }
            }
            return (sum / counter);
        }
        public void generateTethaMatrix()
        {
            TethaMatrix = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j ++)
                {
                    TethaMatrix[i, j] = 0;
                }
            }
            TethaMatrix[0, 0] = -1;
            for (int i = 1; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                TethaMatrix[i, i] = 1;
                TethaMatrix[i, 0] = calculateMean(i - 1);
                TethaMatrix[0, i] = TethaMatrix[i, 0];
            }
        }
        public void createRMatrix()
        {
            Smatrix = new bool[dataTable.InstanceCount, dataTable.FeaturesInfo.Count];
            uniqPatternCounter = new List<int>[dataTable.InstanceCount];

            numberOfUniqPatterns = 0;
            for (int i = 0; i < dataTable.InstanceCount; i++)
            {
                Console.Write(i + " ");
                bool found = false;
                int z;
                for (z = 0; z < numberOfUniqPatterns; z++)
                {
                    int cnt = 0;
                    for (int j = 0; j < dataTable.FeaturesInfo.Count; j++)
                    {
                        if (Smatrix[z, j] == missing[i, j])
                        {
                            cnt++;
                        }
                    }
                    if (cnt == dataTable.FeaturesInfo.Count)
                    {
                        found = true;
                        break;
                    }
                }
                if (found == false)
                {
                    for (int j = 0; j < dataTable.FeaturesInfo.Count; j++)
                    {
                        Smatrix[numberOfUniqPatterns, j] = missing[i, j];
                    }
                    if (uniqPatternCounter[numberOfUniqPatterns] == null)
                    {
                        uniqPatternCounter[numberOfUniqPatterns] = new List<int>();
                    }
                    uniqPatternCounter[numberOfUniqPatterns].Add(i);
                    numberOfUniqPatterns++;
                    Console.WriteLine("not found");
                }
                else
                {
                    uniqPatternCounter[z].Add(i);
                    Console.WriteLine("found");
                }
            }
            // reverse
            for (int i = 0; i < numberOfUniqPatterns; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count; j++)
                {
                    Smatrix[i, j] = !Smatrix[i, j];
                }
            }
            // Debugging
            for (int i = 0; i < numberOfUniqPatterns; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count; j++)
                {
                    Console.Write(Smatrix[i, j] + ", ");                
                }
                Console.Write("Samples : ");
                for (int j = 0; j < uniqPatternCounter[i].Count; j++)
                {
                    Console.Write(uniqPatternCounter[i][j] + ", ");
                }
                Console.WriteLine();
            }
        }
        public void createMatrixes()
        {
            OMatrix = new List<int>[numberOfUniqPatterns];
            MMatrix = new List<int>[numberOfUniqPatterns];
            for (int i = 0; i < numberOfUniqPatterns; i++)
            {
                OMatrix[i] = new List<int>();
                MMatrix[i] = new List<int>();
                for (int j = 0; j < dataTable.FeaturesInfo.Count; j++)
                {
                    if (Smatrix[i,j] == true)
                    {
                        OMatrix[i].Add(j);
                    }
                    else
                    {
                        MMatrix[i].Add(j);
                    }
                }
            }
            // Debugging
            for (int i = 0; i < numberOfUniqPatterns; i++)
            {
                Console.Write("M[" + i + "] = (");
                for (int z = 0; z < MMatrix[i].Count; z++)
                {
                    Console.Write(MMatrix[i][z] + ", ");
                }
                Console.WriteLine(")");
                Console.Write("O[" + i + "] = (");
                for (int z = 0; z < OMatrix[i].Count; z++)
                {
                    Console.Write(OMatrix[i][z] + ", ");
                }
                Console.WriteLine(")");
            }
        }
        public void createTMatrix()
        {
            TMatrix = new float[numberOfUniqPatterns, dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            for (int s = 0; s < numberOfUniqPatterns; s++)
            {
                for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
                {
                    Console.Write("( ");
                    for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                    {
                        if (i == 0 && j == 0)
                        {
                            TMatrix[s, i, j] = uniqPatternCounter[s].Count;
                        }
                        else if (i == j)
                        {
                            for (int z = 0; z < uniqPatternCounter[s].Count; z++)
                            {
                                TMatrix[s, i, j] += 
                                    (values[uniqPatternCounter[s][z], j - 1] * 
                                     values[uniqPatternCounter[s][z], j - 1]);
                            }
                        }
                        else if (j < i)
                        {
                            TMatrix[s, i, j] = 0;
                        }
                        else if (i == 0)
                        {
                            for (int z = 0; z < uniqPatternCounter[s].Count; z++)
                            {
                                TMatrix[s, i, j] += (values[uniqPatternCounter[s][z], j - 1]);
                            }
                        }
                        else
                        {
                            for (int z = 0; z < uniqPatternCounter[s].Count; z++)
                            {
                                TMatrix[s, i, j] +=
                                    (values[uniqPatternCounter[s][z], i - 1] *
                                     values[uniqPatternCounter[s][z], j - 1]);
                            }
                        }
                        Console.Write(TMatrix[s, i, j] + "\t");
                    }
                    Console.WriteLine(")");
                }
            }
            // DEBUGGING
            /*for (int s = 0; s < numberOfUniqPatterns; s++)
            {
                for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
                {
                    Console.Write("( ");
                    for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                    {
//                        Console.Write("%f, ", TMatrix[s, i, j]);
                        Console.Write(TMatrix[s, i, j] + "\t");
                    }
                    Console.WriteLine(")");
                }
            }*/
        }
        public void generateTobsMatrix()
        {
            float[, ,] Tobs = new float[numberOfUniqPatterns, dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            float[, ,] Tmis = new float[numberOfUniqPatterns, dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            TobsMatrix = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            for (int s = 0; s < numberOfUniqPatterns; s++)
            {
                Console.WriteLine("----------------------------------------------------------------------");
                for (int i = 0; i < MMatrix[s].Count; i++)
                {
                    for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                    {
                        Tmis[s, MMatrix[s][i] + 1, j] = TMatrix[s, MMatrix[s][i] + 1, j];
                        Tmis[s, j, MMatrix[s][i] + 1] = TMatrix[s, j, MMatrix[s][i] + 1];
                    }
                }
                for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
                {
                    Console.Write("(");
                    for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                    {
                        Console.Write(Tmis[s, i, j] + "\t");
                    }
                    Console.WriteLine(")");
                }
                for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
                {
                    //Console.Write("(");
                    for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                    {
                        Tobs[s, i, j] = TMatrix[s, i, j] - Tmis[s, i, j];
                        //Console.Write(Tobs[s, i, j] + "\t");
                        if (s == 0)
                        {
                            TobsMatrix[i, j] = Tobs[s, i, j];
                        }
                        else
                        {
                            TobsMatrix[i, j] += Tobs[s, i, j];
                        }
                    }
                    //Console.WriteLine(")");
                }
            }
            Console.WriteLine("----------------------------------------------------------------------");
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                Console.Write("(");
                for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                {
                    Console.Write(TobsMatrix[i, j] + "\t");
                }
                Console.WriteLine(")");
            }
            Console.WriteLine("----------------------------------------------------------------------");
        }
        public float[,] SWP(int k, float[,] matrix, float multiplier)
        {
            float[,] result = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            float[,] matrix1 = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                {
                    matrix1[i, j] = multiplier * matrix[i, j];
                }
            }
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                {
                    if (i != k && j != k)
                    {
                        result[i, j] = matrix1[i, j] - ((matrix1[i, k] * matrix1[k, j]) / matrix1[k, k]);
                    }
                }
            }
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                if (i != k)
                {
                    result[k, i] = matrix1[k, i] / matrix1[k, k];
                    result[i, k] = matrix1[i, k] / matrix1[k, k];
                }
            }
            result[k, k] = -1 / matrix1[k, k];
            return result;
        }
        public float[,] RSW(int k, float[,] matrix, float multiplier)
        {
            float[,] result = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                {
                    if (i != k && j != k)
                    {
                        result[i, j] = matrix[i, j] - ((matrix[i, k] * matrix[k, j]) / matrix[k, k]);
                    }
                }
            }
            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                if (i != k)
                {
                    result[k, i] = - matrix[k, i] / matrix[k, k];
                    result[i, k] = - matrix[i, k] / matrix[k, k];
                }
            }
            result[k, k] = -1 / matrix[k, k];
            return result;
        }
        public float[,] CHOL(int sample, float[,] matrix1, float multiplier)
        {
            float[,] result = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            float[,] matrix = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];

            for (int i = 0; i < dataTable.FeaturesInfo.Count + 1; i++)
            {
                for (int j = 0; j < dataTable.FeaturesInfo.Count + 1; j++)
                {
                    matrix[i, j] = multiplier * matrix1[i, j];
                }
            }

            for (int i = 0; i < MMatrix[sample].Count; i++)
            {
                float sum = 0;
                for (int k = 0; k < MMatrix[sample].Count; k++)
                {
                    if (MMatrix[sample][k] < MMatrix[sample][i])
                    {
                        sum += (matrix[MMatrix[sample][k] + 1, MMatrix[sample][i] + 1] *
                            matrix[MMatrix[sample][k] + 1, MMatrix[sample][i] + 1]);
                    }
                }
                result[MMatrix[sample][i] + 1, MMatrix[sample][i] + 1] =
                    (float)Math.Sqrt(matrix[MMatrix[sample][i] + 1, MMatrix[sample][i] + 1] - sum);

                for (int j = 0; j < MMatrix[sample].Count; j++)
                {
                    if (MMatrix[sample][j] > MMatrix[sample][i])
                    {
                        float sum1 = 0;
                        for (int k = 0; k < MMatrix[sample].Count; k++)
                        {
                            if (MMatrix[sample][k] < MMatrix[sample][i])
                            {
                                sum1 += (matrix[MMatrix[sample][k] + 1, MMatrix[sample][i] + 1] *
                                    matrix[MMatrix[sample][k] + 1, MMatrix[sample][j] + 1]);
                            }
                        }
                        result[MMatrix[sample][i] + 1, MMatrix[sample][j] + 1] =
                            ((float)1 / matrix[MMatrix[sample][i] + 1, MMatrix[sample][i] + 1]) *
                            (matrix[MMatrix[sample][i] + 1, MMatrix[sample][j] + 1] - sum1);
                    }
                }
            }
            return result;
        }

        public void SetMachineBase(IMachineBase _modelBase)
        {
            modelBase = _modelBase;
        }
        public void Run(ref bool shouldTerminate)
        {
            IDataTable dt = modelBase.GetInput("Dataset") as IDataTable;
            fs = dt.FeatureStatistics;
            filledDataTable = (IDataTable)Transform(dt);
        }
        
        public IDataSet Transform(IDataSet dataSet)
        {
            dataTable = dataSet as IDataTable;
            IDataTableBuilder dtb = (IDataTableBuilder)dataTable.GetBuilder();
            dtb.CloneFrom(dataTable);
            
            values = dtb.Values;
            missing = dtb.Missing;

            generateTethaMatrix();
            createRMatrix();
            createMatrixes();
            createTMatrix();
            generateTobsMatrix();

            Random randObj = new Random();

            float[,] tetha = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            float[,] T = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            float[,] c = new float[dataTable.FeaturesInfo.Count + 1, dataTable.FeaturesInfo.Count + 1];
            float[] z = new float[dataTable.FeaturesInfo.Count + 1];
            tetha = TethaMatrix;

            for (int iter = 0; iter < 20; iter++)
            {
                // Copying Tobs into T;
                for (int y = 0; y < dataTable.FeaturesInfo.Count + 1; y++)
                {
                    for (int x = 0; x < dataTable.FeaturesInfo.Count + 1; x++)
                    {
                        T[y, x] = TobsMatrix[y, x];
                    }
                }
                
                for (int s = 0; s < numberOfUniqPatterns; s++)
                {
                    for (int j = 1; j < dataTable.FeaturesInfo.Count + 1; j++)
                    {
                        if (Smatrix[s, j - 1] == true && tetha[j, j] > 0)
                        {
                            tetha = SWP(j, tetha, 1);
                        }
                        if (Smatrix[s, j - 1] == false && tetha[j, j] < 0)
                        {
                            tetha = RSW(j, tetha, 1);
                        }
                    }
                    c = CHOL(s, tetha, 1);
                    for (int i = 0; i < uniqPatternCounter[s].Count; i++)
                    {
                        for (int j = 0; j < MMatrix[s].Count; j++)
                        {
                            values[uniqPatternCounter[s][i], MMatrix[s][j]] = tetha[0, MMatrix[s][j] + 1];

                            for (int k = 0; k < OMatrix[s].Count; k++)
                            {
                                values[uniqPatternCounter[s][i], MMatrix[s][j]] +=
                                    (tetha[OMatrix[s][k] + 1, MMatrix[s][j] + 1] *
                                     values[uniqPatternCounter[s][i], OMatrix[s][k]]);
                            }
                            z[MMatrix[s][j] + 1] = (float)(randObj.NextDouble());
                            for (int k = 0; k < MMatrix[s].Count; k++)
                            {
                                if (MMatrix[s][k] <= MMatrix[s][j])
                                {
                                    values[uniqPatternCounter[s][i], MMatrix[s][j]] +=
                                        (c[MMatrix[s][k] + 1, MMatrix[s][j] + 1] *
                                         z[MMatrix[s][k] + 1]);
                                }
                            }
                            T[0, MMatrix[s][j] + 1] += values[uniqPatternCounter[s][i], MMatrix[s][j]];

                            for (int k = 0; k < OMatrix[s].Count; k++)
                            {
                                T[OMatrix[s][k] + 1, MMatrix[s][j] + 1] +=
                                    (values[uniqPatternCounter[s][i], MMatrix[s][j]] *
                                     values[uniqPatternCounter[s][i], OMatrix[s][k]]);
                            }
                            for (int k = 0; k < MMatrix[s].Count; k++)
                            {
                                if (MMatrix[s][k] <= MMatrix[s][j])
                                {
                                    T[OMatrix[s][k] + 1, MMatrix[s][j] + 1] +=
                                        (values[uniqPatternCounter[s][i], MMatrix[s][j]] *
                                         values[uniqPatternCounter[s][i], OMatrix[s][k]]);
                                }
                            }
                        }
                    }
                }
                
                tetha = SWP(1, T, ((float)1 / dataTable.InstanceCount));
                for (int i = 2; i < dataTable.FeaturesInfo.Count + 1; i++)
                {
                    tetha = SWP(i, tetha, 1);
                }
                //tetha = SWP(0, T, ((float)1 / dataTable.InstanceCount));
                //tetha = CHOL(0, T, ((float)1 / dataTable.InstanceCount));
                //tetha = CHOL(0, T, 1);
            }

            /* Debuging */
            for (int j = 0; j < dataTable.InstanceCount; j++)
            {
                for (int i = 0; i < dataTable.FeaturesInfo.Count; i++)
                {
                    Console.Write(values[j, i] + " ");
                }
                Console.WriteLine();
            }
            for (int i = 0; i < dataTable.FeaturesInfo.Count; i++)
            {
                Console.Write(fs[i].mean + " ");
            }
            /**/
            return dtb.Build();
        }
    }
}

