#region << Using Directives >>
using System;
using Volpe.Cafe.Data;
using Volpe.Cafe.Settings;
using Volpe.Cafe.Utils;
using Volpe.Cafe.Model;
using Volpe.Cafe.Generic;
#endregion
namespace Volpe.Cafe.IO.Reporting.CSV
{
    [Serializable]
    public sealed class CsvComplianceReport : CsvReportingBase
    {
        #region 
        static CsvComplianceReport()
        {
            int rcCount = RCValue<object>.Classes.Length;
            RegClasses = new RegulatoryClass[rcCount + 1];
            Array.Copy(RCValue<object>.Classes, RegClasses, rcCount);
            RegClasses[rcCount] = RegulatoryClass.All;
        }
        public CsvComplianceReport(string path, bool append) : base(path, append) { }
        #endregion
        #region 
        protected override string GetHeader()
        {
            return Header;
        }
        protected override void ParseData_Internal(Scenario scen, ModelYear year, ICompliance compliance)
        {
            Industry scenData = compliance.GetData(scen, year);
            Industry baseData = (scen.IsBaseline) ? null : compliance.GetData(compliance.Settings.Scenarios[0], year);
            Manufacturer[] scenMfrs = scenData.Manufacturers.ToArray();
            Manufacturer[] baseMfrs = (scen.IsBaseline) ? null : baseData.Manufacturers.ToArray();
            for (int i = 0; i < scenMfrs.Length; i++)
            {
                this.WriteComplianceData(scen, year, scenData, scenMfrs[i], scenMfrs[i].ModelData, scenMfrs[i].Vehicles.ToArray(),
                    (scen.IsBaseline) ? null : baseMfrs[i].ModelData, compliance.Settings);
            }
            Manufacturer.CModelData scenImd = this.AggregateIndustryData(scenMfrs);
            Manufacturer.CModelData baseImd = (scen.IsBaseline) ? null : this.AggregateIndustryData(baseMfrs);
            this.WriteComplianceData(scen, year, scenData, null, scenImd, scenData.Vehicles.ToArray(), baseImd, compliance.Settings);
            if (year.Year == scenData.MinYear.Year)
            {
                this._scenMmdSum = new Manufacturer.CModelData[scenMfrs.Length];
                this._baseMmdSum = new Manufacturer.CModelData[scenMfrs.Length];
                for (int i = 0; i < scenMfrs.Length; i++)
                {
                    this._scenMmdSum[i] = new Manufacturer.CModelData();
                    if (!scen.IsBaseline) { this._baseMmdSum[i] = new Manufacturer.CModelData(); }
                }
                this._scenImdSum = new Manufacturer.CModelData();
                if (!scen.IsBaseline) { this._baseImdSum = new Manufacturer.CModelData(); }
            }
            for (int i = 0; i < scenMfrs.Length; i++)
            {
                this.AggregateModelYearData(this._scenMmdSum[i], scenMfrs[i].ModelData);
                if (!scen.IsBaseline) { this.AggregateModelYearData(this._baseMmdSum[i], baseMfrs[i].ModelData); }
            }
            this.AggregateModelYearData(this._scenImdSum, scenImd);
            if (!scen.IsBaseline) { this.AggregateModelYearData(this._baseImdSum, baseImd); }
            if (year.Year == scenData.MaxYear.Year)
            {   
                for (int i = 0; i < scenMfrs.Length; i++)
                {
                    this.WriteComplianceData(scen, null, scenData, scenMfrs[i], this._scenMmdSum[i], null, this._baseMmdSum[i], null);
                }
                this.WriteComplianceData(scen, null, scenData, null, this._scenImdSum, null, this._baseImdSum, null);
            }
        }
        void WriteComplianceData(Scenario scen, ModelYear year, Industry ind, Manufacturer scenMfr,
            Manufacturer.CModelData scenMD, Vehicle[] scenVehs, Manufacturer.CModelData baseMD, ModelingSettings settings)
        {
            RCDouble scenSales = scenMD.Sales;
            RCDouble baseSales = (scen.IsBaseline) ? RCDouble.Zero : baseMD.Sales;
            int rcCount = 0;
            foreach (RegulatoryClass regClass in RegClasses)
            {
                if (scenSales.GetValue(regClass) != 0 || baseSales.GetValue(regClass) != 0)
                {
                    if (rcCount > 1 || regClass != RegulatoryClass.All)
                    {
                        this.WriteComplianceData(scen, year, regClass, ind, scenMfr, scenMD, scenVehs, baseMD, settings);
                        rcCount++;
                    }
                }
            }
        }
        void WriteComplianceData(Scenario scen, ModelYear year, RegulatoryClass regClass, Industry scenData, Manufacturer scenMfr,
            Manufacturer.CModelData scenMD, Vehicle[] scenVehs, Manufacturer.CModelData baseMD, ModelingSettings settings)
        {
            this.Write(scen.Index);
            this.Write(Interaction.GetTitleCase(scen.Description));
            this.Write(year     == null                ? "TOTAL" : year.ToString());
            this.Write(scenMfr  == null                ? "TOTAL" : scenMfr.Description.ProperName);
            this.Write(regClass == RegulatoryClass.All ? "TOTAL" : regClass.ToString());
            double scenSales = scenMD.Sales.GetValue(regClass);
            double baseSales = (scen.IsBaseline) ? 0 : baseMD.Sales.GetValue(regClass);
            this.Write(scenSales);
            if (scenMfr == null)
            {
                if (year == null) { this.Write(scenData.CalcLaborHours(scenData.MinYear, scenData.MaxYear, regClass)); }
                else              { this.Write(scenData.CalcLaborHours(year, regClass)); }
            }
            else
            {
                if (year == null) { this.Write(scenMfr.CalcLaborHours(scenData.MinYear, scenData.MaxYear, regClass)); }
                else              { this.Write(scenMfr.CalcLaborHours(year, regClass)); }
            }
            if (year == null) { this.WriteEmpty(9); }
            else
            {
                RCDouble avgCW, avgFP, avgWF;
                this.ComputeAverageCWFP(scen, year, scenVehs, out avgCW, out avgFP, out avgWF);
                RCDouble co2Target = Standards.GetVehicleCO2Target(scenVehs, scen, year, settings);
                RCDouble co2Rating = Standards.GetVehicleCO2Rating(scenVehs, scen, year, settings);
                if (regClass == RegulatoryClass.All)
                {
                    this.Write(scenMD.PreliminaryStandard.HarmonicMean  (scenMD.Sales));
                    this.Write(scenMD.Standard           .HarmonicMean  (scenMD.Sales));
                    this.Write(scenMD.CAFE_2Bag          .HarmonicMean  (scenMD.Sales));
                    this.Write(scenMD.CAFE               .HarmonicMean  (scenMD.Sales));
                    this.Write(       avgCW              .ArithmeticMean(scenMD.Sales));
                    this.Write(       avgFP              .ArithmeticMean(scenMD.Sales));
                    this.Write(       avgWF              .ArithmeticMean(scenMD.Sales));
                    this.Write(       co2Target          .ArithmeticMean(scenMD.Sales));
                    this.Write(       co2Rating          .ArithmeticMean(scenMD.Sales));
                }
                else
                {
                    this.Write(scenMD.PreliminaryStandard[regClass]);
                    this.Write(scenMD.Standard           [regClass]);
                    this.Write(scenMD.CAFE_2Bag          [regClass]);
                    this.Write(scenMD.CAFE               [regClass]);
                    this.Write(       avgCW              [regClass]);
                    this.Write(       avgFP              [regClass]);
                    this.Write(       avgWF              [regClass]);
                    this.Write(       co2Target          [regClass]);
                    this.Write(       co2Rating          [regClass]);
                }
            }
            double[] scenCosts = new double[] {
                scenMD.TechCost           .GetValue(regClass),
                scenMD.Fines              .GetValue(regClass),
                scenMD.RegCost            .GetValue(regClass),
                scenMD.DiscCost           .GetValue(regClass),
                scenMD.LossOfValue        .GetValue(regClass),
                scenMD.RelativeLossOfValue.GetValue(regClass),
                scenMD.MaintenanceCost    .GetValue(regClass),
                scenMD.RepairCost         .GetValue(regClass),
                scenMD.TaxesAndFees       .GetValue(regClass),
                scenMD.FinancingCost      .GetValue(regClass),
                scenMD.InsuranceCost      .GetValue(regClass),
                scenMD.TotalConsumerCosts .GetValue(regClass),
                scenMD.TotalSocialCosts   .GetValue(regClass) };
            double[] baseCosts = (scen.IsBaseline) ? EmptyCosts : new double[] {
                baseMD.TechCost           .GetValue(regClass),
                baseMD.Fines              .GetValue(regClass),
                baseMD.RegCost            .GetValue(regClass),
                baseMD.DiscCost           .GetValue(regClass),
                baseMD.LossOfValue        .GetValue(regClass),
                baseMD.RelativeLossOfValue.GetValue(regClass),
                baseMD.MaintenanceCost    .GetValue(regClass),
                baseMD.RepairCost         .GetValue(regClass),
                baseMD.TaxesAndFees       .GetValue(regClass),
                baseMD.FinancingCost      .GetValue(regClass),
                baseMD.InsuranceCost      .GetValue(regClass),
                baseMD.TotalConsumerCosts .GetValue(regClass),
                baseMD.TotalSocialCosts   .GetValue(regClass) };
            for (int i = 0; i < scenCosts.Length; i++)
            {
                this.Write(scenCosts[i] - baseCosts[i]);
            }
            for (int i = 0; i < scenCosts.Length; i++)
            {
                this.Write(((scenSales == 0) ? 0 : scenCosts[i] / scenSales) - ((baseSales == 0) ? 0 : baseCosts[i] / baseSales));
            }
            this.Write(scenMD.Credits    .GetValue(regClass));
            this.Write(scenMD.TCreditsOut.GetValue(regClass));
            this.Write(scenMD.TCreditsIn .GetValue(regClass));
            this.NewRow();
        }
        void WriteDelta(RCDouble scenValue, RCDouble baseValue, RegulatoryClass regClass)
        {
            this.Write(scenValue.GetValue(regClass) - baseValue.GetValue(regClass));
        }
        void ComputeAverageCWFP(Scenario scen, ModelYear year, Vehicle[] vehs, out RCDouble avgCW, out RCDouble avgFP, out RCDouble avgWF)
        {
            RCDouble sales = new RCDouble();
            avgCW = new RCDouble();
            avgFP = new RCDouble();
            avgWF = new RCDouble();
            for (int i = 0; i < vehs.Length; i++)
            {
                Vehicle              veh      = vehs[i];
                Vehicle.CDescription vd       = veh.Description;
                RegulatoryClass      regClass = veh.RegClass;
                double               vehSales = vd .Sales[year.Index];
                sales[regClass] += vehSales;
                avgCW[regClass] += vehSales * vd.CurbWeight;
                avgFP[regClass] += vehSales * vd.Footprint;
                avgWF[regClass] += vehSales * Standards.GetWorkFactor(veh, scen, year);
            }
            foreach (RegulatoryClass regClass in RCDouble.Classes)
            {
                if (sales[regClass] != 0)
                {
                    avgCW[regClass] /= sales[regClass];
                    avgFP[regClass] /= sales[regClass];
                    avgWF[regClass] /= sales[regClass];
                }
            }
        }
        Manufacturer.CModelData AggregateIndustryData(Manufacturer[] mfrs)
        {
            Manufacturer.CModelData imd = new Manufacturer.CModelData();
            for (int i = 0; i < mfrs.Length; i++)
            {
                Manufacturer.CModelData mmd = mfrs[i].ModelData;
                imd.Sales               += mmd.Sales;
                imd.TechCost            += mmd.TechCost;
                imd.Fines               += mmd.Fines;
                imd.RegCost             += mmd.RegCost;
                imd.DiscCost            += mmd.DiscCost;
                imd.LossOfValue         += mmd.LossOfValue;
                imd.RelativeLossOfValue += mmd.RelativeLossOfValue;
                imd.MaintenanceCost     += mmd.MaintenanceCost;
                imd.RepairCost          += mmd.RepairCost;
                imd.TaxesAndFees        += mmd.TaxesAndFees;
                imd.FinancingCost       += mmd.FinancingCost;
                imd.InsuranceCost       += mmd.InsuranceCost;
                imd.TotalConsumerCosts  += mmd.TotalConsumerCosts;
                imd.TotalSocialCosts    += mmd.TotalSocialCosts;
                imd.Credits             += mmd.Credits;
                imd.TCreditsOut         += mmd.TCreditsOut;
                imd.TCreditsIn          += mmd.TCreditsIn;
                foreach (RegulatoryClass regClass in RCDouble.Classes)
                {
                    if (mmd.PreliminaryStandard[regClass] != 0) { imd.PreliminaryStandard[regClass] += mmd.Sales[regClass] / mmd.PreliminaryStandard[regClass]; }
                    if (mmd.Standard           [regClass] != 0) { imd.Standard           [regClass] += mmd.Sales[regClass] / mmd.Standard           [regClass]; }
                    if (mmd.CAFE_2Bag          [regClass] != 0) { imd.CAFE_2Bag          [regClass] += mmd.Sales[regClass] / mmd.CAFE_2Bag          [regClass]; }
                    if (mmd.CAFE               [regClass] != 0) { imd.CAFE               [regClass] += mmd.Sales[regClass] / mmd.CAFE               [regClass]; }
                }
            }
            foreach (RegulatoryClass regClass in RCDouble.Classes)
            {
                imd.PreliminaryStandard[regClass] = (imd.PreliminaryStandard[regClass] == 0) ? 0 : imd.Sales[regClass] / imd.PreliminaryStandard[regClass];
                imd.Standard           [regClass] = (imd.Standard           [regClass] == 0) ? 0 : imd.Sales[regClass] / imd.Standard           [regClass];
                imd.CAFE_2Bag          [regClass] = (imd.CAFE_2Bag          [regClass] == 0) ? 0 : imd.Sales[regClass] / imd.CAFE_2Bag          [regClass];
                imd.CAFE               [regClass] = (imd.CAFE               [regClass] == 0) ? 0 : imd.Sales[regClass] / imd.CAFE               [regClass];
            }
            return imd;
        }
        void AggregateModelYearData(Manufacturer.CModelData sum, Manufacturer.CModelData value)
        {
            sum.Sales               += value.Sales;
            sum.TechCost            += value.TechCost;
            sum.Fines               += value.Fines;
            sum.RegCost             += value.RegCost;
            sum.DiscCost            += value.DiscCost;
            sum.LossOfValue         += value.LossOfValue;
            sum.RelativeLossOfValue += value.RelativeLossOfValue;
            sum.MaintenanceCost     += value.MaintenanceCost;
            sum.RepairCost          += value.RepairCost;
            sum.TaxesAndFees        += value.TaxesAndFees;
            sum.FinancingCost       += value.FinancingCost;
            sum.InsuranceCost       += value.InsuranceCost;
            sum.TotalConsumerCosts  += value.TotalConsumerCosts;
            sum.TotalSocialCosts    += value.TotalSocialCosts;
            sum.Credits             += value.Credits;
            sum.TCreditsOut         += value.TCreditsOut;
            sum.TCreditsIn          += value.TCreditsIn;
        }
        #endregion
        #region 
        public override string ReportName { get { return CsvReportName; } }
        #endregion
        #region 
        static readonly RegulatoryClass[] RegClasses;
        public const string CsvReportName = "Compliance CSV Report";
        const string Header =
            "Scenario,Scenario Name,Model Year,Manufacturer,Reg-Class," +
            "Sales,k.Labor Hours,Prelim-Stnd,Standard,CAFE (2-cycle),CAFE,Average CW,Average FP,Average WF,CO2 Required,CO2 Achieved," +
            "Tech Cost,Fines,Reg-Cost,Disc Cost,Value Loss,Rel. Value Loss,Maint Cost,Repair Cost,Taxes/Fees,Financing,Insurance,Total Consumer Costs,Total Social Costs," +
            "Avg Tech Cost,Avg Fines,Avg Reg-Cost,Avg Disc Cost,Avg Value Loss,Avg Rel. Value Loss,Avg Maint Cost,Avg Repair Cost,Avg Taxes/Fees,Avg Financing,Avg Insurance,Avg Consumer Costs,Avg Social Costs," +
            "Credits Earned,Credits Out,Credits In";
        static readonly double[] EmptyCosts = new double[13];
        Manufacturer.CModelData[] _scenMmdSum, _baseMmdSum;
        Manufacturer.CModelData   _scenImdSum, _baseImdSum;
        #endregion
    }
}

