using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

using LabelerCore.Model;

namespace LabelerCore.Loop {

    public static class ModelDLL {
        public const string DLLName = "CppModel.dll";
    }

    internal static class Helpers {
        public static double[] _2dto1d(double[][] X) {
            double[] data = new double[X.Length * X[0].Length];
            //tmp, need to use System.Buffer.BlockCopy instead!
            for (int i = 0; i < X.Length; i++)
                for (int j = 0; j < X[0].Length; j++)
                    data[i * X[0].Length + j] = X[i][j];
            return data;
        }
    }

    public class CppDataset {
        internal IntPtr _cppDatasetPtr;

        #region DLLIMPORTS
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr create_Dataset();
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr init_Dataset(string filename);
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern void kill_Dataset(IntPtr dataset);
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int Dataset_add_sample(IntPtr dataset, string label, string path, double[] data, uint n_points);
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int Dataset_remove_sample(IntPtr dataset, string path);
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern void Dataset_save_to(IntPtr dataset, string filename);
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern uint Dataset_n_classes(IntPtr dataset);
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        internal static extern uint Dataset_get_classes(IntPtr dataset, IntPtr[] class_names, int[] class_codes);

        #endregion DLLIMPORTS

        public CppDataset() {
            _cppDatasetPtr = create_Dataset();
        }

        public CppDataset(string filename) {
            _cppDatasetPtr = init_Dataset(filename);
        }

        public int AddSample(string label, string path, double[] data) {
            return Dataset_add_sample(_cppDatasetPtr, label, path, data, (uint)data.Length);
        }

        public Tuple<int, string>[] GetClasses() {
            int nClasses = (int)Dataset_n_classes(_cppDatasetPtr);

            IntPtr[] class_names = new IntPtr[nClasses];
            for (int i = 0; i < nClasses; i++) {
                class_names[i] = Marshal.AllocHGlobal(sizeof(byte) * 5000);
            }
            int[] class_codes = new int[nClasses+1];
            Dataset_get_classes(_cppDatasetPtr, class_names, class_codes);
            Tuple<int, string>[] ret = new Tuple<int, string>[nClasses];
            for (int i = 0; i < nClasses; i++) {
                ret[i] = new Tuple<int, string>(
                    class_codes[i],
                    Marshal.PtrToStringAnsi(class_names[i]));
                Marshal.FreeHGlobal(class_names[i]);
                
            }
            return ret;
        }

        public static Tuple<int, string>[] GetClasses(IntPtr cppDatasetPtr) {
            int nClasses = (int)Dataset_n_classes(cppDatasetPtr);

            IntPtr[] class_names = new IntPtr[nClasses];
            for (int i = 0; i < nClasses; i++) {
                class_names[i] = Marshal.AllocHGlobal(sizeof(byte) * 5000);
            }
            int[] class_codes = new int[nClasses + 1];
            Dataset_get_classes(cppDatasetPtr, class_names, class_codes);
            Tuple<int, string>[] ret = new Tuple<int, string>[nClasses];
            for (int i = 0; i < nClasses; i++) {
                ret[i] = new Tuple<int, string>(
                    class_codes[i],
                    Marshal.PtrToStringAnsi(class_names[i]));
                Marshal.FreeHGlobal(class_names[i]);

            }
            return ret;
        }


        public int RemoveSample(string path) {
            return Dataset_remove_sample(_cppDatasetPtr, path);
        }

        public void SaveTo(string filename) {
            Dataset_save_to(_cppDatasetPtr, filename);
        }

        ~CppDataset() {
            CppDataset.kill_Dataset(_cppDatasetPtr);
        }
    }


    public struct CppNeighbor {
        public double[] data;
        public int y;
        public string label;
        public double distance;
        public uint rank;
        public string path;
    }

    public enum Metric { MANHATTAN, EUCLIDEAN, SQUARED_EUCLIDEAN };
    public enum Score { ACCURACY, F1_MACRO, F1_MICRO };

    public class CppLoopClassifier {

        internal IntPtr _cppLoopClfPtr;

        #region DLLIMPORTS
        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr init_LoopClf(uint n_features);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr init_LoopClf(string filename);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr copy_LoopClf(IntPtr LoopClf);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void kill_LoopClf(IntPtr LoopClf);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void LoopClf_save_to(IntPtr LoopClf, string filename);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void LoopClf_train(IntPtr LoopClf, IntPtr Dataset);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void LoopClf_predict(IntPtr LoopClf, double[] data, uint n_samples, int[] y_pred);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern double LoopClf_get_neighbors(IntPtr LoopClf,
                                double[] sample, uint n_neighbors,
                                double[] out_data,
                                int[] out_targets,
                                IntPtr[] out_labels,
                                double[] out_distances,
                                uint[] out_ranks,
                                IntPtr[] out_pathes);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void LoopClf_classification_info(IntPtr LoopClf, double[] x, IntPtr info);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern uint LoopClf_n_features(IntPtr LoopClf);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr LoopClf_dataset(IntPtr LoopClf);

        [DllImport(ModelDLL.DLLName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void LoopClf_get_probs(IntPtr LoopClf, double[] sample, double[] probs, int[] classes);
        
        #endregion

        public uint NumFeatures {
            get { return LoopClf_n_features(_cppLoopClfPtr); }
        }

        public Tuple<int, string>[] GetClasses() {
            return CppDataset.GetClasses(LoopClf_dataset(_cppLoopClfPtr));
        }

        public Tuple<int, double>[] GetProbs(double[] x) {
            int nClasses = (int)CppDataset.Dataset_n_classes(LoopClf_dataset(_cppLoopClfPtr));
            int[] classes = new int[nClasses];
            double[] probs = new double[nClasses];
            LoopClf_get_probs(_cppLoopClfPtr, x, probs, classes);
            Tuple<int, double>[] ret = new Tuple<int,double>[nClasses];
            for (int i = 0; i < ret.Length; i++) {
                ret[i] = new Tuple<int,double>(classes[i], probs[i]);
            }
            return ret;
        }


        public CppLoopClassifier(int numFeatures) {
            _cppLoopClfPtr = init_LoopClf((uint)numFeatures);
        }

        public CppLoopClassifier(string filename) {
            _cppLoopClfPtr = init_LoopClf(filename);
        }

        private CppLoopClassifier(IntPtr LoopClf) {
            _cppLoopClfPtr = LoopClf;
        }

        public void SaveTo(string filename) {
            LoopClf_save_to(_cppLoopClfPtr, filename);
        }

        public virtual void Train(CppDataset dataset) {
            LoopClf_train(_cppLoopClfPtr, dataset._cppDatasetPtr);
        }

        public virtual int[] Predict(double[][] X) {
            int[] result = new int[X.Length];
            uint n_samples = (uint)X.Length;
            double[] x1d = Helpers._2dto1d(X);
            LoopClf_predict(_cppLoopClfPtr, x1d, n_samples, result);
            return result;
        }

        public virtual int Predict(double[] x) {
            int[] result = new int[x.Length];
            uint n_features = (uint)x.Length;
            LoopClf_predict(_cppLoopClfPtr, x, 1, result);
            return result[0];
        }

        public CppNeighbor[] GetNeighbors(double[] x, uint numNeighbors) {

            double[] data = new double[numNeighbors * NumFeatures];
            int[] targets = new int[numNeighbors];
            double[] distances = new double[numNeighbors];
            uint[] ranks = new uint[numNeighbors];
            IntPtr[] pathes = new IntPtr[numNeighbors];
            IntPtr[] labels = new IntPtr[numNeighbors];
            for (int i = 0; i < numNeighbors; i++) {
                pathes[i] = Marshal.AllocHGlobal(sizeof(byte) * 1000);
                labels[i] = Marshal.AllocHGlobal(sizeof(byte) * 1000);
            }
            LoopClf_get_neighbors(_cppLoopClfPtr, x, numNeighbors, data, targets, labels, distances, ranks, pathes);
            CppNeighbor[] neighbors = new CppNeighbor[numNeighbors];

            for (int i = 0; i < numNeighbors; i++) {
                neighbors[i].data = new double[NumFeatures];
                for (int j = 0; j < NumFeatures; j++)
                    neighbors[i].data[j] = data[i * NumFeatures + j];
                neighbors[i].y = targets[i];
                neighbors[i].distance = distances[i];
                neighbors[i].rank = ranks[i];
                neighbors[i].path = Marshal.PtrToStringAnsi(pathes[i]);
                neighbors[i].label = Marshal.PtrToStringAnsi(labels[i]);
                Marshal.FreeHGlobal(pathes[i]);
                Marshal.FreeHGlobal(labels[i]);
            }

            return neighbors;
        }

        public string ClassificationInfo(double[] x) {
            IntPtr infoptr = Marshal.AllocHGlobal(sizeof(char) * 10000);
            LoopClf_classification_info(_cppLoopClfPtr, x, infoptr);
            string info = Marshal.PtrToStringAnsi(infoptr);
            Marshal.FreeHGlobal(infoptr);
            return info;
        }

        ~CppLoopClassifier() {
            CppLoopClassifier.kill_LoopClf(_cppLoopClfPtr);
        }

        public object Clone() {
            return new CppLoopClassifier(copy_LoopClf(this._cppLoopClfPtr));
        }
    }


}
