#define FLEET
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace PHEMlightdll
{
    public class Start
    {
        private List<string> _DataPath;
        private CEPHandler DataInput;
        public Helpers Helper = new Helpers();

        //******************* Parameters of Array or Single calculation *******************
        //********************************* INPUT ******************************************
        //***  DATATYP              |  UNIT   |  VARIBLE                  |     Description  ***
        //List<string>              |  [-]    |   DataFiles (VEH, FC, EMI)| Name of file (e.g. "PC_D_EU4" path neede if not in "Default Vehicles") or aggregated name (PC, HDV, BUS, TW) by FleetMix calculation
        //List<double> / double     |  [s]    |   Time                    | Time signal
        //List<double> / double     |  [m/s]  |   Velocity                | Velocity signal
        //double                    |  [m/s^2]|   acc                     | Acceleration (ONLY NEDDED BY SINGLE CALCULATION)
        //List<double> / double     |  [%]    |   Gradient                | Gradient of the route
        //out List<VehicleResult>   |  [-]    |   VehicleResultsOrg       | Returned result list
        //bool                      |  [-]    |   fleetMix = false        | Optional parameter if fleetMix should be calculate
        //string                    |  [-]    |   CommentPref = "c"       | Optional parameter for comment prefix

        //********************************* OUPUT: VehicleResultsOrg **********************
        //***  DATATYP              |  UNIT   |  VARIBLE       |      Description  ***
        //string                    |  [-]    |   vehicle      | Name of the vehicle
        //string                    |  [-]    |   cycle        | Name of the cycle
        //double                    |  [s]    |   time         | Time
        //double                    |  [m/s]  |   speed        | Velocity
        //double                    |  [kW]   |   power        | Calculated power at the engine (ICE for conventional and HEV vehicles, electric engine for BEVs) including engine inertia and auxiliaries; not limited for engine fullload and braking limitations
        //double                    |  [kW]   |   P_pos        | Positive engine power limited with engine rated power
        //double                    |  [-]    |   pNormRated   | Engine power normalised with rated engine power and limited with the power range (fullload and drag) as specified in the characteristic curve for fuel consumption
        //double                    |  [-]    |   pNormDrive   | Engine power normalised with "P_drive" and limited with the power range (fullload and drag) as specified in the characteristic curve for emissions
        //double                    |  [m/s^2]|   acc          | Caclulated/given acceleration
        //Dictionary<string, double>|  [*]    |   Emissiondata | Calculated emissions for all components which are defined in the emission curves. Unit dependent of emission component


        #region calculate
        //Calculate data from array
        public bool CALC_Array(List<string> DataFiles,
                               List<double> Time,
                               List<double> Velocity,
                               List<double> Gradient,
                               out List<VehicleResult> VehicleResultsOrg,
                               bool fleetMix = false,
                               Correction DataCor = null,
                               string CommentPref = "c")
        {
            //Declaration
            int i;
            double acc;
            List<VehicleResult>  _VehicleResult = new List<VehicleResult>();

            //Initialisation
            Helper.ErrMsg = null;

            //Borrow
            Helper.CommentPrefix = CommentPref;
            _DataPath = new List<string>();
            //Set path by normal calculation (on given) and set path by fleetmix (on Default Vehicles) calculation
            for (i = 0; i < DataFiles.Count; i++)
            {
                if ((DataFiles[i].LastIndexOf(@"\")) >= 0)
                {
                    _DataPath.Add(DataFiles[i]);
                }
                else
                {
                    //_DataPath.Add(Assembly.GetExecutingAssembly().Location.Substring(0, Assembly.GetExecutingAssembly().Location.LastIndexOf(@"\")) + @"\Default Vehicles\" + Helper.PHEMDataV);
                    _DataPath.Add(DataFiles[i + 1].Substring(0, DataFiles[i + 1].LastIndexOf(@"\")));
                    _DataPath.Add(DataFiles[i + 1].Substring(0, DataFiles[i + 1].LastIndexOf(@"\")));
                    _DataPath.Add(DataFiles[i + 1].Substring(0, DataFiles[i + 1].LastIndexOf(@"\")));
                    i += 1;
                }
            }

            //Read the vehicle and emission data
            #if FLEET
            if (fleetMix)
            {
                //Set the vehicle class
                Helper.gClass = _DataPath[0];

                //Generate the class
                DataInput = new CEPHandler();

                //Read the FleetShares
                if (!DataInput.ReadFleetShares(DataFiles[1], Helper))
                {
                    VehicleResultsOrg = null;
                    return false;
                }
                //Read the vehicle and emission data
                if (!DataInput.GetFleetCEP(_DataPath, DataFiles[0], Helper, DataCor))
                {
                    VehicleResultsOrg = null;
                    return false;
                }
            }
            else
            #endif
            {
                //Get vehicle string
                if (!Helper.setclass(DataFiles[0]))
                {
                    VehicleResultsOrg = null;
                    return false;
                }

                //Generate the class
                DataInput = new CEPHandler();

                //Read the vehicle and emission data
                if (!DataInput.GetCEP(_DataPath, Helper, DataCor))
                {
                    VehicleResultsOrg = null;
                    return false;
                }
            }

            //Calculate emissions per second
            for (i = 1; i <= Time.Count - 1; i++)
            {
                //Calculate the acceleration
                acc = (Velocity[i] - Velocity[i - 1]) / (Time[i] - Time[i - 1]);

                //Calculate and save the data in the List
                _VehicleResult.Add(PHEMLight.CreateVehicleStateData(Helper,
                                                                    DataInput.CEPS[Helper.gClass],
                                                                    Time[i - 1],
                                                                    Velocity[i - 1],
                                                                    acc,
                                                                    Gradient[i - 1]));
                if (Helper.ErrMsg != null)
                {
                    VehicleResultsOrg = null;
                    return false;
                }
            }
            VehicleResultsOrg = _VehicleResult;
            return true;
        }

        //Calculate single data
        public bool CALC_Single(List<string> DataFiles,
                                double Time,
                                double Velocity,
                                double acc,
                                double Gradient,
                                out List<VehicleResult> VehicleResultsOrg,
                                bool fleetMix = false,
                                Correction DataCor = null,
                                string CommentPref = "c")
        {
            //Declaration
            List<VehicleResult> _VehicleResult = new List<VehicleResult>();
            VehicleResultsOrg = _VehicleResult;

            //Borrow
            Helper.CommentPrefix = CommentPref;
            _DataPath = new List<string>();
            //Set path by normal calculation (on given) and set path by fleetmix (on Fleetshare file) calculation
            for (int i = 0; i < DataFiles.Count; i++)
            {
                if ((DataFiles[i].LastIndexOf(@"\")) >= 0)
                {
                    _DataPath.Add(DataFiles[i]);
                }
                else
                {
                    //_DataPath.Add(Assembly.GetExecutingAssembly().Location.Substring(0, Assembly.GetExecutingAssembly().Location.LastIndexOf(@"\")) + @"\Default Vehicles\" + Helper.PHEMDataV);
                    _DataPath.Add(DataFiles[i + 1].Substring(0, DataFiles[i + 1].LastIndexOf(@"\")));
                    _DataPath.Add(DataFiles[i + 1].Substring(0, DataFiles[i + 1].LastIndexOf(@"\")));
                    _DataPath.Add(DataFiles[i + 1].Substring(0, DataFiles[i + 1].LastIndexOf(@"\")));
                    i += 1;
                }
            }

            //Read the vehicle and emission data
            #if FLEET
            if (fleetMix)
            {
                //Set the vehicle class
                Helper.gClass = "AggClass_" + DataFiles[0];

                //Generate the class
                DataInput = new CEPHandler();

                //Read the FleetShares
                if (!DataInput.ReadFleetShares(DataFiles[1], Helper))
                {
                    VehicleResultsOrg = null;
                    return false;
                }
                //Read the vehicle and emission data
                if (!DataInput.GetFleetCEP(_DataPath, DataFiles[0], Helper, DataCor))
                {
                    VehicleResultsOrg = null;
                    return false;
                }
            }
            else
            #endif
            {
                //Get vehicle string
                if (!Helper.setclass(DataFiles[0]))
                {
                    VehicleResultsOrg = null;
                    return false;
                }

                //Generate the class
                DataInput = new CEPHandler();

                //Read the vehicle and emission data
                if (!DataInput.GetCEP(_DataPath, Helper, DataCor))
                {
                    VehicleResultsOrg = null;
                    return false;
                }
            }

            //Calculate and save the data in the List
            _VehicleResult.Add(PHEMLight.CreateVehicleStateData(Helper,
                                                                DataInput.CEPS[Helper.gClass],
                                                                Time,
                                                                Velocity,
                                                                acc,
                                                                Gradient));
            VehicleResultsOrg = _VehicleResult;
            return true;
        }
        #endregion

        #region ExportData
        private Dictionary<string, cErgEntry> ErgEntries = new Dictionary<string, cErgEntry>();
        private List<string> ErgEntryList = new List<string>();                                   //Needed because Dictionary is not sorted

        //Add to ERG file
        private void AddToErg(string IDstring, string Head, string Unit)
        {
            if (!ErgEntries.ContainsKey(IDstring))
            {
                ErgEntries.Add(IDstring, new cErgEntry(Head, Unit));
                ErgEntryList.Add(IDstring);
            }
        }

        //Create result head
        private string ErgHead()
        {
            //Declaration
            StringBuilder s = new StringBuilder();
            string key = null;
            bool First = true;

            foreach (string key_loopVariable in ErgEntryList)
            {
                key = key_loopVariable;
                if (!First)
                    s.Append(",");
                s.Append(ErgEntries[key].Head);
                First = false;
            }
            //Return value
            return s.ToString();
        }

        //Create result units
        private string ErgUnits()
        {
            //Declaration
            StringBuilder s = new StringBuilder();
            bool First = true;
            string key = null;

            foreach (string key_loopVariable in ErgEntryList)
            {
                key = key_loopVariable;
                if (!First)
                    s.Append(",");
                s.Append(ErgEntries[key].Unit);
                First = false;
            }

            //Return value
            return s.ToString();
        }

        //Output sequence for the emissions
        private void OutSeq(VehicleResult _VehicleResult, bool STA = false, bool add = false)
        {
            string Unit = "/km";
            List<string> OutSeqStr = new List<string> { "FC", "FC_EL", "CO2", "NOX", "CO", "HC", "PM", "PN" };

            if (STA) Unit = "/h";

            if (!add)
            {
                //Clear the result arrays
                ErgEntries.Clear();
                ErgEntryList.Clear();

                AddToErg("FC", "FC", "[g" + Unit + "]");
                AddToErg("FC_EL", "Engine Power", "[kWh" + Unit + "]");
                AddToErg("CO2", "CO2", "[g" + Unit + "]");
                AddToErg("NOX", "NOx", "[g" + Unit + "]");
                AddToErg("CO", "CO", "[g" + Unit + "]");
                AddToErg("HC", "HC", "[g" + Unit + "]");
                AddToErg("PM", "PM", "[g" + Unit + "]");
                AddToErg("PN", "PN", "[#" + Unit + "]");

                foreach (string id in _VehicleResult.EmissionData.Emi.Keys)
                {
                    if (!OutSeqStr.Contains(id)) AddToErg(id.ToUpper(), id, "[g" + Unit + "]");
                }
            }
            else
            {
                foreach (string id in _VehicleResult.EmissionData.Emi.Keys)
                {
                    if (!ErgEntries.ContainsKey(id.ToUpper())) AddToErg(id.ToUpper(), id, "[g" + Unit + "]");
                }
            }
        }

        //Export the data
        public bool ExportData(string path, List<VehicleResult> _VehicleResult)
        {
            if (path == null || _VehicleResult == null || _VehicleResult.Count == 0) return false;

            //Write head
            StringBuilder allLines = new StringBuilder();
            string lineEnding = "\r\n";

            //Produce emission output sequence. Only first one needed because should be always the same for a single vehicle
            OutSeq(_VehicleResult[0], true);

            //Vehicle type
            allLines.AppendLine("Vehicletype: ," + _VehicleResult[0].Vehicle);

            //Header and unit
            allLines.AppendLine("Time, Speed, Gradient, Accelaration, Engine power raw, P_pos, P_norm_rated, P_norm_drive," + ErgHead());
            allLines.AppendLine("[s], [m/s], [%], [m/s^2], [kW], [kW], [-], [-]," + ErgUnits());

            //Write data
            foreach (VehicleResult Result in _VehicleResult)
            {
                allLines.Append(Result.Time.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Speed.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Grad.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Accelaration.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Power.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.PPos.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.PNormRated.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.PNormDrive.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                foreach (string id in ErgEntryList)
                {
                    if (Result.EmissionData.Emi.ContainsKey(id))
                        allLines.Append(Result.EmissionData.Emi[id].ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                    else
                        allLines.Append("-,");
                }
                allLines.Append(lineEnding);
            }

            // Write the string to a file.
            if (path.IndexOf(".", 0) < 0)
            {
                path = path + ".sta";
            }
            try
            {
                StreamWriter file = new StreamWriter(path);
                file.WriteLine(allLines);
                file.Close();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        //Export summerized data
        public bool ExportSumData(string path, List<VehicleResult> _VehicleResult)
        {
            if (path == null || _VehicleResult == null) return false;
            StringBuilder allLines = new StringBuilder();

            if (path.IndexOf(".", 0) < 0)
            {
                path = path + ".erg";
            }

            //Produce emission output sequence for all calculated vehicles
            OutSeq(_VehicleResult[0], false);
            foreach (VehicleResult Result in _VehicleResult)
            {
                OutSeq(Result, false, true);
            }

            if (!File.Exists(path))
            {
                //Write head
                allLines.AppendLine("PHEMLight Results");
                allLines.AppendLine("");
                allLines.AppendLine("Vehicle, Cycle, Time, Speed, Gradient, Accelaration, Engine power raw, P_pos, P_norm_rated, P_norm_drive," + ErgHead());
                allLines.AppendLine("[-], [-], [s], [km/h], [%], [m/s^2], [kW], [kW], [-], [-]," + ErgUnits());
            }

            //Write data
            foreach (VehicleResult Result in _VehicleResult)
            {
                allLines.Append(Result.Vehicle + ",");
                allLines.Append(Result.Cycle + ",");
                allLines.Append(Result.Time.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Speed.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Grad.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Accelaration.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.Power.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.PPos.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.PNormRated.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                allLines.Append(Result.PNormDrive.ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                foreach (string id in ErgEntryList)
                {
                    if (Result.EmissionData.Emi.ContainsKey(id))
                        allLines.Append(Result.EmissionData.Emi[id].ToString("0.0000", CultureInfo.InvariantCulture) + ",");
                    else
                        allLines.Append("-,");
                }
                allLines.Append("\r\n");
            }
            // Write the string to a file.
            try
            {
                StreamWriter file = new StreamWriter(path, true);
                file.WriteLine(allLines);
                file.Close();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        public VehicleResult GenerateSumData(List<VehicleResult> _VehicleResult)
        {
            //Declaration
            string vehicle = "";
            string cycle = "";
            double sum_time = 0;
            double sum_speed = 0;
            double sum_grad = 0;
            double sum_power = 0;
            double sum_pPos = 0;
            double sum_pNormRated = 0;
            double sum_pNormDrive = 0;
            double sum_acc = 0;
            Dictionary<string, double> sum_Emi = new Dictionary<string, double>();

            if (_VehicleResult == null || _VehicleResult.Count == 0) return new VehicleResult("", "", 0, 0, 0, 0, 0, 0, 0, 0, sum_Emi);

            //Vehicle and cycle are always the same here
            vehicle = _VehicleResult[0].Vehicle;
            cycle = _VehicleResult[0].Cycle;

            //Write data
            foreach (VehicleResult Result in _VehicleResult)
            {
                sum_speed += Result.Speed * 3.6;
                sum_power += Result.Power;
                if (Result.PPos > 0) { sum_pPos += Result.PPos; }
                sum_grad += Result.Grad;
                sum_pNormRated += Result.PNormRated;
                sum_pNormDrive += Result.PNormDrive;
                sum_acc += Result.Accelaration;
                foreach(string id in Result.EmissionData.Emi.Keys)
                {
                    if (sum_Emi.ContainsKey(id))
                        sum_Emi[id] += Result.EmissionData.Emi[id];
                    else
                        sum_Emi.Add(id, Result.EmissionData.Emi[id]);
                }
            }

            //Build average
            sum_time = _VehicleResult[_VehicleResult.Count - 1].Time - _VehicleResult[0].Time;
            sum_power /= _VehicleResult.Count;
            sum_pPos /= _VehicleResult.Count;
            sum_grad /= _VehicleResult.Count;
            sum_pNormRated /= _VehicleResult.Count;
            sum_pNormDrive /= _VehicleResult.Count;
            sum_acc /= _VehicleResult.Count;
            if (sum_speed > 0)
            {
                foreach (string id in sum_Emi.Keys.ToList())
                {
                    sum_Emi[id] /= sum_speed;
                }
            }
            else
            {
                foreach (string id in sum_Emi.Keys)
                {
                    sum_Emi[id] = 0;
                }
            }
            sum_speed /= _VehicleResult.Count;

            return new VehicleResult(vehicle,
                                     cycle,
                                     sum_time,
                                     sum_speed,
                                     sum_grad,
                                     sum_power,
                                     sum_pPos,
                                     sum_pNormRated,
                                     sum_pNormDrive,
                                     sum_acc,
                                     sum_Emi);
        }
        #endregion
    }

    //Calculation
    class PHEMLight
    {
        #region CreateVehicleStateData
        static public VehicleResult CreateVehicleStateData(Helpers Helper,
                                                           CEP currCep,
                                                           double time,
                                                           double inputSpeed,
                                                           double inputAcc,
                                                           double Gradient = 0,
                                                           Correction DataCor = null)
        {
            //Declaration
            double speed = Math.Max(inputSpeed, 0);
            double acc;
            double P_pos;

            //Speed/Acceleration limitation
            if (speed == 0)
                acc = 0;
            else
                acc = Math.Min(inputAcc, currCep.GetMaxAccel(speed, Gradient, (Helper.pClass == Constants.strBEV | Helper.uClass == Constants.strHybrid)));

            //Calculate the power
            double power = currCep.CalcPower(speed, acc, Gradient, (Helper.pClass == Constants.strBEV | Helper.uClass == Constants.strHybrid));
            double P_eng = currCep.CalcEngPower(power);
            double Pwheel = 0;
            if (Helper.uClass == Constants.strHybrid) Pwheel = currCep.CalcWheelPower(speed, acc, Gradient);
            //Power limitation
            if (P_eng >= 0)
                P_pos = power;
            else
                P_pos = 0;

            //Calculate the result values (BEV)
            if (Helper.pClass == Constants.strBEV)
            {
                return new VehicleResult(Helper.gClass,
                                         "",
                                         time,
                                         speed,
                                         Gradient,
                                         P_eng,
                                         P_pos,
                                         P_eng / currCep.RatedPower,
                                         P_eng / currCep.DrivingPower,
                                         acc,
                                         currCep.GetAllEmission(P_eng, speed, Helper));
            }

            //Calculate the decel costing
            double decelCoast = currCep.GetDecelCoast(speed, acc, Gradient);

            //Calculate the result values (Zero emissions by costing, Idling emissions by v <= 0.5m / s²)
            if (acc >= decelCoast || speed <= Constants.ZERO_SPEED_ACCURACY)
            {
                if (Helper.uClass == Constants.strHybrid)
                    return new VehicleResult(Helper.gClass,
                         "",
                         time,
                         speed,
                         Gradient,
                         P_eng,
                         P_pos,
                         P_eng / currCep.RatedPower,
                         P_eng / currCep.DrivingPower,
                         acc,
                         currCep.GetAllEmission(Pwheel, speed, Helper));
                else
                    return new VehicleResult(Helper.gClass,
                                             "",
                                             time,
                                             speed,
                                             Gradient,
                                             P_eng,
                                             P_pos,
                                             P_eng / currCep.RatedPower,
                                             P_eng / currCep.DrivingPower,
                                             acc,
                                             currCep.GetAllEmission(P_eng, speed, Helper));
            }
            else
            {
                if (Helper.uClass == Constants.strHybrid)
                    return new VehicleResult(Helper.gClass,
                     "",
                     time,
                     speed,
                     Gradient,
                     P_eng,
                     P_pos,
                     P_eng / currCep.RatedPower,
                     P_eng / currCep.DrivingPower,
                     acc,
                     currCep.GetAllEmission(Pwheel, speed, Helper, true));
                else
                    return new VehicleResult(Helper.gClass,
                                         "",
                                         time,
                                         speed,
                                         Gradient,
                                         P_eng,
                                         P_pos,
                                         P_eng / currCep.RatedPower,
                                         P_eng / currCep.DrivingPower,
                                         acc,
                                         currCep.GetAllEmission(P_eng, speed, Helper, true));
            }
        }
        #endregion
    }
}
