using System;
using System.Linq;
using LabelerCore.Model.Math;

namespace LabelerCore.Model.Classifiers
{
    public class BootstrapBagging : IClassifier
    {
        private int _numberOfClfs;
        private double _maxSamples;
        //private double _maxFeatures;
        //private bool _bootstrapFeatures;

        private IClassifier[] _clfs;
        private IClassifier _baseClf;

        public BootstrapBagging(IClassifier clf, 
                      int numberOfClfs = 10, 
                      double maxSamples = 1.0
                      //double maxFeatures = 1.0,
                      //bool bootstrapFeatures=false
                                )
        {
            _baseClf = clf;
            _numberOfClfs = numberOfClfs;
            _maxSamples = Utils.CheckBoundaries(maxSamples, 0.0, 1.0);
            //_maxFeatures = Utils.CheckBoundaries(maxFeatures, 0.0, 1.0);
            //_bootstrapFeatures = bootstrapFeatures;

        }

        private void _CheckFitness()
        {
            if(_clfs == null)
                throw new NullReferenceException("Bagging classifier is not fitted yet");
            foreach(var clf in _clfs)
                if(clf == null)
                    throw new NullReferenceException("Bagging classifier is not fitted yet");
        }

        public void Fit(double[][] X, int[] y)
        {
            CheckInputs.CheckNumberOfSamples(X, y);
            _clfs = new IClassifier[_numberOfClfs];
            int btSampleSize = (int)System.Math.Floor(_maxSamples * X.Length);
            //int nFeatures = (int)System.Math.Floor(_maxFeatures * X[0].Length); 
            for(int i = 0; i < _numberOfClfs; i++)
            {
                //generate bootstrap sample indices
                int[] btSampleInd = new int[btSampleSize];
                Random rnd = new Random();
                for(int j = 0; j < btSampleSize; j++)
                    btSampleInd[j] = rnd.Next(0, X.Length);
                //get bootstrap sample
                double[][] Xbt = Utils.ReOrder(X, btSampleInd);
                int[] ybt = Utils.ReOrder(y, btSampleInd);
                //add new clf
                _clfs[i] = (IClassifier)_baseClf.Clone();
                _clfs[i].Fit(Xbt, ybt);

            }
        }

        public int[] Predict(double[][] X)
        {
            _CheckFitness();
            return X.AsParallel().AsOrdered().Select(x => Predict(x)).ToArray();
        }

        public int Predict(double[] x)
        {
            _CheckFitness();
            return Stats.Mode(_clfs.AsParallel().Select(c => c.Predict(x)).ToArray());
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }

    }
}
