using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace LabelerCore.Model
{
    //Encode string labels with integer value between 0 and number of classes - 1
    //Inspired by scikit-learn library
    public class LabelEncoder
    {
        private List<string> _classes;
        public List<string> Classes { get { return _classes; } }

        public LabelEncoder Fit(string[] y)
        {
            this._classes = new HashSet<string>(y.ToList()).ToList();
            this._classes.Sort();
            return this;
        }

        //Fit encoder and map string labels to integer values
        public int[] FitAndTransform(string[] y)
        {
            this.Fit(y);
            return y.Select(str => this._classes.FindIndex(x => x == str))
                    .ToArray();
        }

        //Return transformed integer labels
        public int[] Transform(string[] y)
        {
            if(this._classes == null)
            {
                throw new NullReferenceException("Encoder is not fitted yet");
            }
            var ySet = new HashSet<string>(y.ToList());
            var encoderSet = new HashSet<string>(this._classes);
            if( !(ySet.IsSubsetOf(encoderSet)) )
            {
                ySet.ExceptWith(encoderSet);
                string message ="y contains new labels: ";
                foreach(var item in ySet)
                {
                    message += item.ToString();
                    message += ", ";
                }
                throw new ArgumentException(message);
            }
            return y.Select(str => this.Transform(str))
                    .ToArray();
        }

        public int Transform(string y)
        {
            if (this._classes == null)
            {
                throw new NullReferenceException("Encoder is not fitted yet");
            }
            var encoderSet = new HashSet<string>(this._classes);
            if(!encoderSet.Contains(y))
            {
                string message = y + " is new label:";
                throw new ArgumentException(message);
            }

            return this._classes.FindIndex(x => x == y);
        }

        //Transform labels back to original encoding
        public string[] InverseTransform(int[] y)
        {
            if (this._classes == null)
            {
                throw new NullReferenceException("Encoder is not fitted yet");
            }
            string[] result = new string[y.Length];
            for(int i = 0; i < y.Length; i++)
            {
                result[i] = this.InverseTransform(y[i]);
            }
            return result;
        }

        public string InverseTransform(int y)
        {
            if (this._classes == null)
            {
                throw new NullReferenceException("Encoder is not fitted yet");
            }
            return this._classes[y];
        }

    }
}