
// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2024. Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Linq;
using System.Collections.Generic;

using Hiperscan.Unix;
using Hiperscan.Extensions.ReferenceCheck.Control.Enums;
using Hiperscan.Extensions.ReferenceCheck.Control.Evaluation;

namespace Hiperscan.Extensions.ReferenceCheck.Control.Acquisition
{
    public class SessionManager
    {
        private class MinMax
        {
            public int Min { get; set; }
            public int Max { get; set; }
        }

        private const int BLACK_WHITE_PER_SESSION = 5;
        private const int TRANSFLECTANCE_PER_SESSION = 5;

        public SessionManager(int black, int white, int transflectance, int insert)
        {
            this.BLack = black;
            this.White = white;
            this.Insert = insert;
            this.Transflectance = transflectance;
            this.Result = new ResultData();

            this.Calculate();
        }

        private void Calculate()
        {
            this.CheckMinCount();
            this.GetBlackWhiteSessions();
            this.GetInsertSessions();
            this.GetTransflectanceSessions();
            this.GetTotalSessions();
            this.CreateAllSessions();
        }


        private void CheckMinCount()
        {
            if (this.BLack < 1)
                throw new Exception(Catalog.GetString("At least one black reference is needed."));

            if (this.White < 1)
                throw new Exception(Catalog.GetString("At least one white reference is required."));
        }

        private void GetBlackWhiteSessions()
        {
            int black_sessions = this.GetSessionsCount(this.BLack, SessionManager.BLACK_WHITE_PER_SESSION);
            int white_sessions = this.GetSessionsCount(this.White, SessionManager.BLACK_WHITE_PER_SESSION);
            if (black_sessions == white_sessions)
            {
                this.BlackWhiteSessions.Min = black_sessions;
                //this.BlackWhiteSessions.Max = Math.Min(black_sessions, white_sessions);
                this.BlackWhiteSessions.Max = Math.Min(this.BLack, this.White);
            }
            else
            {
                this.BlackWhiteSessions.Min = Math.Max(black_sessions, white_sessions);
                this.BlackWhiteSessions.Max = Math.Min(this.BLack, this.White);

                if (this.BlackWhiteSessions.Min > this.BlackWhiteSessions.Max)
                    throw new Exception(Catalog.GetString("Cannot create a session with")); //TODO
            }
        }

        private void GetInsertSessions()
        {
            if (this.Insert == 0)
            {
                this.InsertSessions.Min = 0;
                this.InsertSessions.Max = 0;
            }
            else
            {
                this.InsertSessions.Min = this.GetSessionsCount(this.Insert, SessionManager.BLACK_WHITE_PER_SESSION);
                this.InsertSessions.Max = this.Insert;
            }

            if (this.InsertSessions.Min > this.BlackWhiteSessions.Max)
                throw new Exception(Catalog.GetString("Cannot create a session with")); //TODO
        }

        private void GetTransflectanceSessions()
        {
            if (this.Transflectance == 0)
            {
                this.TransflectanceSessions.Min = 0;
                this.TransflectanceSessions.Max = 0;
            }
            else
            {
                this.TransflectanceSessions.Min = this.GetSessionsCount(this.Transflectance, SessionManager.TRANSFLECTANCE_PER_SESSION);
                this.TransflectanceSessions.Max = this.Transflectance;
            }

            if (this.TransflectanceSessions.Min > this.BlackWhiteSessions.Max)
                throw new Exception(Catalog.GetString("Cannot create a session with")); //TODO
        }

        private void GetTotalSessions()
        {
            this.TotalSessions = Math.Max(this.BlackWhiteSessions.Min, this.InsertSessions.Min);
            this.TotalSessions = Math.Max(this.TotalSessions, this.TransflectanceSessions.Min);
        }

        private void CreateAllSessions()
        {
            this.AllSesstions = new Dictionary<AcquisitionType, AcquisitionSessions>();
            foreach (AcquisitionType item in Enum.GetValues(typeof(AcquisitionType)))
            {
                this.AllSesstions.Add(item, new AcquisitionSessions());
                for (int i = 1; i <= this.TotalSessions; i++)
                {
                    if (item == AcquisitionType.Empty)
                        this.AllSesstions[item].AddSession(i, 1);
                }
            }

            for (int i = this.TotalSessions; i > 0; i--)
            {
                int count = this.BLack / i;
                this.AllSesstions[AcquisitionType.Black].AddSession(i, count);
                this.BLack -= count;
            }

            for (int i = this.TotalSessions; i > 0; i--)
            {
                int count = this.White / i;
                this.AllSesstions[AcquisitionType.White].AddSession(i, count);
                this.White -= count;
            }

            for (int i = this.TotalSessions; i > 0; i--)
            {
                int count = this.Insert / i;
                this.AllSesstions[AcquisitionType.WhiteInsert].AddSession(i, count);
                this.Insert -= count;
            }

            for (int i = this.TotalSessions; i > 0; i--)
            {
                int count = this.Transflectance / i;
                this.AllSesstions[AcquisitionType.Transflectance].AddSession(i, count);
                this.Transflectance -= count;
            }

            for (int i = this.TotalSessions; i > 0; i--)
            {
                int number_of_acquisitions = 0;

                number_of_acquisitions += this.GetNumberOfAcquisitionById(AcquisitionType.Black, i);
                number_of_acquisitions += this.GetNumberOfAcquisitionById(AcquisitionType.White, i);
                number_of_acquisitions += this.GetNumberOfAcquisitionById(AcquisitionType.Transflectance, i);
                number_of_acquisitions += this.GetNumberOfAcquisitionById(AcquisitionType.WhiteInsert, i);
                number_of_acquisitions += this.GetNumberOfAcquisitionById(AcquisitionType.Empty, i);

                this.CalibrationSessions.AddSession(i, number_of_acquisitions);
            }
        }

        private int GetNumberOfAcquisitionById(AcquisitionType type, int id)
        {
            return this.AllSesstions[type].SessionQuue.
                Find(x => x.Id == id).NumberOfAcquisitions;
        }

        public void WriteSessionData()
        {
            Console.WriteLine("==========================================");
            Console.WriteLine("Session-Manager => Total-Sesstions: {0}", this.TotalSessions);
            Console.WriteLine("Session:         {0}", this.GetSessionsIds());
            Console.WriteLine("Back:            {0}", this.GetSessionNumbers(this.AllSesstions[AcquisitionType.Black]));
            Console.WriteLine("White:           {0}", this.GetSessionNumbers(this.AllSesstions[AcquisitionType.White]));
            Console.WriteLine("Transflectance:  {0}", this.GetSessionNumbers(this.AllSesstions[AcquisitionType.Transflectance]));
            Console.WriteLine("Insert:          {0}", this.GetSessionNumbers(this.AllSesstions[AcquisitionType.WhiteInsert]));
            Console.WriteLine("Empty:           {0}", this.GetSessionNumbers(this.AllSesstions[AcquisitionType.Empty]));
            Console.WriteLine("Calibration time {0}", this.GetCalibrationNumbers());
            Console.WriteLine("==========================================");
        }

        private string GetSessionsIds()
        {
            string sessions_ids = String.Empty;
            for (int i = 0; i < this.TotalSessions; i++)
                sessions_ids += String.Format("{0:D2} ", i + 1);

            return sessions_ids.Trim(); ;
        }

        private string GetSessionNumbers(AcquisitionSessions sessions)
        {
            string sessions_values = String.Empty;
            foreach (AcquisitionSession session in sessions.SessionQuue)
                sessions_values += String.Format("{0:D2} ", session.NumberOfAcquisitions);

            return sessions_values.Trim(); ;
        }

        private string GetCalibrationNumbers()
        {
            string sessions_values = String.Empty;
            foreach (CalibrationSession session in this.CalibrationSessions.SessionQuue)
                sessions_values += String.Format("{0:D2} ", session.TimeOutTimeMinutes);

            return sessions_values.Trim();
        }


        private int GetSessionsCount(int number_of_references, int number_per_session)
        {
            int sessions = number_of_references / number_per_session;
            if (number_of_references % number_per_session != 0)
                sessions++;

                return sessions;
        }

        public bool AreChangesNotSaved
        {
            get
            {
                if (this.IsRestored)
                    return true;

                return this.Operator != this.OldOperator;
            }
        }

        public bool HasValidOperatorName
        {
            get
            {
                if (this.IsRestored)
                    return true;

                return String.IsNullOrEmpty((this.Operator ?? String.Empty).Trim()) == false;
            }
        }

        public string CreationDate
        {
            get
            {
                CalibrationSession session = this.CalibrationSessions.SessionQuue.FirstOrDefault();

                if (session == null || session.DarkSpectrum == null)
                    return String.Empty;

                return session.DarkSpectrum.Timestamp.ToString("d");
            }
        }

        private int TotalSessions { get; set; }

        private MinMax BlackWhiteSessions { get; set; } = new MinMax();
        private MinMax InsertSessions { get; set; } = new MinMax();
        private MinMax TransflectanceSessions { get; set; } = new MinMax();

        private int BLack { get; set; }
        private int White { get; set; }
        private int Insert { get; set; }
        private int Transflectance { get; set; }

        public string Operator { get; set; } = String.Empty;
        public string OldOperator { get; set; } = String.Empty;
        public bool IsRestored { get; set; } = false;


        public Dictionary<AcquisitionType, AcquisitionSessions> AllSesstions { get; private set; }
        public CalibrationSessions CalibrationSessions { get; private set; } = new CalibrationSessions();
        internal ResultData Result { get; set; }
    }
}
