using System;
using Volpe.Cafe.Data;
using Volpe.Cafe.Settings;
namespace Volpe.Cafe.Model
{
    sealed class CreditTrading
    {
        public static bool CanCarryForward(RegulatoryClass rc, Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex,
            ModelingSettings settings)
        {
            CreditTradingValues ct = settings.Parameters.CreditTradingValues;
            if (!settings.OperatingModes.AllowCreditTrading || !ct.AllowCarryForward) { return false; }
            Manufacturer.CModelData mmd = modelData[year.Index].Manufacturers[mfrIndex].ModelData;
            double credits = ComputeNetCredits(mmd, rc);
            if (credits >= 0) { return false; }
            int minYearIndex = modelData[year.Index].MinYear.Index;
            for (int i = minYearIndex; i < year.Index; i++)
            {   
                int carryFwdYears = scen.ScenInfo[rc].CreditCarryFwd[i];
                if (carryFwdYears == 0)
                {   
                    carryFwdYears = ct.CarryForwardYears;
                }
                if (i + carryFwdYears >= year.Index)
                {   
                    Manufacturer.CModelData pMMD = modelData[i].Manufacturers[mfrIndex].ModelData;
                    double eCredits = Math.Max(0, pMMD.Credits[rc] - pMMD.TCreditsOut[rc]);
                    if (eCredits > 0) { return true; }
                }
            }
            return false;
        }
        public static bool CanFleetTransfer(RegulatoryClass rc, Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex,
            ModelingSettings settings)
        {
            CreditTradingValues ct = settings.Parameters.CreditTradingValues;
            if (!settings.OperatingModes.AllowCreditTrading || !ct.AllowFleetTransfers) { return false; }
            Manufacturer.CModelData mmd = modelData[year.Index].Manufacturers[mfrIndex].ModelData;
            double credits = ComputeNetCredits(mmd, rc);
            double cap = ComputeTransferCap(mmd, rc, year, ct);
            if (cap <= 0 || credits >= 0) { return false; }
            if (rc == RegulatoryClass.PassengerCar)
            {   
                if (mmd.PreliminaryStandard[rc] < mmd.CAFE[rc]) { return false; }
            }
            RegulatoryClass eRC = CT_FleetTransfers_GetNextRC(rc);
            if (eRC != RegulatoryClass.None)
            {
                double eRC_creditBalance = ComputeNetCredits(mmd, eRC);
                if (eRC_creditBalance > 0) { return true; }
                if (ct.AllowCarryForward &&
                    eRC_creditBalance >= 0 &&
                    CanCarryForward(eRC, modelData, scen, year, mfrIndex, settings)) { return true; }
            }
            return false;
        }
        public static void ExamineCredits(Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex, ModelingSettings settings)
        {
            CT_CarryForward  (modelData, scen, year, mfrIndex, settings, false);    
            CT_FleetTransfers(modelData, scen, year, mfrIndex, settings, false);    
            CT_CarryBackward (modelData, scen, year, mfrIndex, settings       );    
        }
        public static void CT_CarryForward(Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex, ModelingSettings settings,
            bool expiringOnly)
        {
            CT_CarryForward(RegulatoryClass.PassengerCar , modelData, scen, year, mfrIndex, settings, expiringOnly);
            CT_CarryForward(RegulatoryClass.LightTruck   , modelData, scen, year, mfrIndex, settings, expiringOnly);
            CT_CarryForward(RegulatoryClass.LightTruck2b3, modelData, scen, year, mfrIndex, settings, expiringOnly);
        }
        public static void CT_CarryForward(RegulatoryClass rc, Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex,
            ModelingSettings settings, bool expiringOnly)
        {
            CreditTradingValues ct = settings.Parameters.CreditTradingValues;
            if (!settings.OperatingModes.AllowCreditTrading || !ct.AllowCarryForward) { return; }
            string                  mfrName = modelData[year.Index].Manufacturers[mfrIndex].Description.Name;
            Manufacturer.CModelData mmd     = modelData[year.Index].Manufacturers[mfrIndex].ModelData;
            double                  credits = ComputeNetCredits(mmd, rc);
            if (credits >= 0) { return; }
            int minYearIndex = modelData[year.Index].MinYear.Index;
            for (int i = minYearIndex; i < year.Index; i++)
            {   
                int carryFwdYears = scen.ScenInfo[rc].CreditCarryFwd[i];
                if (carryFwdYears == 0)
                {   
                    carryFwdYears = ct.CarryForwardYears;
                }
                if (expiringOnly && (i > year.Index - carryFwdYears)) { continue; }
                if (i + carryFwdYears < year.Index) { continue; }
                Manufacturer.CModelData pMMD = modelData[i].Manufacturers[mfrIndex].ModelData;
                double eCredits = Math.Max(0, pMMD.Credits[rc] - pMMD.TCreditsOut[rc]);
                if (eCredits <= 0) { continue; }
                CreditAdjustmentMode cfAdjMode = CreditAdjustmentMode.Fixed;
                DoTransfer(mfrName, eCredits, -credits, cfAdjMode, ct, rc, ModelYear.NewModelYearFromIndex(i), pMMD, rc, year, mmd);
                credits = ComputeNetCredits(mmd, rc);
                if (credits >= 0) { return; }
            } 
        }
        public static void CT_FleetTransfers(Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex, ModelingSettings settings,
            bool expiringOnly)
        {
            CT_FleetTransfers(RegulatoryClass.PassengerCar , modelData, scen, year, mfrIndex, settings, false);
            CT_FleetTransfers(RegulatoryClass.LightTruck   , modelData, scen, year, mfrIndex, settings, false);
            CT_FleetTransfers(RegulatoryClass.LightTruck2b3, modelData, scen, year, mfrIndex, settings, false);
        }
        public static void CT_FleetTransfers(RegulatoryClass rc, Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex,
            ModelingSettings settings, bool expiringOnly)
        {
            CreditTradingValues ct = settings.Parameters.CreditTradingValues;
            if (!settings.OperatingModes.AllowCreditTrading || !ct.AllowFleetTransfers) { return; }
            string                  mfrName = modelData[year.Index].Manufacturers[mfrIndex].Description.Name;
            Manufacturer.CModelData mmd     = modelData[year.Index].Manufacturers[mfrIndex].ModelData;
            double                  credits = ComputeNetCredits (mmd, rc);
            double                  cap     = ComputeTransferCap(mmd, rc, year, ct);
            if (cap <= 0 || credits >= 0) { return; }
            if (rc == RegulatoryClass.PassengerCar)
            {   
                if (mmd.PreliminaryStandard[rc] < mmd.CAFE[rc]) { return; }
            }
            double rCredits = Math.Min(cap, -credits);
            RegulatoryClass eRC = CT_FleetTransfers_GetNextRC(rc);
            if (eRC == RegulatoryClass.None) { return; }
            if (ct.AllowCarryForward && ComputeNetCredits(mmd, eRC) >= 0)
            {
                int minYearIndex = modelData[year.Index].MinYear.Index;
                for (int j = minYearIndex; j < year.Index; j++)
                {   
                    int carryFwdYears = scen.ScenInfo[rc].CreditCarryFwd[j];
                    if (carryFwdYears == 0)
                    {   
                        carryFwdYears = ct.CarryForwardYears;
                    }
                    if (expiringOnly && (j > year.Index - carryFwdYears)) { continue; }
                    if (j + carryFwdYears < year.Index) { continue; }
                    Manufacturer.CModelData pMMD = modelData[j].Manufacturers[mfrIndex].ModelData;
                    double eCredits = Math.Max(0, pMMD.Credits[eRC] - pMMD.TCreditsOut[eRC]); 
                    if (eCredits <= 0) { continue; }  
                    double outECredits, outRCredits;
                    RequestCredits(eCredits, rCredits, ct.AdjustmentMode, ct, eRC, year, mmd, rc, year, mmd, out outECredits, out outRCredits);
                    CreditAdjustmentMode cfAdjMode = CreditAdjustmentMode.Fixed;
                    DoTransfer(mfrName, outECredits, double.PositiveInfinity, cfAdjMode, ct, eRC, ModelYear.NewModelYearFromIndex(j), pMMD, eRC, year, mmd);
                    eCredits = ComputeNetCredits(mmd, eRC);
                    DoTransfer(mfrName, eCredits, outRCredits, ct.AdjustmentMode, ct, eRC, year, mmd, rc, year, mmd);
                    credits = ComputeNetCredits (mmd, rc);
                    cap     = ComputeTransferCap(mmd, rc, year, ct);
                    if (cap <= 0 || credits >= 0) { return; }   
                    rCredits = Math.Min(cap, -credits);         
                }
            }
            if (expiringOnly) { return; }   
            if (ComputeNetCredits(mmd, eRC) > 0)
            {
                double eCredits = ComputeNetCredits(mmd, eRC);
                DoTransfer(mfrName, eCredits, rCredits, ct.AdjustmentMode, ct, eRC, year, mmd, rc, year, mmd);
                credits = ComputeNetCredits(mmd, rc);
                cap = ComputeTransferCap(mmd, rc, year, ct);
                if (cap <= 0 || credits >= 0) { return; }   
                rCredits = Math.Min(cap, -credits);         
            }
        }
        static RegulatoryClass CT_FleetTransfers_GetNextRC(RegulatoryClass uRC)
        {
            if (uRC == RegulatoryClass.PassengerCar) { return RegulatoryClass.LightTruck; }
            if (uRC == RegulatoryClass.LightTruck  ) { return RegulatoryClass.PassengerCar; }
            return RegulatoryClass.None;
        }
        public static void CT_CarryBackward(Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex, ModelingSettings settings)
        {
            CT_CarryBackward(RegulatoryClass.PassengerCar , modelData, scen, year, mfrIndex, settings);
            CT_CarryBackward(RegulatoryClass.LightTruck   , modelData, scen, year, mfrIndex, settings);
            CT_CarryBackward(RegulatoryClass.LightTruck2b3, modelData, scen, year, mfrIndex, settings);
        }
        public static void CT_CarryBackward(RegulatoryClass rc, Industry[] modelData, Scenario scen, ModelYear year, int mfrIndex,
            ModelingSettings settings)
        {
            CreditTradingValues ct = settings.Parameters.CreditTradingValues;
            if (!settings.OperatingModes.AllowCreditTrading || !ct.AllowCarryBackward || ct.CarryBackwardYears < 1) { return; }
            Manufacturer.CModelData mmd = modelData[year.Index].Manufacturers[mfrIndex].ModelData;
            double eCredits = ComputeNetCredits(mmd, rc);
            if (eCredits <= 0) { return; }
            int cbSYrIndex = ComputeCarryBwdStartYr(modelData, year, ct);
            int minYearIndex = modelData[year.Index].MinYear.Index;
            for (int i = minYearIndex; i < year.Index; i++)
            {
                string                  mfrName = modelData[i].Manufacturers[mfrIndex].Description.Name;
                Manufacturer.CModelData pMMD    = modelData[i].Manufacturers[mfrIndex].ModelData;
                double                  credits = ComputeNetCredits(pMMD, rc);
                if (credits >= 0) { continue; }
                CreditAdjustmentMode cfAdjMode = CreditAdjustmentMode.Fixed;
                DoTransfer(mfrName, eCredits, -credits, cfAdjMode, ct, rc, year, mmd, rc, ModelYear.NewModelYearFromIndex(i), pMMD);
                eCredits = ComputeNetCredits(mmd, rc);
                if (eCredits <= 0) { return; }
            } 
        }
        [Obsolete("cannot use ComputeCarryFwdStartYr anymore since credit expiration changes by origin year", true)]
        static int ComputeCarryFwdStartYr(Industry[] modelData, ModelYear year, CreditTradingValues ct)
        {
            return Math.Max(modelData[year.Index].MinYear.Index, year.Index - ct.CarryForwardYears);
        }
        static int ComputeCarryBwdStartYr(Industry[] modelData, ModelYear year, CreditTradingValues ct)
        {
            return Math.Max(modelData[year.Index].MinYear.Index, year.Index - ct.CarryBackwardYears);
        }
        static double ComputeNetCredits(Manufacturer.CModelData mmd, RegulatoryClass rc)
        {
            return mmd.Credits[rc] + mmd.TCreditsIn[rc] - mmd.TCreditsOut[rc];
        }
        static double ComputeTransferCap(Manufacturer.CModelData mmd, RegulatoryClass rc, ModelYear year, CreditTradingValues ct)
        {
            double cap = ct.GetTransferCaps(rc, year.Year);
            return Math.Floor(cap * mmd.Sales[rc]) - mmd.TCreditsInCapped[rc];
        }
        static double ComputeVMT(CreditTradingValues ct, RegulatoryClass rc, ModelYear year)
        {
            return ct.GetAdjustmentVMT(rc, year.Year);
        }
        static void DoTransfer(string mfrName, double eCredits, double rCredits, CreditAdjustmentMode adjMode, CreditTradingValues ct,
            RegulatoryClass eRC, ModelYear eYear, Manufacturer.CModelData eMMD,     
            RegulatoryClass uRC, ModelYear uYear, Manufacturer.CModelData uMMD)     
        {
            double outECredits, outRCredits;
            RequestCredits(eCredits, rCredits, adjMode, ct, eRC, eYear, eMMD, uRC, uYear, uMMD, out outECredits, out outRCredits);
            eMMD.TCreditsOut[eRC] += outECredits;
            uMMD.TCreditsIn [uRC] += outRCredits;
            if (eRC != uRC) { uMMD.TCreditsInCapped[uRC] += outRCredits; }
        }
        static void RequestCredits(double eCredits, double rCredits, CreditAdjustmentMode adjMode, CreditTradingValues ct,
            RegulatoryClass eRC, ModelYear eYear, Manufacturer.CModelData eMMD,     
            RegulatoryClass uRC, ModelYear uYear, Manufacturer.CModelData uMMD,     
            out double outECredits, out double outRCredits)
        {
            double eVMT = 0, uVMT = 0;
            if (adjMode == CreditAdjustmentMode.Variable)
            {
                eVMT = ComputeVMT(ct, eRC, eYear);
                uVMT = ComputeVMT(ct, uRC, uYear);
            }
            RequestCredits(eCredits, rCredits, adjMode,
                eVMT, eMMD.CAFE[eRC], eMMD.Standard[eRC], uVMT, uMMD.CAFE[uRC], uMMD.Standard[uRC],
                out outECredits, out outRCredits);
        }
        static void RequestCredits(double eCredits, double rCredits, CreditAdjustmentMode adjMode,
            double eVMT, double eCAFE, double eSTND, double uVMT, double uCAFE, double uSTND,
            out double outECredits, out double outRCredits)
        {
            if (double.IsInfinity(rCredits) && double.IsInfinity(eCredits)) { outECredits = outRCredits = 0; return; }
            double adjFactor = GetCreditAdjFactor(adjMode, eVMT, eCAFE, eSTND, uVMT, uCAFE, uSTND);
            if (!double.IsInfinity(eCredits))
            {
                outRCredits = Math.Floor(eCredits / adjFactor);
                if (double.IsInfinity(rCredits) || outRCredits <= rCredits)
                {   
                    outECredits = Math.Ceiling(outRCredits * adjFactor);
                    return;
                }
            }
            outECredits = Math.Ceiling(rCredits    * adjFactor);
            outRCredits = Math.Floor  (outECredits / adjFactor);
        }
        #region 
        static double GetCreditAdjFactor(CreditAdjustmentMode adjMode, double eVMT, double eCAFE, double eSTND, double uVMT, double uCAFE, double uSTND)
        {
            switch (adjMode)
            {
                case CreditAdjustmentMode.Fixed   : return 1;
                case CreditAdjustmentMode.Variable: return (uVMT * eCAFE * eSTND) / (eVMT * uCAFE * uSTND);
                default: throw new ArgumentException("The Credit Adjustment Factor is not valid.");
            }
        }
        public static double GetCreditAdjFactorFT(RegulatoryClass eRC, RegulatoryClass uRC, Manufacturer mfr, ModelYear year, CreditTradingValues ct)
        {
            double eVMT = 0, uVMT = 0;
            if (ct.AdjustmentMode == CreditAdjustmentMode.Variable)
            {
                eVMT = ComputeVMT(ct, eRC, year);
                uVMT = ComputeVMT(ct, uRC, year);
            }
            return GetCreditAdjFactor(ct.AdjustmentMode, eVMT, mfr.ModelData.CAFE[eRC], mfr.ModelData.Standard[eRC],
                                                         uVMT, mfr.ModelData.CAFE[uRC], mfr.ModelData.Standard[uRC]);
        }
        #endregion
    }
}

