using System;
using System.Linq;
using System.Text;

using LabelerCore.Data;
using LabelerCore.Model;
using LabelerCore.Model.Classifiers;
using LabelerCore.Model.Transformers;


namespace LabelerCore.Loop {

    public class ClassificationDetails {
        public readonly double[] input;
        public readonly Neighbor[] Neighbors;
        public Tuple<string, double>[] CentroidDist;

        public ClassificationDetails(double[] input, Neighbor[] neighbors, Tuple<string, double>[] centroidDist) {
            this.input = input;
            this.Neighbors = neighbors;
            this.CentroidDist = centroidDist;
        }

        public override string ToString() {
            StringBuilder sb = new StringBuilder();
            sb.Append("Neighbors: \n");

            int clWidth = CentroidDist.Select(x => x.Item1)
                                      .Concat(Neighbors.Select(x => x.vehicleClass))
                                      .Select(x => x.Length)
                                      .Max();


            foreach (Neighbor n in Neighbors) {
                string row = string.Format("{0} | {1} \n", n.vehicleClass.PadRight(clWidth),
                                                        n.distance.ToString("F3").PadLeft(6));
                sb.Append(row);
            }

            sb.Append("Centroids:\n");
            foreach (Tuple<string, double> centrDist in CentroidDist.OrderBy(x => x.Item2)) {
                string row = string.Format("{0} | {1} \n", centrDist.Item1.PadRight(clWidth),
                                                           centrDist.Item2.ToString("F3").PadLeft(6));
                sb.Append(row);
            }
            return sb.ToString();
        }
    }

    public class ClassificationInfo {
        private KNNClassifier _knn = new KNNClassifier();
        private NearestCentroid _nearestCentroid = new NearestCentroid();
        private MinMaxScaler _minmax = new MinMaxScaler();
        private Weight _weight;
        private LabelEncoder _labelEncoder = new LabelEncoder();
        private Sample[] _samples;

        public bool IsFitted {
            get {
                return _samples != null;
            }
        }

        public ClassificationInfo() {
        }


        public void Fit(Sample[] samples) {
            _weight = new Weight(Sample.Features.Select(x => x.Weight).ToArray());

            _samples = samples;
            string[] labels = samples.Select(x => x.vehicleClass).ToArray();
            _labelEncoder = new LabelEncoder();
            _labelEncoder.Fit(labels);

            double[][] X = new double[samples.Length][];
            int[] y = new int[samples.Length];

            for (int i = 0; i < samples.Length; i++) {
                X[i] = samples[i].FeatureArray;
                y[i] = _labelEncoder.Transform(samples[i].vehicleClass);
            }

            X = _minmax.FitTransform(X);
            X = _weight.Transform(X);
            _knn.Fit(X, y);
            _nearestCentroid.Fit(X, y);
        }

        public ClassificationDetails Details(double[] X, ushort numberOfNeighbors = 5) {
            //get neighbors
            X = _weight.Transform(_minmax.Transform(X));
            Tuple<int[], double[]> indDist = _knn.KNeighbors(X, numberOfNeighbors);
            Neighbor[] neighbors = new Neighbor[numberOfNeighbors];
            for (int i = 0; i < neighbors.Length; i++)
                neighbors[i] = new Neighbor(_samples[indDist.Item1[i]], indDist.Item2[i]);

            //get nearest centroids
            double[] distances = _nearestCentroid.CentroidsDistance(X);
            int[] ind = Model.Math.Stats.ArgKSmallest(distances, numberOfNeighbors);
            //key - class name, value - distance to the class centroid
            Tuple<string, double>[] centroidDist = new Tuple<string, double>[numberOfNeighbors];
            for (int i = 0; i < ind.Length; i++) {
                int classCode = _nearestCentroid.Classes[ind[i]];
                centroidDist[i] = new Tuple<string, double>(_labelEncoder.InverseTransform(classCode), distances[ind[i]]);
            }

            return new ClassificationDetails(X, neighbors, centroidDist);
        }

    }
}
