using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Sched.CustomClasses;

namespace Sched.ScheduleAlgorithm
{
    public class BaseAlgorithm
    {
        protected const int _emptySpotVal = -1;
        protected (int CourseId,int LessonTypeId)[,,] MainArray;     //(courseId,LessonTypeId)
        protected (int CourseId, int LessonTypeId)[] TempArrayRow;
        protected int nDay = 7;
        protected int nTimeblocks = 15;
        protected int nWeeks;

        protected int _dayStart;
        protected int _dayEnd;
        protected int _periodStart;
        protected int _periodEnd;
        protected Semester _sem;

        //Score
        protected int _lessonTypePreference = -1;
        protected int _bonusCourseSameDay = -50;
        protected int _bonusCourseContinuous = -100;
        protected int _bonusCourseLessonTypePreference = -50;       //bonus for this lesson type of course being first
        protected int _penaltyGaps = 100;
        protected int _penaltyLatePeriod = 50;
        protected int _penaltyLatePeriodThreshold = 10;
        protected int _penaltyAloneInADay = 20;
        protected int _penaltyDayAlonePeriodsThreshold = 4;

        public BaseAlgorithm(int dayStart, int dayEnd, int periodStart, int periodEnd, int semesterId, int studentGroupId, int lessonTypePreference)
        {
            this._dayStart = dayStart;
            this._dayEnd = dayEnd;
            this._periodStart = periodStart;
            this._periodEnd = periodEnd;

            this._lessonTypePreference = lessonTypePreference;

            _sem = (Semester)DBI.GetItemById("Semester", semesterId);
            nWeeks = _sem.GetNumberOfWeeks();

            
            TempArrayRow = new (int, int)[nTimeblocks];
            MainArray = new (int, int)[nDay, nTimeblocks, nWeeks];
            for (int w = 0; w < nWeeks; w++)
            {
                for (int d = _dayStart; d <= _dayEnd; d++)
                {
                    for (int p = _periodStart; p <= _periodEnd; p++)
                    {
                        MainArray[d, p, w] = (_emptySpotVal, _emptySpotVal);

                    }
                }
            }

            PrepareMainArray(DBI.GetLessonsByStudentGroupId(studentGroupId));  //must insert already declared lessons
        }

        protected List<LessonWithArray> PrepareLessons(List<Lesson> lessonList)
        {
            List<LessonWithArray> lessonsToAssign = new List<LessonWithArray>();

            foreach (Lesson lssn in lessonList)
            {
                LessonWithArray lwa = new LessonWithArray(lssn, nDay, nTimeblocks, nWeeks);
                lwa.MarkOccupiedAllLessons(DBI.GetConflictLessons(lssn, _periodStart, _periodEnd), _sem);
                lwa.RecalculateEmptySpotsScore(_dayStart, _dayEnd, _periodStart, _periodEnd);
                lessonsToAssign.Add(lwa);
            }

            return lessonsToAssign;
        }

        protected void PrepareMainArray(ObservableCollection<object> lessonColl)
        {
            foreach (Lesson lssn in lessonColl)
            {
                InsertLesson(lssn);
            }
        }
        public void InsertLesson(Lesson lssn)
        {
            InsertValues(lssn, lssn.CourseId,lssn.LessonTypeId);
        }
        public void RemoveLesson(Lesson lssn)
        {
            InsertValues(lssn, _emptySpotVal, _emptySpotVal);
        }
        public void InsertValues(Lesson lssn, int courseId, int lessonTypeId)
        {
            int nWeeks = MainArray.GetLength(2);
            int day = AutoscheduleData.GetWeekDayIndex(lssn.StartDateDT.DayOfWeek.ToString());
            int periodicity = lssn.Periodicity;
            int weekStart = _sem.GetWeek(lssn.StartDateDT) % nWeeks; //different number of layers according to lesson, e.g. 7 % 1 ->0, 7%2 -> 1, 7 % 13 - >7

            int weekStep = 1;
            if (periodicity == 0)
            {
                weekStep = nWeeks;
            }
            else if (periodicity == 2)
            {
                weekStep = 2;
            }

            for (int w = weekStart; w < nWeeks; w += weekStep)
            {
                for (int p = lssn.PeriodStart; p < (lssn.PeriodStart + lssn.Hours); p++)
                {
                    MainArray[day, p, w].CourseId = courseId;
                    MainArray[day, p, w].LessonTypeId = lessonTypeId;
                }
            }
        }

        protected int Score_Period()
        {
            // Calculates penalty for evening period
            int lastPeriod = 0;
            for (int i = _periodStart; i <= _periodEnd; i++)
            {
                if (TempArrayRow[i].CourseId != _emptySpotVal)
                {
                    lastPeriod = i;
                }
            }

            if (lastPeriod > _penaltyLatePeriodThreshold)
            {
                return _penaltyLatePeriod * (lastPeriod - _penaltyLatePeriodThreshold);   //by how much it reaches past the threshold
            }
            return 0;
        }
        protected int Score_Days()
        {
            //Give penalty for lssn being alone on the day
            int sumLessonHours = 0;
            for (int i = _periodStart; i <= _periodEnd; i++)
            {
                if (TempArrayRow[i].CourseId != _emptySpotVal)
                {
                    sumLessonHours++;
                }
            }

            if (sumLessonHours < _penaltyDayAlonePeriodsThreshold)
            {
                return _penaltyAloneInADay;
            }

            return 0;
        }
        protected int Score_GapsBetweenLessons()
        {
            //Count all empty spots
            int nGaps = 0;
            bool rowIsEmpty = true;
            for (int i = _periodStart; i <= _periodEnd; i++)   //at start
            {
                if (TempArrayRow[i].CourseId == _emptySpotVal)
                {
                    nGaps++;
                }
                else
                {
                    rowIsEmpty = false;
                }
            }

            if(!(rowIsEmpty)){
                //Subtract all start gaps
                for (int i = _periodStart; i <= _periodEnd; i++)
                {
                    if (TempArrayRow[i].CourseId == _emptySpotVal)
                    {
                        nGaps--;
                    }
                    else
                    {   //first lesson found
                        break;
                    }
                }
                //Subtract all end gaps
                for (int i = _periodEnd; i >= _periodStart; i--) //at end
                {
                    if (TempArrayRow[i].CourseId == _emptySpotVal)
                    {
                        nGaps--;
                    }
                    else
                    {
                        //last lesson found
                        break;
                    }
                }
                return nGaps * _penaltyGaps;
            }
            return 0;
        }
        protected int Score_CourseContinuity()
        {
            int score = 0;

            //Get sum of continuous blocks
            List<(int cid, int ltid, int pstart, int hours)> blocks = new List<(int, int, int,int)>();
            (int cid, int ltid, int pstart, int hours) = (_emptySpotVal, _emptySpotVal, _periodStart, 0);
            for (int i = _periodStart; i <= _periodEnd; i++)
            {
                int newcid = TempArrayRow[i].CourseId;
                int newltid = TempArrayRow[i].LessonTypeId;
                if ((cid != newcid)||(ltid != newltid))  //change occured
                {
                    blocks.Add((cid, ltid, pstart,hours));
                    cid = newcid;
                    ltid = newltid;
                    pstart = i;
                    hours = 1;
                }
                else
                {
                    hours++;
                }

                if (i == _periodEnd)
                {
                    blocks.Add((cid, ltid, pstart, hours));
                }
            }

            //Get if continous
            for (int i = 0; i < blocks.Count; i++)
            {
                if (blocks[i].cid != _emptySpotVal)
                {
                    for (int k = i + 1; k < blocks.Count; k++)
                    {
                        //Bonuses
                        if (blocks[i].cid == blocks[k].cid)
                        {
                            score += _bonusCourseSameDay;

                            if ((blocks[i].pstart + blocks[i].hours) == blocks[k].pstart)
                            {
                                score += _bonusCourseContinuous;
                            }

                            if (blocks[i].ltid == _lessonTypePreference)    //this type is before the other lesson types
                            {
                                score += _bonusCourseLessonTypePreference;
                            }

                        }
                    }
                }
            }

            return score;
        }

        protected int ScoreMain(int day, int week)
        {
            int scorePrev = 0;
            for (int i = _periodStart; i <= _periodEnd; i++)
            {
                TempArrayRow[i].CourseId = MainArray[day, i, week].CourseId;
                TempArrayRow[i].LessonTypeId = MainArray[day, i, week].LessonTypeId;
            }

            scorePrev += Score_Period();
            scorePrev += Score_Days();
            scorePrev += Score_GapsBetweenLessons();
            scorePrev += Score_CourseContinuity();

            return scorePrev;
        }
        protected int ScoreSingle(int day, int period, int week, int hours, int courseId, int lessonTypeId)
        {
            int scorePrev = ScoreMain(day, week);
            int scoreNew = 0;
            //Insert lesson
            for (int i = period; i < (period + hours); i++)
            {
                TempArrayRow[i] = (courseId, lessonTypeId);
            }
            //string sNew = "";
            scoreNew += Score_Period();
            //sNew += " SP: " + scoreNew.ToString();
            scoreNew += Score_Days();
            //sNew += " SD: " + scoreNew.ToString();
            scoreNew += Score_GapsBetweenLessons();
            //sNew += " SG: " + scoreNew.ToString();
            scoreNew += Score_CourseContinuity();
            //sNew += " SC: " + scoreNew.ToString();
            //Debug.WriteLine("NEW: " + sNew);

            //Debug.WriteLine("Dif: " + (scoreNew - scorePrev).ToString());

            return scoreNew - scorePrev;    //eg. 50 - 200 = -150 (better), eg. - 50 - 200 =-250(better),eg. 300 - 200 = 100 (worse)
        }      
        protected int ScoreFull()
        {
            int score = 0;
            for (int w = 0; w < nWeeks; w++)
            {
                for (int d = _dayStart; d <= _dayEnd; d++)
                {
                    for (int p = _periodStart; p <= _periodEnd; p++)
                    {
                        TempArrayRow[p].CourseId = MainArray[d, p, w].CourseId;
                        TempArrayRow[p].LessonTypeId = MainArray[d, p, w].LessonTypeId;
                    }
                    score += Score_Period();
                    score += Score_Days();
                    score += Score_GapsBetweenLessons();
                    score += Score_CourseContinuity();
                }
            }
            return score;
        }

    }
}
