using CarbonService.Models;
using CarbonLib.Carbon.Models;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;

namespace CarbonLib.Carbon
{
    public class CarbonCalculation25 : ICarbonCalculation
    {
        private readonly string configPath;

        private SetsForDataTransformation dataTransformationSet;
        private GIS_Soils_Sets gisSoilSet;
        private CropSets cropSet;
        private ParameterSet parameterSet;
        private FarmInputsetsModel farmInputSet;

        private List<CropYieldModel> cropYieldConfig;
        private List<ManCcontModel> manCcontConfig;
        private List<CropPropertyModel> cropPropertyConfig;
        private List<YieldGreenManureModel> yieldGreenManureConfig;
        private List<CropCoverMonthsModel> cropCoverMonthConfig;

        private List<MappingCropFarmmapsMiterraRothC> mappingCropFarmmapsMiterraRothC;
        private List<MappingGreenManureFarmmapsMiterraRothC> mappingGreenManureFarmmapsMiterraRothC;

        public CarbonCalculation25(string configPath)
        {
            this.configPath = configPath;

            var dataTransformationSetFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "SetsForDataTransformation.json"));
            var gisSoilFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "GIS_Soil_Sets.json"));
            var cropSetFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "CropSets.json"));
            var parameterRothCFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "ParametersRothC.json"));
            var farmInputSetFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "Farm_input_sets.json"));

            var cropYieldFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "CropYield.json"));
            var manCcontFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "ManCcont.json"));
            var cropPropertyFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "CropProperties.json"));
            var yieldGreenManureFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "YieldGreenManure.json"));
            var cropCoverMonthFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "CropCoverMonth.json"));

            var mappingCropFarmmapsMiterraFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "Mapping", "MappingCropsFarmmapsMiterraRothC.json"));
            var mappingGreenManureFarmmapsMiterraFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "Mapping", "MappingGreenManureFarmmapsMiterraRothC.json"));

            // sets
            //var regionSet = GamsSets.ReadSets<RegionSets>(regionSetFile);
            dataTransformationSet = GamsSets.ReadSets<SetsForDataTransformation>(dataTransformationSetFile);
            gisSoilSet = GamsSets.ReadSets<GIS_Soils_Sets>(gisSoilFile);
            cropSet = GamsSets.ReadSets<CropSets>(cropSetFile);
            parameterSet = GamsSets.ReadSets<ParameterSet>(parameterRothCFile);
            farmInputSet = GamsSets.ReadSets<FarmInputsetsModel>(farmInputSetFile);

            cropYieldConfig = GamsSets.ReadSets<List<CropYieldModel>>(cropYieldFile);
            manCcontConfig = GamsSets.ReadSets<List<ManCcontModel>>(manCcontFile);
            cropPropertyConfig = GamsSets.ReadSets<List<CropPropertyModel>>(cropPropertyFile);
            yieldGreenManureConfig = GamsSets.ReadSets<List<YieldGreenManureModel>>(yieldGreenManureFile);
            cropCoverMonthConfig = GamsSets.ReadSets<List<CropCoverMonthsModel>>(cropCoverMonthFile);

            // mapping
            mappingCropFarmmapsMiterraRothC = GamsSets.ReadSets<List<MappingCropFarmmapsMiterraRothC>>(mappingCropFarmmapsMiterraFile);
            mappingGreenManureFarmmapsMiterraRothC = GamsSets.ReadSets<List<MappingGreenManureFarmmapsMiterraRothC>>(mappingGreenManureFarmmapsMiterraFile);

        }

        public string Validate(CarbonRequest farmInput)
        {
            var postalCodePresent = dataTransformationSet.KNMI_zone_Reg.Any(x => x.Reg.Any(y => y == farmInput.PostalCode));
            if (!postalCodePresent)
            {
                return $"Unknown postalcode (reg) {farmInput.PostalCode}";
            }

            var allCrops = farmInput.CropFields.SelectMany(s => s.HistoricalCropData.SelectMany(x => x.Crops)).Distinct();
            foreach (var crop in allCrops)
            {
                if (string.IsNullOrEmpty(crop.Crop))
                {
                    return "Missing crop";
                }
                var cropPresent = cropPropertyConfig.Any(x => x.Name.ToLower() == crop.Crop.ToLower());
                if (!cropPresent)
                {
                    return $"Unknown crop {crop.Crop}";
                }
            }


            var allSoils = farmInput.CropFields.Select(s => s.SoilType);
            foreach (var soil in allSoils)
            {
                if (!gisSoilSet.SoilType.Any(x => x == soil))
                {
                    return $"Unknown soil {soil}";
                }
            }

            var allGreenManure = farmInput.CropFields.SelectMany(s => s.GreenManures.SelectMany(x => x.GreenManureTypes)).Distinct();
            foreach (var greenManure in allGreenManure)
            {
                if (!yieldGreenManureConfig.Any(x => x.ManureName.ToLower() == greenManure.Type.ToLower()))
                {
                    return $"Unknown green manure {greenManure.Type}";
                }
            }

            var allOrgManure = farmInput.CropFields.SelectMany(x => x.HistoricalCropData.SelectMany(s => s.Crops.Where(w => w.OrganicManures != null).SelectMany(y => y.OrganicManures.Select(r => r.Type)))).Distinct();
            foreach (var orgManure in allOrgManure)
            {
                if (!manCcontConfig.Any(x => x.ManCcont == orgManure))
                {
                    return $"Unknown organic manure {orgManure}";
                }
            }

            //var allYears
            return null;
        }

        public CarbonRequest MapFarmMapsToMiterra(FarmMapsCarbonRequest farmInput)
        {
            var mappedInput = new CarbonRequest();
            mappedInput.PostalCode = farmInput.PostalCode;
            mappedInput.CropFields = new List<CropField>();
            foreach (var cropField in farmInput.CropFields)
            {
                var mappedCropData = new List<HistoricalCropData>();
                var greenManures = new List<GreenManure>();
                mappedInput.CropFields.Add(new CropField
                {
                    Name = cropField.Name,
                    Area = cropField.Area,
                    HistoricalCropData = mappedCropData,
                    SoilType = MapSoil(cropField.SoilType),
                    SoilProperty = new SoilProperty { Clay_Content = cropField.SoilProperty.Clay_Content, OM_Const = cropField.SoilProperty.OM_Const, C_Const = cropField.SoilProperty.C_Const, Depth10 = cropField.SoilProperty.Depth10, Depth = cropField.SoilProperty.Depth, OM_Choice = cropField.SoilProperty.OM_Choice },
                    GreenManures = greenManures
                });
                foreach (var crops in cropField.HistoricalCropData)
                {
                    foreach (var cropYear in crops.Crops)
                    {
                        if (!string.IsNullOrEmpty(cropYear.MiterraCropName))
                        {
                            mappedCropData.Add(new HistoricalCropData
                            {
                                Year = crops.Year,
                                Crops = new List<OrgManureApplied> {
                                new OrgManureApplied {
                                    Crop = cropYear.MiterraCropName,
                                    CropRes = cropYear.CropRes,
                                    OrganicManures =
                                    cropYear.OrganicManures.Where(y=>y.Quantity > 0 && !string.IsNullOrEmpty(y.Type)).Select(x => new OrganicManureType { Type = x.Type, Quantity = x.Quantity.HasValue? x.Quantity.Value : 0 }).ToList(),
                                    CropYield = cropYear.CropYield,
                                    Irrigation = crops.Irrigations} }
                            });
                        }
                        else
                        {
                            var cropMapping = mappingCropFarmmapsMiterraRothC.FirstOrDefault(x => x.CropCode == cropYear.CropCode);
                            if (cropMapping != null)
                            {
                                var orgManure = new List<OrganicManureType>();
                                if (cropYear.OrganicManures != null)
                                {
                                    foreach (var manure in cropYear.OrganicManures)
                                    {
                                        if (!string.IsNullOrEmpty(manure.Type) && manure.Quantity.HasValue && manure.Quantity > 0)
                                        {
                                            orgManure.Add(new OrganicManureType { Type = manure.Type, Quantity = manure.Quantity.HasValue ? manure.Quantity.Value : 0 });
                                        }
                                    }
                                }
                                var crop = mappedCropData.SingleOrDefault(x => x.Year == crops.Year);
                                if (crop == null)
                                {
                                    mappedCropData.Add(new HistoricalCropData { Year = crops.Year, Crops = new List<OrgManureApplied> { new OrgManureApplied { Crop = cropMapping.MiterraRothCCrop, OrganicManures = orgManure, CropYield = cropYear.CropYield, CropRes = cropYear.CropRes } } });
                                }
                                else
                                {
                                    crop.Crops.Add(new OrgManureApplied { Crop = cropMapping.MiterraRothCCrop, OrganicManures = orgManure, CropYield = cropYear.CropYield, CropRes = cropYear.CropRes });
                                }
                            }
                            else
                            {
                                mappedCropData.Add(new HistoricalCropData { Year = crops.Year, Crops = new List<OrgManureApplied> { new OrgManureApplied { Crop = cropYear.CropCode, OrganicManures = null, CropYield = cropYear.CropYield, CropRes = cropYear.CropRes } } });
                            }
                        }
                    }

                    foreach (var cropYear in crops.GreenManures)
                    {
                        if (string.IsNullOrEmpty(cropYear.CropCode))
                        {
                            continue;
                        }
                        var greenManure = cropYear.CropCode;// mappingGreenManureFarmmapsMiterraRothC.FirstOrDefault(x => x.CropCode == cropYear.CropCode && x.PurposeCode == "118");
                        var manureYear = greenManures.SingleOrDefault(x => x.Year == crops.Year);
                        if (manureYear == null)
                        {
                            greenManures.Add(new GreenManure { Year = crops.Year, GreenManureTypes = new List<GreenManureType> { new GreenManureType { Type = greenManure, Quality = cropYear.Quality == QualityFarmMaps.Good ? Quality.Good : cropYear.Quality == QualityFarmMaps.Bad ? Quality.Bad : Quality.Average } } });
                        }
                    }
                }
            }
            return mappedInput;
        }

        public double? GetCropYield(string cropCode, int year, int postalCode)
        {
            string provinceCode = GetProvinceCode(dataTransformationSet, postalCode);
            var crpYield = cropYieldConfig.SingleOrDefault(x => x.CropName.ToLower() == cropCode.ToLower());
            if (crpYield == null)
            {
                return 0;
            }
            var crpYieldProvince = crpYield.Yields.SingleOrDefault(x => x.ProvinceCode == provinceCode);
            if (crpYieldProvince == null)
            {
                return 0;
            }
            var cropYield = crpYieldProvince.YearData.SingleOrDefault(x => x.Year == year)?.YieldValue;
            return cropYield;
        }

        public CarbonSummary CalculateClimateScenario(CarbonRequest farmInput)
        {
            var simpleResult = Calculate(farmInput);
            var carbonResultList = new List<CarbonSummary>();
            var knmiFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "KNMI_data.csv"));
            var climateFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "klimaatscenario.csv"));

            for (int i = 1; i < 9; i++)
            {
                var climateData = InitClimateFile(knmiFile, climateFile, i);
                var result = Calculate(farmInput, i, climateData);
                carbonResultList.Add(result);
            }

            var carbonTotal = new List<CarbonSummaryList>();



            var summaryTotal = new CarbonSummaryList { CropFieldClimate = new List<CropFieldSummaryList>(), CarbonSummary = simpleResult };
            var cropFieldSummaryList = new CropFieldSummaryList { Name = carbonResultList[0].CropField[0].Name, SummaryList = new List<YearSummaryClimate>() };

            for (var year = 2011; year < 2051; year++)
            {
                var yearData = carbonResultList.SelectMany(x => x.CropField[0].Summary.Where(x => x.Year == year)).ToList();
                if (yearData.Count == 0)
                {
                    continue;
                }
                var buldensityData = GetMinMaxMedian(yearData, year, "Bulk_Density");
                var balanceData = GetMinMaxMedian(yearData, year, "Balance");
                var initialData = GetMinMaxMedian(yearData, year, "Initial");
                var om_iniData = GetMinMaxMedian(yearData, year, "OM_ini");
                var iniCcontentData = GetMinMaxMedian(yearData, year, "IniCcontent");
                var co2seqData = GetMinMaxMedian(yearData, year, "CO2seq");
                var soc_CarbonCompostData = GetMinMaxMedian(yearData, year, "SOC_CarbonCompost");
                var soc_CarbonGreenManure = GetMinMaxMedian(yearData, year, "SOC_CarbonGreenManure");
                var soc_CarbonResiduesData = GetMinMaxMedian(yearData, year, "SOC_CarbonResidues");
                var soc_CarbonManureManure = GetMinMaxMedian(yearData, year, "SOC_CarbonManure");
                var emisionData = GetMinMaxMedian(yearData, year, "Emision");
                cropFieldSummaryList.SummaryList.Add(new YearSummaryClimate
                {
                    Year = year,
                    Bulk_Density = buldensityData,
                    Balance = balanceData,
                    Initial = initialData,
                    IniCcontent = iniCcontentData,
                    OM_ini = om_iniData,
                    CO2seq = co2seqData,
                    SOC_CarbonCompost = soc_CarbonCompostData,
                    SOC_CarbonGreenManure = soc_CarbonGreenManure,
                    SOC_CarbonResidues = soc_CarbonResiduesData,
                    SOC_CarbonManure = soc_CarbonManureManure,
                    Emision = emisionData,
                    Crop = yearData[0].Crop,
                    Total_crop_area = yearData[0].Total_crop_area,
                });
            }
            summaryTotal.CropFieldClimate.Add(cropFieldSummaryList);

            foreach (var yearSummary in summaryTotal.CarbonSummary.CropField[0].Summary)
            {
                //var yearValues = summaryTotal.CropFieldClimate.Where(x => x.SummaryList[0].Year == yearSummary.Year).Select(y => y.SummaryList[0]).ToList();
                var yearValues = summaryTotal.CropFieldClimate[0].SummaryList.Where(x => x.Year == yearSummary.Year).ToList();
                foreach (var cropFieldClimate in summaryTotal.CropFieldClimate)
                {
                    var yeardata = cropFieldClimate.SummaryList.Where(x => x.Year == yearSummary.Year);
                    var test = yeardata.Select(x => x.Bulk_Density).First()[1];
                }
                var Bulk_Density = yearValues.Select(x => x.Bulk_Density).First()[1];
                var balance = yearValues.Select(x => x.Balance).First()[1];
                var Initial = yearValues.Select(x => x.Initial).First()[1];
                var IniCcontent = yearValues.Select(x => x.IniCcontent).First()[1];
                var OM_ini = yearValues.Select(x => x.OM_ini).First()[1];
                var CO2seq = yearValues.Select(x => x.CO2seq).First()[1];
                var SOC_CarbonCompost = yearValues.Select(x => x.SOC_CarbonCompost).First()[1];
                var SOC_CarbonGreenManure = yearValues.Select(x => x.SOC_CarbonGreenManure).First()[1];
                var SOC_CarbonResidues = yearValues.Select(x => x.SOC_CarbonResidues).First()[1];
                var SOC_CarbonManure = yearValues.Select(x => x.SOC_CarbonManure).First()[1];
                var Emision = yearValues.Select(x => x.Emision).First()[1];
                yearSummary.Balance = balance;
                yearSummary.Bulk_Density = Bulk_Density;
                yearSummary.Initial = Initial;
                yearSummary.IniCcontent = IniCcontent;
                yearSummary.OM_ini = OM_ini;
                yearSummary.OM_ini_Climate = yearValues.Select(x => x.OM_ini).First();
                yearSummary.CO2seq = CO2seq;
                yearSummary.SOC_CarbonCompost = SOC_CarbonCompost;
                yearSummary.SOC_CarbonGreenManure = SOC_CarbonGreenManure;
                yearSummary.SOC_CarbonResidues = SOC_CarbonResidues;
                yearSummary.SOC_CarbonManure = SOC_CarbonManure;
                yearSummary.Emision = Emision;
            }


            return summaryTotal.CarbonSummary;
        }

        private List<double> GetMinMaxMedian(List<YearSummary> yearData, int year, string varName)
        {

            if (yearData.Count == 0)
            {

            }
            var minValue = (double)yearData.Min(s => s.GetType().GetProperty(varName).GetValue(s));
            var medianValue = CalculateMedian(yearData.Select(s => Convert.ToDouble(s.GetType().GetProperty(varName).GetValue(s))).ToList());
            var maxValue = (double)yearData.Max(s => s.GetType().GetProperty(varName).GetValue(s));
            return new List<double> { minValue, medianValue, maxValue };
        }

        public CarbonSummary Calculate(CarbonRequest farmInput)
        {
            return Calculate(farmInput, 0);
        }

        public CarbonSummary Calculate(CarbonRequest farmInput, int climateScenarioNumber = 0, Dictionary<(string, string, int), ClimateVariable> climate = null)
        {
            var knmiFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "KNMI_data.csv"));
            var climateFile = Helper.GetPlatformIndependentDirectoryPath(Path.Combine(configPath, "klimaatscenario.csv"));

            // soilTypeData (external input)
            var soilTypeData = GetSoilTypeData(farmInput);

            //soilPropertiesField (external input)
            var soilPropertiesField = GetSoilPropertiesField(farmInput);

            // HistoricalCropData (external input)
            var historicalCropdata = GetHistoricalCropdata(farmInput);

            // cropAreaSheet (external input)
            var cropAreaField = GetCropAreaField(farmInput);

            var year = GetYears(farmInputSet, farmInput);


            //////////////////////////////////////////////
            // precalculations
            //////////////////////////////////////////////


            //soilPropSoilType
            var soilPropSoilType = GetSoilPropSoilType(soilTypeData, soilPropertiesField);

            //if OM_cont > 0, then C_cont = OM_cont *0.54, else take C_cont from input
            foreach (var item in soilPropertiesField)
            {
                var soilProps = soilPropSoilType.Keys.Where(x => x.Item1 == item.Key).ToList();
                double? sumC_Cont = null;
                foreach (var soilProp in soilProps)
                {
                    if (soilPropSoilType[soilProp].OM_Cont > 0)
                    {
                        if (sumC_Cont == null)
                        {
                            sumC_Cont = soilPropSoilType[soilProp].OM_Cont * 0.54;
                        }
                        else
                        {
                            sumC_Cont += soilPropSoilType[soilProp].OM_Cont * 0.54;
                        }
                    }
                }
                if (sumC_Cont.HasValue)
                {
                    item.Value.C_Cont = sumC_Cont.Value;
                }
            }

            var oM_corr_SoilType = new Dictionary<(string, string), double>();
            var c_corr_SoilType = new Dictionary<(string, string), double>();
            foreach (var item in soilPropertiesField)
            {
                var soilOfItem = soilTypeData[item.Key].FirstOrDefault();
                var initialSoilKey = historicalCropdata.Keys.FirstOrDefault(x => x.Item1 == item.Key && x.Item2 == year.First());
                var initialSoil = historicalCropdata[initialSoilKey].FirstOrDefault();
                var soilPropertyDepth10 = item.Value.Depth10;
                if (item.Value.Depth.HasValue && item.Value.Depth.Value == 10)
                {
                    soilPropertyDepth10 = true;
                }
                // Different corrections for depth (10, 25, 30 cm)
                // depth of 10cm has same result as previous checkbox (sample taken at 10cm)
                parameterSet.LayerDepth = item.Value.Depth.HasValue ? item.Value.Depth.Value : parameterSet.LayerDepth;
                if (initialSoil == "Temp_gras" && soilPropertyDepth10)
                {
                    c_corr_SoilType.Add((item.Key, soilOfItem), item.Value.C_Cont * 0.97);
                }
                else if (cropSet.Grass_nat.Contains(initialSoil) && soilPropertyDepth10 && (soilOfItem == "Klei" || soilOfItem == "Zand"))
                {
                    if (soilOfItem == "Klei")
                    {
                        c_corr_SoilType.Add((item.Key, soilOfItem), item.Value.C_Cont * 0.67);
                    }
                    else
                    {
                        c_corr_SoilType.Add((item.Key, soilOfItem), item.Value.C_Cont * 0.81);
                    }
                }
                else
                {
                    c_corr_SoilType.Add((item.Key, soilOfItem), item.Value.C_Cont);
                }
            }

            var oM_Corr = new Dictionary<string, double>();
            foreach (var item in oM_corr_SoilType)
            {
                oM_Corr.Add(item.Key.Item1, item.Value);
            }
            var c_Corr = new Dictionary<string, double>();
            foreach (var item in c_corr_SoilType)
            {
                c_Corr.Add(item.Key.Item1, item.Value);
                oM_corr_SoilType.Add(item.Key, item.Value * (1 / 0.54));
            }

            // soilBulkDensities            
            Dictionary<string, SoilBulkDensityType> soilBulkDensities = GetSoilBulkDensities(soilPropertiesField, soilTypeData, soilPropSoilType, oM_corr_SoilType);

            foreach (var soilBulkDensity in soilBulkDensities)
            {
                if (soilPropSoilType.ContainsKey((soilBulkDensity.Key, "Klei")))
                {
                    soilPropSoilType[(soilBulkDensity.Key, "Klei")].Bulk_Density = (1.0 / soilBulkDensity.Value.Clay);
                    soilBulkDensity.Value.Clay = (1.0 / soilBulkDensity.Value.Clay);
                }
                if (soilPropSoilType.ContainsKey((soilBulkDensity.Key, "Zand")))
                {
                    soilPropSoilType[(soilBulkDensity.Key, "Zand")].Bulk_Density = (1.0 / soilBulkDensity.Value.Sand);
                    soilBulkDensity.Value.Sand = (1.0 / soilBulkDensity.Value.Sand);
                }
            }

            var bulkDens = new Dictionary<string, double>();
            foreach (var soilBulkDensity in soilBulkDensities)
            {
                bulkDens.Add(soilBulkDensity.Key, (soilBulkDensity.Value.Clay + soilBulkDensity.Value.Sand));
            }

            foreach (var item in soilPropertiesField)
            {
                var soilProps = soilPropSoilType.Keys.Where(x => x.Item1 == item.Key).ToList();
                var sumBulkDensity = 0.0;
                foreach (var soilProp in soilProps)
                {
                    sumBulkDensity += soilPropSoilType[soilProp].Bulk_Density;
                }
                item.Value.Bulk_Density = sumBulkDensity;
            }
            // finish precalculations


            //////////////////////////////////////////
            // loop rothc
            //////////////////////////////////////////

            var soc_Field = new Dictionary<string, List<YearColumnValue>>();

            var cropAreaField1 = new Dictionary<(string, int), List<ColumnValue>>();
            var carbonInputs = new GamsThreeKeyParameter();
            var soc_Balance = new Dictionary<(string, int), List<ColumnValue>>();
            var socIni = new Dictionary<(string, int), List<ColumnValue>>();
            var cropAreaFarm = new Dictionary<(string, int), List<ColumnValue>>();

            // start loop year
            foreach (var loopYear in year)
            {
                var historicalCropYearKeys = historicalCropdata.Keys.Where(k => k.Item2 == loopYear).Distinct().ToList();
                foreach (var historicalCropYearKey in historicalCropYearKeys)
                {
                    foreach (var historicalCropKeyValue in historicalCropdata[historicalCropYearKey])
                    {
                        if (cropAreaField1.ContainsKey(historicalCropYearKey))
                        {
                            cropAreaField1[historicalCropYearKey].Add(new ColumnValue(historicalCropKeyValue, cropAreaField[historicalCropYearKey.Item1]));
                        }
                        else
                        {
                            cropAreaField1.Add(historicalCropYearKey, new List<ColumnValue> { new ColumnValue(historicalCropKeyValue, cropAreaField[historicalCropYearKey.Item1]) });
                        }
                    }
                }

                // cropAreaFarm                
                var croplist = new List<ColumnValue>();
                foreach (var field in farmInput.CropFields.Select(s => s.Name))
                {
                    croplist.AddRange(cropAreaField1[(field, loopYear)]);
                }
                cropAreaFarm.Add(("Farmname", loopYear), croplist);

                Dictionary<string, List<ClimateVariable>> climateVariable;
                if (climateScenarioNumber == 0)
                {
                    climateVariable = GetClimateVariable(knmiFile, dataTransformationSet, gisSoilSet, loopYear, farmInput, climateFile, climateScenarioNumber);
                }
                else
                {
                    climateVariable = GetClimateVariable2(knmiFile, dataTransformationSet, gisSoilSet, loopYear, farmInput, climateFile, climateScenarioNumber, climate);
                }

                // Crop and soil areas
                // soilArea keys: (fieldname, year, soil)
                var soilArea = new Dictionary<(string, int, string), List<ColumnValue>>();
                foreach (var cropArea in cropAreaField1)
                {
                    foreach (var soilType in soilTypeData[cropArea.Key.Item1])
                    {
                        foreach (var crparea in cropArea.Value)
                        {
                            var key = (cropArea.Key.Item1, loopYear, crparea.Key);
                            if (soilArea.ContainsKey(key))
                            {
                                soilArea[key].Add(new ColumnValue(soilType, crparea.Value));
                            }
                            else
                            {
                                soilArea.Add(key, new List<ColumnValue> { new ColumnValue(soilType, crparea.Value) });
                            }
                        }
                    }
                }

                // Mineral soils
                var mineralSoilArea = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var cropArea in cropAreaField1)
                {
                    foreach (var soilType in soilTypeData[cropArea.Key.Item1])
                    {
                        if (!gisSoilSet.MineralSoils.Contains(soilType))
                        {
                            continue;
                        }
                        var total = 0.0;
                        foreach (var crparea in cropArea.Value)
                        {
                            total += crparea.Value;
                            var key = (cropArea.Key.Item1, loopYear);
                            if (mineralSoilArea.ContainsKey(key))
                            {
                                var miniralSoil = mineralSoilArea[key].FirstOrDefault(x => x.Key == crparea.Key);
                                if (miniralSoil == null)
                                {
                                    mineralSoilArea[key].Add(new ColumnValue(crparea.Key, crparea.Value));
                                }
                                else
                                {
                                    miniralSoil.Value += crparea.Value;
                                }
                            }
                            else
                            {
                                mineralSoilArea.Add(key, new List<ColumnValue> { new ColumnValue(crparea.Key, crparea.Value) });
                            }
                        }
                    }
                }

                // carbon input

                // This part had organic manure per year per cropfield
                var carbonInputManure2 = new Dictionary<(string, string), List<ColumnValue>>();
                var carbonInputCompost2 = new Dictionary<(string, string), ColumnValue>();
                foreach (var field in farmInput.CropFields)
                {
                    foreach (var cropData in field.HistoricalCropData.Where(x => x.Year == loopYear).SelectMany(s => s.Crops))
                    {
                        if (cropData.OrganicManures == null)
                        {
                            continue;
                        }
                        foreach (var compost in cropData.OrganicManures)
                        {
                            if (farmInputSet.ManureSources_liv.Any(x => x.ManureSource_liv.ToUpper() == compost.Type.ToUpper()))
                            {
                                var result = compost.Quantity * manCcontConfig.SingleOrDefault(x => x.ManCcont.ToUpper() == compost.Type.ToUpper())?.Value ?? 0;
                                if (carbonInputManure2.ContainsKey((field.Name, cropData.Crop)))
                                {
                                    carbonInputManure2[(field.Name, cropData.Crop)].Add(new ColumnValue(compost.Type, result));
                                }
                                else
                                {
                                    carbonInputManure2.Add((field.Name, cropData.Crop), new List<ColumnValue> { new ColumnValue(compost.Type, result) });
                                }
                            }
                            else if (farmInputSet.composts.Any(x => x.compost.ToUpper() == compost.Type.ToUpper()))
                            {
                                var result = compost.Quantity * manCcontConfig.SingleOrDefault(x => x.ManCcont.ToUpper() == compost.Type.ToUpper())?.Value ?? 0;
                                if (carbonInputCompost2.ContainsKey((field.Name, cropData.Crop)))
                                {
                                    carbonInputCompost2[(field.Name, cropData.Crop)] = new ColumnValue(compost.Type, result);
                                }
                                else
                                {
                                    carbonInputCompost2.Add((field.Name, cropData.Crop), new ColumnValue(compost.Type, result));
                                }
                            }
                        }
                    }
                }

                // CarbonInputCompost

                // cropYield
                var yieldField = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var fieldYear in cropAreaField1)
                {
                    string provinceCode = GetProvinceCode(dataTransformationSet, farmInput.PostalCode);

                    var valueList = new List<ColumnValue>();
                    foreach (var item in fieldYear.Value)
                    {
                        var crpYield = cropYieldConfig.SingleOrDefault(x => x.CropName.ToLower() == item.Key.ToLower());
                        if (crpYield != null)
                        {
                            double yieldValue = 0;
                            var yieldProvinceYear = crpYield.Yields.Single(x => x.ProvinceCode == provinceCode).YearData.FirstOrDefault(y => y.Year == loopYear);
                            if (yieldProvinceYear == null)
                            {
                                yieldValue = 0;
                            }
                            else
                            {
                                yieldValue = yieldProvinceYear.YieldValue;
                            }
                            valueList.Add(new ColumnValue(crpYield.CropName, yieldValue));
                        }
                    }
                    yieldField.Add(fieldYear.Key, valueList);

                    var farmfield = farmInput.CropFields.SingleOrDefault(x => x.Name == fieldYear.Key.Item1);
                    var crop = farmfield.HistoricalCropData.SingleOrDefault(x => x.Year == fieldYear.Key.Item2);
                    foreach (var cropItem in crop.Crops)
                    {
                        if (cropItem.CropYield.HasValue)
                        {
                            valueList.SingleOrDefault(x => x.Key.ToLower() == cropItem.Crop.ToLower()).Value = cropItem.CropYield.Value;
                        }
                    }
                }

                // DM_Yield_Field
                // dm_Yield_Field key: (field, year)
                var dm_Yield_Field = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var yieldFieldYear in yieldField)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var value in yieldFieldYear.Value)
                    {
                        var newValue = value.Value * 0.001;
                        var dmValue = cropPropertyConfig.SingleOrDefault(x => x.Name == value.Key)?.DM_content ?? 0;
                        newValue = newValue * dmValue;
                        valueList.Add(new ColumnValue(value.Key, newValue));
                    }
                    dm_Yield_Field.Add(yieldFieldYear.Key, valueList);
                }

                var harvestIndexField = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var dmField in dm_Yield_Field)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in dmField.Value)
                    {
                        var newValue = 0.0;
                        switch (item.Key)
                        {
                            // maybe in json strawcrops?
                            case "Maize":
                                newValue = 0.024 * item.Value + 0.228;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Corn_cob_mix":
                                newValue = 0.024 * item.Value + 0.228;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Winter_wheat":
                                newValue = 0.0246 * item.Value + 0.3192;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Summer_wheat":
                                newValue = 0.0246 * item.Value + 0.3192;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Summer_barley":
                                newValue = 0.0256 * item.Value + 0.3727;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "winter_barley":
                                newValue = 0.0256 * item.Value + 0.3727;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Rapeseed":
                                newValue = 0.008 * item.Value + 0.3037;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Rye":
                                newValue = 0.0256 * item.Value + 0.3727;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "oats":
                                newValue = 0.0256 * item.Value + 0.3727;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Sunflower":
                                newValue = 0.0077 * item.Value + 0.3045;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            case "Other_cereals":
                                newValue = 0.0256 * item.Value + 0.3727;
                                valueList.Add(new ColumnValue(item.Key, newValue));
                                break;
                            default:
                                break;
                        }
                    }
                    if (valueList.Count > 0)
                    {
                        harvestIndexField.Add(dmField.Key, valueList);
                    }
                }

                // AboveGroundResidues
                var aboveGroundResidues = new Dictionary<(string, int), List<ColumnValue>>();
                var stubbleChaff = new Dictionary<(string, int), List<ColumnValue>>();
                var strawYield = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var harvestField in harvestIndexField)
                {
                    var valueList = new List<ColumnValue>();
                    var valueListStubbleChaff = new List<ColumnValue>();
                    var valueListStrawYield = new List<ColumnValue>();
                    foreach (var harvestValue in harvestField.Value)
                    {
                        var dm_YieldValue = dm_Yield_Field[harvestField.Key].SingleOrDefault(x => x.Key == harvestValue.Key);
                        var result = (dm_YieldValue.Value / harvestValue.Value) - dm_YieldValue.Value;
                        valueList.Add(new ColumnValue(harvestValue.Key, result));
                        valueListStubbleChaff.Add(new ColumnValue(harvestValue.Key, result * parameterSet.StubbleFraction));
                        valueListStrawYield.Add(new ColumnValue(harvestValue.Key, result * (1 - parameterSet.StubbleFraction)));
                    }
                    aboveGroundResidues.Add(harvestField.Key, valueList);
                    stubbleChaff.Add(harvestField.Key, valueListStubbleChaff);
                    strawYield.Add(harvestField.Key, valueListStrawYield);
                }


                // BelowGroundCarbonInput 
                // (field, year) --> crop,value
                var belowGroundCarbonInput = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var stubbleC in stubbleChaff)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var stubbleKeyValue in stubbleC.Value)
                    {
                        var dmYieldValue = dm_Yield_Field[stubbleC.Key].Single(s => s.Key.ToLower() == stubbleKeyValue.Key.ToLower());
                        var strawYieldValue = strawYield[stubbleC.Key].Single(s => s.Key.ToLower() == stubbleKeyValue.Key.ToLower());
                        var dmContentStraw = cropSet.StrawCrops.Single(s => s.StrawCrop.ToLower() == stubbleKeyValue.Key.ToLower());

                        var result = ((dmYieldValue.Value + ((stubbleKeyValue.Value + strawYieldValue.Value) * dmContentStraw.DM_Content)) * parameterSet.CarbonBiomass) / ((1 / parameterSet.FracBelowGroundCinput) - 1);
                        valueList.Add(new ColumnValue(stubbleKeyValue.Key, result));
                    }
                    belowGroundCarbonInput.Add(stubbleC.Key, valueList);
                }

                // CarbonInputs
                // (field, year, crop)
                // what to do with carbon residues??
                // other cereals?? (has value in cInputCropRes and is strawCrop)
                foreach (var cropArea in cropAreaField1.Where(x => x.Key.Item2 == loopYear))
                {
                    foreach (var crop in cropArea.Value)
                    {
                        var cInputCropRes = cropPropertyConfig.SingleOrDefault(s => s.Name.ToLower() == crop.Key.ToLower())?.C_input_CropRes ?? 0.0;
                        carbonInputs.AddValue((cropArea.Key.Item1, cropArea.Key.Item2, crop.Key), "CarbonResidues", cInputCropRes);

                        if (cropSet.StrawCrops.Any(x => x.StrawCrop.ToLower() == crop.Key.ToLower()))
                        {
                            var belowGroundCarbon = belowGroundCarbonInput[cropArea.Key].SingleOrDefault(x => x.Key.ToLower() == crop.Key.ToLower());
                            var below2 = GetColumnValueForKey(belowGroundCarbonInput, cropArea.Key, crop.Key);
                            var stubble = GetColumnValueForKey(stubbleChaff, cropArea.Key, crop.Key);
                            var dm_contentStraw = cropSet.StrawCrops.SingleOrDefault(x => x.StrawCrop.ToLower() == crop.Key.ToLower())?.DM_Content ?? 0;
                            var strawYld = strawYield[cropArea.Key].SingleOrDefault(x => x.Key.ToLower() == crop.Key.ToLower())?.Value ?? 0;
                            var fracR = cropPropertyConfig.SingleOrDefault(x => x.Name.ToLower() == crop.Key.ToLower())?.FracR ?? 0.0;

                            var field = farmInput.CropFields.SingleOrDefault(x => x.Name == cropArea.Key.Item1);
                            var crop_res = field.HistoricalCropData.SingleOrDefault(x => x.Year == loopYear).Crops[0].CropRes ? 0 : 1;

                            //var result = belowGroundCarbon.Value + (stubble * dm_contentStraw * parameterSet.CarbonBiomass) + ((1 - fracR) * strawYld * dm_contentStraw * parameterSet.CarbonBiomass);
                            var result = belowGroundCarbon.Value + (stubble * dm_contentStraw * parameterSet.CarbonBiomass) + (crop_res * strawYld * dm_contentStraw * parameterSet.CarbonBiomass);

                            //check if key exists (from if condition (in case of other cereals))
                            if (result > 12000)
                            {
                                result = 12000;
                            }
                            carbonInputs.AddOrUpdateValue((cropArea.Key.Item1, cropArea.Key.Item2, crop.Key), "CarbonResidues", result);
                        }
                    }
                }

                // CarbonInputGreenManure                
                var carbonInputGreenManure = GetCarbonInputGreenManure(loopYear, farmInput, yieldGreenManureConfig);


                //Total carbon input[in ton C / ha] from manure, compost, crop residues and green manure                
                // CarbonInputs(field, year, crop,'CarbonResidues')$(CarbonInputs(field, year,crop,'CarbonResidues')>12000) = 12000;???

                foreach (var carbon in carbonInputManure2)
                {
                    var sumCarbonInputManure = carbon.Value.Sum(x => x.Value);
                    var carbonKeyInput = carbonInputs.TrippleKeyParam.Where(x => x.Key.Item1 == carbon.Key.Item1 && x.Key.Item3.ToLower() == carbon.Key.Item2.ToLower() && x.Key.Item2 == loopYear).Select(s => s.Key).ToList();

                    foreach (var carbonKey in carbonKeyInput)
                    {
                        carbonInputs.AddValue(carbonKey, "CarbonManure", sumCarbonInputManure);
                    }
                }

                foreach (var carbonManure in carbonInputGreenManure.Where(x => x.Key.Item2 == loopYear))
                {
                    var sum = carbonManure.Value.Sum(x => x.Value);
                    foreach (var item in historicalCropdata[carbonManure.Key])
                    {
                        carbonInputs.AddValue((carbonManure.Key.Item1, carbonManure.Key.Item2, item), "CarbonGreenManure", sum);
                    }
                }

                foreach (var carbon in carbonInputCompost2)
                {
                    foreach (var historicData in historicalCropdata.Where(x => x.Key.Item2 == loopYear))
                    {
                        var historicDataItem = historicData.Value.SingleOrDefault(x => x.ToLower() == carbon.Key.Item2.ToLower());
                        if (historicDataItem != null)
                        {
                            carbonInputs.AddValueSkipIfColumnExists((historicData.Key.Item1, historicData.Key.Item2, historicDataItem), "CarbonCompost", carbon.Value.Value);
                        }
                    }
                }

                // TempModFactor
                var tempModFactor = new Dictionary<string, List<ColumnValue>>();
                foreach (var climateVar in climateVariable)
                {
                    var valueList = new List<ColumnValue>();
                    var count = 1;
                    foreach (var temperature in climateVar.Value)
                    {
                        var temp = 47.9 / (1 + Math.Exp(106.0 / (temperature.Temperature + 18.3)));
                        valueList.Add(new ColumnValue($"M{count}", temp));
                        count++;
                    }
                    tempModFactor.Add(climateVar.Key, valueList);
                }

                // CoverModFactor
                // input sheet
                var coverModFactor = new Dictionary<string, List<ColumnValue>>();

                foreach (var crpCover in cropCoverMonthConfig)
                {
                    var valueList = new List<ColumnValue>();
                    for (int i = 1; i < 13; i++)
                    {
                        if (crpCover.CoverMonths.Contains($"M{i}"))
                        {
                            valueList.Add(new ColumnValue($"M{i}", 0.6));
                        }
                        else
                        {
                            valueList.Add(new ColumnValue($"M{i}", 1.0));
                        }
                    }
                    coverModFactor.Add(crpCover.CropName, valueList);
                }

                // SMDMax 
                // field, crop
                var smdMax = new Dictionary<string, double>();
                foreach (var soil in soilPropertiesField)
                {
                    var result = -(20 + (1.3 * soil.Value.Clay_Content) - (0.01 * Math.Pow(soil.Value.Clay_Content, 2))) * (parameterSet.LayerDepth / 23);
                    smdMax.Add(soil.Key, result);
                }

                // SMDaccMonthi
                var smdAccMonthi = new Dictionary<string, List<ColumnValue>>();

                foreach (var climateItem in climateVariable)
                {
                    var valueList = new List<ColumnValue>();
                    for (int i = 0; i < climateItem.Value.Count; i++)
                    {
                        if (i == 0)
                        {

                            /* smdMax met factor maand corrigeren 
                               SMDmax(Field,Crop,AllMonths) = ifthen(CropCoverMonths(Crop,AllMonths)=1,
                               SMDmax(Field,Crop,AllMonths),SMDmax(Field,Crop,AllMonths)/1.8);
                            */
                            var crop = cropAreaField1.SingleOrDefault(x => x.Key.Item2 == loopYear).Value[0].Key;
                            var cover = cropCoverMonthConfig.SingleOrDefault(x => x.CropName.ToLower() == crop.ToLower())?.CoverMonths;
                            var hasCover = cover.Any(x => x == $"M{i + 1}");
                            var smdMaxValue = smdMax[climateItem.Key];
                            if (!hasCover)
                            {
                                smdMaxValue = smdMax[climateItem.Key] / 1.8;
                            }
                            //fsa todo: add precipitation
                            var irrigation = 0;
                            var irrigations = farmInput.CropFields[0].HistoricalCropData.SingleOrDefault(x => x.Year == loopYear).Crops[0].Irrigation;
                            if (!string.IsNullOrEmpty(irrigations))
                            {
                                var irrigationArray = irrigations.Split(',');
                                if (irrigationArray.Length > i)
                                {
                                    irrigation = string.IsNullOrEmpty(irrigationArray[i]) ? 0 : Convert.ToInt16(irrigationArray[i]);
                                }
                            }
                            var result = climateItem.Value[i].Precipitation - climateItem.Value[i].Evapotranspiration + irrigation;
                            result = result < 0 ? result : 0;
                            result = result < smdMaxValue ? smdMaxValue : result;
                            valueList.Add(new ColumnValue("M1", result));
                        }
                        else
                        {
                            /* smdMax met factor maand corrigeren 
                                SMDmax(Field,Crop,AllMonths) = ifthen(CropCoverMonths(Crop,AllMonths)=1,
                                SMDmax(Field,Crop,AllMonths),SMDmax(Field,Crop,AllMonths)/1.8);
                            */
                            var crop = cropAreaField1.SingleOrDefault(x => x.Key.Item2 == loopYear).Value[0].Key;
                            var cover = cropCoverMonthConfig.SingleOrDefault(x => x.CropName.ToLower() == crop.ToLower())?.CoverMonths;
                            var hasCover = cover.Any(x => x == $"M{i + 1}");
                            var smdMaxValue = smdMax[climateItem.Key];
                            if (!hasCover)
                            {
                                smdMaxValue = smdMax[climateItem.Key] / 1.8;
                            }

                            var irrigation = 0;
                            var irrigations = farmInput.CropFields[0].HistoricalCropData.SingleOrDefault(x => x.Year == loopYear).Crops[0].Irrigation;
                            if (!string.IsNullOrEmpty(irrigations))
                            {
                                var irrigationArray = irrigations.Split(',');
                                if (irrigationArray.Length > i)
                                {
                                    irrigation = string.IsNullOrEmpty(irrigationArray[i]) ? 0 : Convert.ToInt16(irrigationArray[i]);
                                }
                            }
                            var result = climateItem.Value[i].Precipitation - climateItem.Value[i].Evapotranspiration + valueList.SingleOrDefault(x => x.Key == $"M{i}").Value + irrigation;
                            result = result < 0 ? result : 0;
                            result = result < smdMaxValue ? smdMaxValue : result;
                            valueList.Add(new ColumnValue($"M{i + 1}", result));
                        }
                    }
                    smdAccMonthi.Add(climateItem.Key, valueList);
                }

                //MoistureModFactor
                var moistureModFactor = new Dictionary<string, List<ColumnValue>>();

                foreach (var fieldSmdAccMonthi in smdAccMonthi)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var smdAccMonth in fieldSmdAccMonthi.Value)
                    {
                        /* smdMax met factor maand corrigeren 
                            SMDmax(Field,Crop,AllMonths) = ifthen(CropCoverMonths(Crop,AllMonths)=1,
                            SMDmax(Field,Crop,AllMonths),SMDmax(Field,Crop,AllMonths)/1.8);
                        */
                        var crop = cropAreaField1.SingleOrDefault(x => x.Key.Item2 == loopYear).Value[0].Key;
                        var cover = cropCoverMonthConfig.SingleOrDefault(x => x.CropName.ToLower() == crop.ToLower())?.CoverMonths;
                        var hasCover = cover.Any(x => x == smdAccMonth.Key);
                        var smdMaxValue = smdMax[fieldSmdAccMonthi.Key];
                        if (!hasCover)
                        {
                            smdMaxValue = smdMax[fieldSmdAccMonthi.Key] / 1.8;
                        }

                        if (smdAccMonth.Value > 0.444 * smdMaxValue)
                        {
                            valueList.Add(new ColumnValue(smdAccMonth.Key, 1));
                        }
                        else
                        {
                            var result = (0.2 + 0.8 * ((smdMaxValue - smdAccMonth.Value) / (smdMaxValue - 0.444 * smdMaxValue)));
                            valueList.Add(new ColumnValue(smdAccMonth.Key, result));
                        }
                    }
                    moistureModFactor.Add(fieldSmdAccMonthi.Key, valueList);
                }


                // TempCoverMoistureModFactorAverage
                var tempCoverMoistureModFactorAverage = new Dictionary<string, List<ColumnValue>>();

                foreach (var field in moistureModFactor.Select(s => s.Key))
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var coverMod in coverModFactor)
                    {
                        var result = 0.0;
                        foreach (var monthValue in coverMod.Value)
                        {
                            var covermodValue = monthValue.Value;
                            var tempModValue = tempModFactor[field].SingleOrDefault(x => x.Key == monthValue.Key).Value;
                            var moistureModValue = moistureModFactor[field].SingleOrDefault(x => x.Key == monthValue.Key).Value;
                            result += covermodValue * tempModValue * moistureModValue;
                        }
                        result = (1.0 / 12.0) * result;
                        valueList.Add(new ColumnValue(coverMod.Key, result));
                    }
                    tempCoverMoistureModFactorAverage.Add(field, valueList);
                }

                //step1

                //SOCini

                if (loopYear == year.First())
                {
                    foreach (var field in cropAreaField1.Where(x => x.Key.Item2 == loopYear))
                    {
                        var valueList = new List<ColumnValue>();
                        var result = parameterSet.LayerDepth * bulkDens[field.Key.Item1] * c_Corr[field.Key.Item1]; //(used to be oM_Corr[field.Key.Item1] * 0.54)
                        foreach (var crop in field.Value)
                        {
                            valueList.Add(new ColumnValue(crop.Key, result));
                        }
                        socIni.Add(field.Key, valueList);
                    }
                }
                else
                {
                    foreach (var fieldItem in soc_Field)
                    {
                        var valueList = new List<ColumnValue>();
                        var prev = fieldItem.Value.SingleOrDefault(x => x.Key == loopYear - 1);
                        if (prev == null)
                        {
                            continue;
                        }
                        foreach (var cropItem in historicalCropdata[(fieldItem.Key, loopYear)])
                        {
                            valueList.Add(new ColumnValue(cropItem, prev.Value));
                        }
                        socIni.Add((fieldItem.Key, loopYear), valueList);
                    }
                }

                //step 2

                // RPM_in_residues
                // DPM_in_residues
                var rpm_in_residues = new Dictionary<(string, int), List<ColumnValue>>();
                var dpm_in_residues = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var carbonInput in carbonInputs.TrippleKeyParam.Where(x => x.Key.Item2 == loopYear))
                {
                    var carbonResidues = carbonInput.Value.SingleOrDefault(x => x.Key == "CarbonResidues")?.Value ?? 0;
                    var carbonGreenManure = carbonInput.Value.SingleOrDefault(x => x.Key == "CarbonGreenManure")?.Value ?? 0;
                    var resultRpm = 0.0;
                    var resultDpm = 0.0;
                    if (cropSet.Perennial.Any(x => x.ToLower() == carbonInput.Key.Item3.ToLower()))
                    {
                        resultRpm = (carbonResidues) * 0.6;
                        resultDpm = (carbonResidues) * 0.4;
                    }
                    else
                    {
                        resultRpm = (carbonResidues + carbonGreenManure) * 0.41;
                        resultDpm = (carbonResidues + carbonGreenManure) * 0.59;
                    }

                    if (rpm_in_residues.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        rpm_in_residues[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultRpm));
                    }
                    else
                    {
                        rpm_in_residues.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultRpm) });
                    }
                    if (dpm_in_residues.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        dpm_in_residues[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultDpm));
                    }
                    else
                    {
                        dpm_in_residues.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultDpm) });
                    }
                }

                // DPM_in_Manure
                // RPM_in_manure
                // HUM_in_manure
                var dpm_in_Manure = new Dictionary<(string, int), List<ColumnValue>>();
                var rpm_in_manure = new Dictionary<(string, int), List<ColumnValue>>();
                var hum_in_manure = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var carbonInput in carbonInputs.TrippleKeyParam.Where(x => x.Key.Item2 == loopYear))
                {
                    var carbonManure = carbonInput.Value.SingleOrDefault(x => x.Key == "CarbonManure")?.Value ?? 0;
                    var resultRpm = (carbonManure) * 0.49;
                    var resultDpm = (carbonManure) * 0.49;
                    var resultHum = (carbonManure) * 0.02;

                    if (rpm_in_manure.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        rpm_in_manure[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultRpm));
                    }
                    else
                    {
                        rpm_in_manure.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultRpm) });
                    }
                    if (dpm_in_Manure.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        dpm_in_Manure[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultDpm));
                    }
                    else
                    {
                        dpm_in_Manure.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultDpm) });
                    }
                    if (hum_in_manure.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        hum_in_manure[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultHum));
                    }
                    else
                    {
                        hum_in_manure.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultHum) });
                    }
                }

                // DPM_compost
                // RPM_compost
                // HUM_compost
                var dpm_compost = new Dictionary<(string, int), List<ColumnValue>>();
                var rpm_compost = new Dictionary<(string, int), List<ColumnValue>>();
                var hum_compost = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var carbonInput in carbonInputs.TrippleKeyParam.Where(x => x.Key.Item2 == loopYear))
                {
                    var carbonCompost = carbonInput.Value.SingleOrDefault(x => x.Key == "CarbonCompost")?.Value ?? 0;
                    var resultRpm = (carbonCompost) * 0.70;
                    var resultDpm = (carbonCompost) * 0.15;
                    var resultHum = (carbonCompost) * 0.15;

                    if (rpm_compost.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        rpm_compost[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultRpm));
                    }
                    else
                    {
                        rpm_compost.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultRpm) });
                    }
                    if (dpm_compost.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        dpm_compost[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultDpm));
                    }
                    else
                    {
                        dpm_compost.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultDpm) });
                    }
                    if (hum_compost.ContainsKey((carbonInput.Key.Item1, carbonInput.Key.Item2)))
                    {
                        hum_compost[(carbonInput.Key.Item1, carbonInput.Key.Item2)].Add(new ColumnValue(carbonInput.Key.Item3, resultHum));
                    }
                    else
                    {
                        hum_compost.Add((carbonInput.Key.Item1, carbonInput.Key.Item2), new List<ColumnValue> { new ColumnValue(carbonInput.Key.Item3, resultHum) });
                    }
                }

                // DPM_in
                // RPM_in
                // HUM_in
                var dpm_in = new Dictionary<(string, int), List<ColumnValue>>();
                var rpm_in = new Dictionary<(string, int), List<ColumnValue>>();
                var hum_in = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var dpmitem in dpm_in_Manure)
                {
                    foreach (var item in dpmitem.Value)
                    {
                        var valueDpmManure = item.Value;
                        var valueDpmResidues = dpm_in_residues[dpmitem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var valueDpmCompost = dpm_compost[dpmitem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var result = valueDpmManure + valueDpmResidues + valueDpmCompost;

                        if (dpm_in.ContainsKey(dpmitem.Key))
                        {
                            dpm_in[dpmitem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            dpm_in.Add(dpmitem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                foreach (var rpmitem in rpm_in_manure)
                {
                    foreach (var item in rpmitem.Value)
                    {
                        var valueRpmManure = item.Value;
                        var valueRpmResidues = rpm_in_residues[rpmitem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var valueRpmCompost = rpm_compost[rpmitem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var result = valueRpmManure + valueRpmResidues + valueRpmCompost;

                        if (rpm_in.ContainsKey(rpmitem.Key))
                        {
                            rpm_in[rpmitem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            rpm_in.Add(rpmitem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                foreach (var humitem in hum_in_manure)
                {
                    foreach (var item in humitem.Value)
                    {
                        var valueHumManure = item.Value;
                        var valueHumCompost = hum_compost[humitem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var result = valueHumManure + valueHumCompost;

                        if (hum_in.ContainsKey(humitem.Key))
                        {
                            hum_in[humitem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            hum_in.Add(humitem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                //step 3

                //DPMini
                var dpmIni = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var dpmItem in dpm_in)
                {
                    foreach (var item in dpmItem.Value)
                    {
                        var tempCover = tempCoverMoistureModFactorAverage[dpmItem.Key.Item1].SingleOrDefault(x => x.Key.ToLower() == item.Key.ToLower())?.Value ?? 0;
                        var result = (item.Value / tempCover) / parameterSet.kDPM;
                        if (dpmIni.ContainsKey(dpmItem.Key))
                        {
                            dpmIni[dpmItem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            dpmIni.Add(dpmItem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                //RPMini
                var rpmIni = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var rpmItem in rpm_in)
                {
                    foreach (var item in rpmItem.Value)
                    {
                        var tempCover = tempCoverMoistureModFactorAverage[rpmItem.Key.Item1].SingleOrDefault(x => x.Key.ToLower() == item.Key.ToLower())?.Value ?? 0;
                        var result = (item.Value / tempCover) / parameterSet.kRPM;
                        if (rpmIni.ContainsKey(rpmItem.Key))
                        {
                            rpmIni[rpmItem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            rpmIni.Add(rpmItem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                //IOMini
                var iomIni = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var socItem in socIni)
                {
                    foreach (var item in socItem.Value)
                    {
                        var result = 0.049 * Math.Pow(item.Value, 1.139);
                        if (iomIni.ContainsKey(socItem.Key))
                        {
                            iomIni[socItem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            iomIni.Add(socItem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                // BIOplusHUM_ini
                var bioPlusHUM_ini = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var socItem in socIni.Where(x => x.Key.Item2 == loopYear))
                {
                    foreach (var item in socItem.Value)
                    {
                        var socIniValue = item.Value;
                        var dpmIniValue = dpmIni[socItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var rpmIniValue = rpmIni[socItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var iomIniValue = iomIni[socItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var result = socIniValue - (dpmIniValue + rpmIniValue + iomIniValue);

                        if (bioPlusHUM_ini.ContainsKey(socItem.Key))
                        {
                            bioPlusHUM_ini[socItem.Key].Add(new ColumnValue(item.Key, result));
                        }
                        else
                        {
                            bioPlusHUM_ini.Add(socItem.Key, new List<ColumnValue> { new ColumnValue(item.Key, result) });
                        }
                    }
                }

                // BIOini
                // HUMini
                var bioIni = new Dictionary<(string, int), List<ColumnValue>>();
                var humIni = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var bioPlusItem in bioPlusHUM_ini)
                {
                    foreach (var item in bioPlusItem.Value)
                    {
                        var bioIniValue = item.Value / (1 + 0.66 / 0.02);
                        var humIniValue = item.Value / (1 + 0.02 / 0.66);

                        if (bioIni.ContainsKey(bioPlusItem.Key))
                        {
                            bioIni[bioPlusItem.Key].Add(new ColumnValue(item.Key, bioIniValue));
                        }
                        else
                        {
                            bioIni.Add(bioPlusItem.Key, new List<ColumnValue> { new ColumnValue(item.Key, bioIniValue) });
                        }
                        if (humIni.ContainsKey(bioPlusItem.Key))
                        {
                            humIni[bioPlusItem.Key].Add(new ColumnValue(item.Key, humIniValue));
                        }
                        else
                        {
                            humIni.Add(bioPlusItem.Key, new List<ColumnValue> { new ColumnValue(item.Key, humIniValue) });
                        }
                    }
                }

                //step 4

                // DPMdec_fraction
                // RPMdec_fraction
                // BIOdec_fraction
                // HUMdec_fraction
                var dpmDec_fraction = new Dictionary<(string, int), List<ColumnValue>>();
                var rpmDec_fraction = new Dictionary<(string, int), List<ColumnValue>>();
                var bioDec_fraction = new Dictionary<(string, int), List<ColumnValue>>();
                var humDec_fraction = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var tempCover in tempCoverMoistureModFactorAverage)
                {
                    foreach (var item in tempCover.Value)
                    {
                        var dpmDecValue = 1 - Math.Exp(-(item.Value * parameterSet.kDPM / 12));
                        var rpmDecValue = 1 - Math.Exp(-(item.Value * parameterSet.kRPM / 12));
                        var bioDecValue = 1 - Math.Exp(-(item.Value * 0.66 / 12));
                        var humDecValue = 1 - Math.Exp(-(item.Value * 0.02 / 12));

                        if (dpmDec_fraction.ContainsKey((tempCover.Key, loopYear)))
                        {
                            dpmDec_fraction[(tempCover.Key, loopYear)].Add(new ColumnValue(item.Key, dpmDecValue));
                        }
                        else
                        {
                            dpmDec_fraction.Add((tempCover.Key, loopYear), new List<ColumnValue> { new ColumnValue(item.Key, dpmDecValue) });
                        }

                        if (rpmDec_fraction.ContainsKey((tempCover.Key, loopYear)))
                        {
                            rpmDec_fraction[(tempCover.Key, loopYear)].Add(new ColumnValue(item.Key, rpmDecValue));
                        }
                        else
                        {
                            rpmDec_fraction.Add((tempCover.Key, loopYear), new List<ColumnValue> { new ColumnValue(item.Key, rpmDecValue) });
                        }

                        if (bioDec_fraction.ContainsKey((tempCover.Key, loopYear)))
                        {
                            bioDec_fraction[(tempCover.Key, loopYear)].Add(new ColumnValue(item.Key, bioDecValue));
                        }
                        else
                        {
                            bioDec_fraction.Add((tempCover.Key, loopYear), new List<ColumnValue> { new ColumnValue(item.Key, bioDecValue) });
                        }

                        if (humDec_fraction.ContainsKey((tempCover.Key, loopYear)))
                        {
                            humDec_fraction[(tempCover.Key, loopYear)].Add(new ColumnValue(item.Key, humDecValue));
                        }
                        else
                        {
                            humDec_fraction.Add((tempCover.Key, loopYear), new List<ColumnValue> { new ColumnValue(item.Key, humDecValue) });
                        }
                    }
                }

                // step 5

                // CO2LostRatio
                var co2LostRatio = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var soilProperty in soilPropertiesField)
                {
                    var result = 1.67 * (1.85 + 1.60 * Math.Exp(-0.0786 * soilProperty.Value.Clay_Content));
                    var data1 = cropAreaField1[(soilProperty.Key, loopYear)];
                    var valueList = new List<ColumnValue>();
                    foreach (var item in data1)
                    {
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    co2LostRatio.Add((soilProperty.Key, loopYear), valueList);
                }

                // CO2LostRate
                var co2LostRate = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var co2 in co2LostRatio)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in co2.Value)
                    {
                        var result = item.Value / (1 + item.Value);
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    co2LostRate.Add(co2.Key, valueList);
                }

                // step 6

                // DPM
                // RPM
                // BIO
                // HUM

                var dpm = new Dictionary<(string, int), List<ColumnValue>>();
                var rpm = new Dictionary<(string, int), List<ColumnValue>>();
                var bio = new Dictionary<(string, int), List<ColumnValue>>();
                var hum = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var dpmItem in dpmIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in dpmItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    dpm.Add(dpmItem.Key, valueList);
                }

                foreach (var rpmItem in rpmIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in rpmItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    rpm.Add(rpmItem.Key, valueList);
                }

                foreach (var bioItem in bioIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in bioItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    bio.Add(bioItem.Key, valueList);
                }

                foreach (var humItem in humIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in humItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    hum.Add(humItem.Key, valueList);
                }

                // ToSOM
                var toSOM = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var dpmItem in dpm)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in dpmItem.Value)
                    {
                        var result = 0.0;
                        for (int i = 0; i < 12; i++)
                        {
                            var dpmValue = item.Value;
                            var dpm_inValue = dpm_in[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                            var dpmDec_FractionValue = dpmDec_fraction[dpmItem.Key].SingleOrDefault(x => x.Key.ToLower() == item.Key.ToLower())?.Value ?? 0;

                            var temp1 = (dpmValue + (dpm_inValue / 12)) * dpmDec_FractionValue;

                            var rpmValue = rpm[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                            var rpm_InValue = rpm_in[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                            var rpmDec_FractionValue = rpmDec_fraction[dpmItem.Key].SingleOrDefault(x => x.Key.ToLower() == item.Key.ToLower())?.Value ?? 0;

                            var temp2 = (rpmValue + (rpm_InValue / 12)) * rpmDec_FractionValue;

                            var bioValue = bio[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                            var bioDec_FractionValue = bioDec_fraction[dpmItem.Key].SingleOrDefault(x => x.Key.ToLower() == item.Key.ToLower())?.Value ?? 0;

                            var humValue = hum[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                            var hum_InValue = hum_in[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                            var humDec_FractionValue = humDec_fraction[dpmItem.Key].SingleOrDefault(x => x.Key.ToLower() == item.Key.ToLower())?.Value ?? 0;

                            var co2 = co2LostRate[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;

                            result = ((dpmValue + (dpm_inValue / 12)) * dpmDec_FractionValue + (rpmValue + (rpm_InValue / 12)) * rpmDec_FractionValue + (bioValue * bioDec_FractionValue) + ((humValue + (hum_InValue / 12)) * humDec_FractionValue)) * (1 - co2);

                            item.Value = (dpmValue + (dpm_inValue / 12)) * (1 - dpmDec_FractionValue);
                            rpm[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key).Value = (rpmValue + (rpm_InValue / 12)) * (1 - rpmDec_FractionValue);
                            bio[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key).Value = bioValue * (1 - bioDec_FractionValue) + result * 0.46;
                            hum[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key).Value = (humValue + (hum_InValue / 12)) * (1 - humDec_FractionValue) + result * 0.54;
                        }
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    toSOM.Add(dpmItem.Key, valueList);
                }

                // SOC
                var soc = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var dpmItem in dpm)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in dpmItem.Value)
                    {
                        if (!mineralSoilArea.ContainsKey(dpmItem.Key))
                        {
                            continue;
                        }
                        var dpmValue = item.Value;
                        var rpmValue = rpm[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var bioValue = bio[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var humValue = hum[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var iomValue = iomIni[dpmItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;

                        var result = dpmValue + rpmValue + bioValue + humValue + iomValue;
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    soc.Add(dpmItem.Key, valueList);
                }

                // step 7
                // DPMss
                // RPMss
                // IOMss

                var dpmSs = new Dictionary<(string, int), List<ColumnValue>>();
                var rpmSs = new Dictionary<(string, int), List<ColumnValue>>();
                var iomSs = new Dictionary<(string, int), List<ColumnValue>>();

                foreach (var dpmItem in dpmIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in dpmItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    dpmSs.Add(dpmItem.Key, valueList);
                }

                foreach (var rpmItem in rpmIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in rpmItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    rpmSs.Add(rpmItem.Key, valueList);
                }

                foreach (var iomItem in iomIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in iomItem.Value)
                    {
                        valueList.Add(new ColumnValue(item.Key, item.Value));
                    }
                    iomSs.Add(iomItem.Key, valueList);
                }

                // BIOss
                // HUMss
                var bioSs = new Dictionary<(string, int), List<ColumnValue>>();
                var humSs = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var co2Lost in co2LostRatio)
                {
                    var valueListBio = new List<ColumnValue>();
                    var valueListHum = new List<ColumnValue>();
                    foreach (var item in co2Lost.Value)
                    {
                        var co2Value = item.Value;
                        var tempCoverValue = 0.0;
                        if (tempCoverMoistureModFactorAverage.ContainsKey(co2Lost.Key.Item1))
                        {
                            tempCoverValue = tempCoverMoistureModFactorAverage[co2Lost.Key.Item1].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        }
                        if (co2Value * tempCoverValue * parameterSet.kBIO > 0)
                        {
                            var sumCarbon = carbonInputs.TrippleKeyParam[(co2Lost.Key.Item1, co2Lost.Key.Item2, item.Key)].Sum(x => x.Value);
                            var result = sumCarbon * 0.46 / (co2Value * tempCoverValue * parameterSet.kBIO);
                            valueListBio.Add(new ColumnValue(item.Key, result));
                        }
                        if (co2Value * tempCoverValue * parameterSet.kHUM > 0)
                        {
                            var sumCarbon = carbonInputs.TrippleKeyParam[(co2Lost.Key.Item1, co2Lost.Key.Item2, item.Key)].Sum(x => x.Value);
                            var result = sumCarbon * 0.54 / (co2Value * tempCoverValue * parameterSet.kHUM);
                            valueListHum.Add(new ColumnValue(item.Key, result));
                        }
                    }
                    bioSs.Add(co2Lost.Key, valueListBio);
                    humSs.Add(co2Lost.Key, valueListHum);
                }

                // SOC_SteadyState
                var soc_SteadyState = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var dpms in dpmSs)
                {
                    var valueList = new List<ColumnValue>();

                    foreach (var item in dpms.Value)
                    {
                        var dpmSValue = item.Value;
                        var rpmSValue = rpmSs[dpms.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var bioSValue = bioSs[dpms.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var humSValue = humSs[dpms.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var iomSValue = iomSs[dpms.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;

                        var result = dpmSValue + rpmSValue + bioSValue + humSValue + iomSValue;
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    soc_SteadyState.Add(dpms.Key, valueList);
                }

                // SOC_Field
                foreach (var item in soc.Where(x => x.Key.Item2 == loopYear))
                {
                    var result = item.Value.Sum(x => x.Value);
                    if (soc_Field.ContainsKey(item.Key.Item1))
                    {
                        soc_Field[item.Key.Item1].Add(new YearColumnValue(loopYear, result));
                    }
                    else
                    {
                        soc_Field.Add(item.Key.Item1, new List<YearColumnValue> { new YearColumnValue(loopYear, result) });
                    }
                }

                // SOC_Balance
                foreach (var socItem in soc)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in socItem.Value)
                    {
                        var socValue = item.Value;
                        var socIniValue = socIni[socItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var result = socValue - socIniValue;
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    soc_Balance.Add(socItem.Key, valueList);
                }

                // SOC_Relative
                var soc_Relative = new Dictionary<(string, int), List<ColumnValue>>();
                foreach (var socItem in socIni)
                {
                    var valueList = new List<ColumnValue>();
                    foreach (var item in socItem.Value)
                    {
                        var socIniValue = item.Value;
                        var socBalanceValue = soc_Balance[socItem.Key].SingleOrDefault(x => x.Key == item.Key)?.Value ?? 0;
                        var result = socBalanceValue / socIniValue;
                        valueList.Add(new ColumnValue(item.Key, result));
                    }
                    soc_Relative.Add(socItem.Key, valueList);
                }
            }
            //end loopYear


            // SOC_output.gms
            // SOC_Summary
            var carbonSummary = new CarbonSummary();
            var cropFieldSummary = new List<CropFieldSummary>();
            carbonSummary.CropField = cropFieldSummary;

            var soc_Summary = new Dictionary<(string, int), List<ColumnValue>>();
            foreach (var item in cropAreaField1)
            {
                var sum = item.Value.Sum(x => x.Value);
                soc_Summary.Add(item.Key, new List<ColumnValue> { new ColumnValue("Total_crop_area", sum) });
            }

            foreach (var item in cropAreaField1)
            {
                var sum = item.Value.Sum(x => x.Value);
                var crpField = cropFieldSummary.SingleOrDefault(x => x.Name == item.Key.Item1);
                if (crpField == null)
                {
                    cropFieldSummary.Add(new CropFieldSummary { Name = item.Key.Item1, Summary = new List<YearSummary> { new YearSummary { Year = item.Key.Item2, Total_crop_area = sum } } });
                }
                else
                {
                    crpField.Summary.Add(new YearSummary { Year = item.Key.Item2, Total_crop_area = sum });
                }
            }

            var allFields = carbonInputs.TrippleKeyParam.Keys.Select(x => x.Item1).Distinct();
            var allYears = carbonInputs.TrippleKeyParam.Keys.Select(x => x.Item2).Distinct();

            foreach (var fieldLoop in allFields)
            {
                foreach (var yLoop in allYears)
                {
                    var sumCarbonManure = 0.0;
                    var sumCarbonResidues = 0.0;
                    var sumCarbonGreenManure = 0.0;
                    var sumCarbonCompost = 0.0;
                    foreach (var item in carbonInputs.TrippleKeyParam.Where(x => x.Key.Item1 == fieldLoop && x.Key.Item2 == yLoop))
                    {
                        sumCarbonManure += item.Value.SingleOrDefault(x => x.Key == "CarbonManure")?.Value ?? 0;
                        sumCarbonResidues += item.Value.SingleOrDefault(x => x.Key == "CarbonResidues")?.Value ?? 0;
                        sumCarbonGreenManure += item.Value.SingleOrDefault(x => x.Key == "CarbonGreenManure")?.Value ?? 0;
                        sumCarbonCompost += item.Value.SingleOrDefault(x => x.Key == "CarbonCompost")?.Value ?? 0;
                    }
                    soc_Summary[(fieldLoop, yLoop)].Add(new ColumnValue("SOC_CarbonManure", sumCarbonManure));
                    soc_Summary[(fieldLoop, yLoop)].Add(new ColumnValue("SOC_CarbonResidues", sumCarbonResidues));
                    soc_Summary[(fieldLoop, yLoop)].Add(new ColumnValue("SOC_CarbonGreenManure", sumCarbonGreenManure));
                    soc_Summary[(fieldLoop, yLoop)].Add(new ColumnValue("SOC_CarbonCompost", sumCarbonCompost));

                    var crpField = cropFieldSummary.Single(x => x.Name == fieldLoop);
                    var summary = crpField.Summary.Single(x => x.Year == yLoop);
                    summary.SOC_CarbonManure = sumCarbonManure;
                    summary.SOC_CarbonResidues = sumCarbonResidues;
                    summary.SOC_CarbonGreenManure = sumCarbonGreenManure;
                    summary.SOC_CarbonCompost = sumCarbonCompost;
                }
            }

            foreach (var item in soc_Balance)
            {
                var sum = item.Value.Sum(x => x.Value);
                soc_Summary[item.Key].Add(new ColumnValue("Balance", sum));

                var crpField = cropFieldSummary.Single(x => x.Name == item.Key.Item1);
                var summary = crpField.Summary.Single(x => x.Year == item.Key.Item2);
                summary.Balance = sum;
            }

            foreach (var item in socIni)
            {
                var sum = item.Value.Sum(x => x.Value);
                soc_Summary[item.Key].Add(new ColumnValue("Initial", sum));

                var crpField = cropFieldSummary.Single(x => x.Name == item.Key.Item1);
                var summary = crpField.Summary.Single(x => x.Year == item.Key.Item2);
                summary.Initial = sum;
            }

            foreach (var item in bulkDens)
            {
                var result = item.Value;
                foreach (var lYear in allYears)
                {
                    soc_Summary[(item.Key, lYear)].Add(new ColumnValue("Bulk_Density", result));
                }
            }

            foreach (var item in bulkDens)
            {
                var result = item.Value;
                foreach (var lYear in allYears)
                {
                    var summaries = cropFieldSummary.Single(x => x.Name == item.Key);
                    var summary = summaries.Summary.Single(x => x.Year == lYear);
                    summary.Bulk_Density = result;
                }
            }

            foreach (var item in socIni)
            {
                var crpField = cropFieldSummary.Single(x => x.Name == item.Key.Item1);
                var summary = crpField.Summary.Single(x => x.Year == item.Key.Item2);
                summary.Crop = item.Value[0].Key;
            }

            foreach (var item in soc_Summary)
            {
                var balanceValue = item.Value.SingleOrDefault(x => x.Key == "Balance")?.Value ?? 0;
                var initValue = item.Value.SingleOrDefault(x => x.Key == "Initial")?.Value ?? 0;
                var bulkValue = item.Value.SingleOrDefault(x => x.Key == "Bulk_Density")?.Value ?? 0;

                double result = 0;
                if (bulkValue > 0)
                {
                    result = (balanceValue + initValue) / parameterSet.LayerDepth / bulkValue;
                }

                item.Value.Add(new ColumnValue("IniCcontent", result));

                var carbonManure = item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonManure")?.Value ?? 0;
                var carbonResidues = item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonResidues")?.Value ?? 0;
                var carbonGreenManure = item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonGreenManure")?.Value ?? 0;
                var carbonCompost = item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonCompost")?.Value ?? 0;
                var emision = -1 * (carbonManure + carbonResidues + carbonGreenManure + carbonCompost - balanceValue);
                item.Value.Add(new ColumnValue("Emision", emision));
            }

            foreach (var item in cropFieldSummary)
            {
                foreach (var crpSummary in item.Summary)
                {
                    var result = (crpSummary.Balance + crpSummary.Initial) / parameterSet.LayerDepth / crpSummary.Bulk_Density;
                    crpSummary.IniCcontent = result;
                }
            }

            foreach (var item in soc_Summary)
            {
                var iniValue = item.Value.SingleOrDefault(x => x.Key == "IniCcontent")?.Value ?? 0;
                var balanceValue = item.Value.SingleOrDefault(x => x.Key == "Balance")?.Value ?? 0;

                var resultOmIni = iniValue / 0.54; //carbon_organicmatter_factor
                var resultCo2 = balanceValue * (44.0 / 12.0);

                item.Value.Add(new ColumnValue("OM_ini", resultOmIni));
                item.Value.Add(new ColumnValue("CO2seq", resultCo2));
            }

            foreach (var summaries in cropFieldSummary)
            {
                foreach (var item in summaries.Summary)
                {
                    var iniValue = item.IniCcontent;
                    var balanceValue = item.Balance;

                    var manure = item.SOC_CarbonManure;
                    var residues = item.SOC_CarbonResidues;
                    var greenManure = item.SOC_CarbonGreenManure;
                    var compost = item.SOC_CarbonCompost;

                    item.OM_ini = iniValue / 0.54; //carbon_organicmatter_factor
                    item.CO2seq = balanceValue * (44.0 / 12.0);

                    var emision = -1 * (manure + residues + greenManure + compost - balanceValue);
                    item.Emision = emision;
                }
            }


            // SOC_Summary_Farm
            var soc_Summary_Farm = new List<FarmYearSummary>();
            foreach (var item in cropAreaFarm)
            {
                var sum = item.Value.Sum(x => x.Value);
                soc_Summary_Farm.Add(new FarmYearSummary { Year = item.Key.Item2, Total_crop_area = sum });
            }

            foreach (var farm in cropAreaFarm)
            {
                var sumCarbonMan = 0.0;
                var sumCarbonRes = 0.0;
                var sumCarbonGreenMan = 0.0;
                var sumCarbonCom = 0.0;
                var sumEmision = 0.0;

                var sumBal = 0.0;
                var sumIni = 0.0;
                var sumInicc = 0.0;

                foreach (var item in soc_Summary.Where(x => x.Key.Item2 == farm.Key.Item2))
                {
                    var cropfieldArea = item.Value.SingleOrDefault(x => x.Key == "Total_crop_area")?.Value ?? 0;
                    sumCarbonMan += item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonManure")?.Value ?? 0;
                    sumCarbonRes += item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonResidues")?.Value ?? 0;
                    sumCarbonGreenMan += item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonGreenManure")?.Value ?? 0;
                    sumCarbonCom += item.Value.SingleOrDefault(x => x.Key == "SOC_CarbonCompost")?.Value ?? 0;
                    sumEmision += item.Value.SingleOrDefault(x => x.Key == "Emision")?.Value ?? 0;

                    sumBal += item.Value.SingleOrDefault(x => x.Key == "Balance")?.Value * cropfieldArea ?? 0;
                    sumIni += item.Value.SingleOrDefault(x => x.Key == "Initial")?.Value ?? 0;
                    sumInicc += item.Value.SingleOrDefault(x => x.Key == "IniCcontent")?.Value ?? 0;
                }
                var farmArea = soc_Summary_Farm.SingleOrDefault(x => x.Year == farm.Key.Item2);
                if (farmArea != null && farmArea.Total_crop_area > 0)
                {
                    var resultCarbonMan = sumCarbonMan / farmArea.Total_crop_area;
                    var resultCarbonRes = sumCarbonRes / farmArea.Total_crop_area;
                    var resultCarbonGreenMan = sumCarbonGreenMan / farmArea.Total_crop_area;
                    var resultCarboncom = sumCarbonCom / farmArea.Total_crop_area;
                    var resultEmision = sumEmision / farmArea.Total_crop_area;

                    var resultBal = sumBal / farmArea.Total_crop_area;
                    var resultIni = sumIni / farmArea.Total_crop_area;
                    var resultiniCc = sumInicc / farmArea.Total_crop_area;
                    var resultOm_Ini = resultiniCc / 0.54; //carbon_organicmatter_factor
                    var resultCo2 = resultBal * (44.0 / 12.0);
                    var resultCo2Tot = resultCo2 * farmArea.Total_crop_area;

                    farmArea.SOC_CarbonManure = resultCarbonMan;
                    farmArea.SOC_CarbonResidues = resultCarbonRes;
                    farmArea.SOC_CarbonGreenManure = resultCarbonGreenMan;
                    farmArea.SOC_CarbonCompost = resultCarboncom;

                    farmArea.Balance = resultBal;
                    farmArea.Initial = resultIni;
                    farmArea.IniCcontent = resultiniCc;
                    farmArea.OM_ini = resultOm_Ini;
                    farmArea.CO2seq = resultCo2;
                    farmArea.CO2seq_total = resultCo2Tot;
                    farmArea.Emision = resultEmision;
                }
            }
            return carbonSummary;
        }

        private double GetColumnValueForKey(Dictionary<(string, int), List<ColumnValue>> input, (string, int) key, string column)
        {
            return input[key].SingleOrDefault(x => x.Key.ToLower() == column.ToLower())?.Value ?? 0;
        }

        private List<int> GetYears(FarmInputsetsModel farmInputSet, CarbonRequest farmInput)
        {
            // assume for all cropfields the same years apply
            return farmInput.CropFields.First().HistoricalCropData.Select(x => x.Year).Distinct().OrderBy(o => o).ToList();
        }

        private Dictionary<string, List<string>> GetSoilTypeData(CarbonRequest farmInput)
        {
            var soilTypeData = new Dictionary<string, List<string>>();
            foreach (var field in farmInput.CropFields)
            {
                soilTypeData.Add(field.Name, new List<string> { field.SoilType });
            }
            return soilTypeData;
        }

        private Dictionary<string, SoilPropertyType> GetSoilPropertiesField(CarbonRequest farmInput)
        {
            var soilPropertiesField = new Dictionary<string, SoilPropertyType>();
            foreach (var field in farmInput.CropFields)
            {
                //soilPropertiesField.Add(field.Name, new SoilPropertyType { OM_Cont = field.SoilProperty.OM_Choice.ToLower() == "c" ? field.SoilProperty.OM_Const.Value * 1.85185 : field.SoilProperty.OM_Const.Value, Clay_Content = field.SoilProperty.Clay_Content.Value, OM_Corr = field.SoilProperty.OM_Const.Value <= 20 ? field.SoilProperty.OM_Const.Value : 0, Depth10 = field.SoilProperty.Depth10.Value, Depth = field.SoilProperty.Depth });
                soilPropertiesField.Add(field.Name, new SoilPropertyType { OM_Cont = field.SoilProperty.OM_Const.HasValue ? field.SoilProperty.OM_Const.Value : 0, C_Cont = field.SoilProperty.C_Const.HasValue ? field.SoilProperty.C_Const.Value : 0, Clay_Content = field.SoilProperty.Clay_Content.Value, OM_Corr = field.SoilProperty.OM_Const.HasValue && field.SoilProperty.OM_Const.Value <= 20 ? field.SoilProperty.OM_Const.Value : 0, C_Corr = field.SoilProperty.C_Const.HasValue && field.SoilProperty.C_Const.Value <= 10.8 ? field.SoilProperty.C_Const.Value : 0, Depth10 = field.SoilProperty.Depth10.Value, Depth = field.SoilProperty.Depth });
            }

            return soilPropertiesField;
        }

        private Dictionary<(string, string), SoilPropertyType> GetSoilPropSoilType(Dictionary<string, List<string>> soilTypeData, Dictionary<string, SoilPropertyType> soilPropertiesField)
        {
            var soilPropSoilType = new Dictionary<(string, string), SoilPropertyType>();
            foreach (var item in soilTypeData)
            {
                foreach (var item2 in item.Value)
                {
                    soilPropSoilType.Add((item.Key, item2), new SoilPropertyType { Bulk_Density = soilPropertiesField[item.Key].Bulk_Density, Clay_Content = soilPropertiesField[item.Key].Clay_Content, OM_Cont = soilPropertiesField[item.Key].OM_Cont, OM_Corr = soilPropertiesField[item.Key].OM_Corr, C_Cont = soilPropertiesField[item.Key].C_Cont, C_Corr = soilPropertiesField[item.Key].C_Corr }); // soilPropertiesField[item.Key]);
                }
            }
            return soilPropSoilType;
        }

        private Dictionary<(string, int), List<string>> GetHistoricalCropdata(CarbonRequest farmInput)
        {
            var historicalCropData = new Dictionary<(string, int), List<string>>();
            foreach (var field in farmInput.CropFields)
            {
                foreach (var cropYear in field.HistoricalCropData)
                {
                    historicalCropData.Add((field.Name, cropYear.Year), cropYear.Crops.Select(x => x.Crop).ToList());
                }
            }
            return historicalCropData;
        }


        private Dictionary<string, double> GetCropAreaField(CarbonRequest farmInput)
        {
            var cropAreaField = new Dictionary<string, double>();
            foreach (var field in farmInput.CropFields)
            {
                cropAreaField.Add(field.Name, field.Area);
            }
            return cropAreaField;
        }

        private Dictionary<string, SoilBulkDensityType> GetSoilBulkDensities(Dictionary<string, SoilPropertyType> soilPropertiesField, Dictionary<string, List<string>> soilTypeData, Dictionary<(string, string), SoilPropertyType> soilPropSoilType, Dictionary<(string, string), double> oM_corr_SoilType)
        {
            var soilBulkDensities = new Dictionary<string, SoilBulkDensityType>();
            foreach (var item in soilPropertiesField)
            {
                soilBulkDensities.Add(item.Key, new SoilBulkDensityType());
                if (soilTypeData[item.Key].Any(x => x == "Klei"))
                {
                    var soilBulkDensityKey = soilPropSoilType.Keys.Single(x => x.Item1 == item.Key && x.Item2 == "Klei");

                    var clay = (0.6117 + (0.003601 * item.Value.Clay_Content)) +
                        (0.002172 * oM_corr_SoilType[(item.Key, "Klei")] * oM_corr_SoilType[(item.Key, "Klei")]) +
                        (0.01715 * Math.Log(oM_corr_SoilType[(item.Key, "Klei")]));

                    soilBulkDensities[item.Key].Clay = clay;
                }
                if (soilTypeData[item.Key].Any(x => x == "Zand"))
                {
                    var soilBulkDensityKey = soilPropSoilType.Keys.Single(x => x.Item1 == item.Key && x.Item2 == "Zand");
                    soilBulkDensities[item.Key].Sand = 0.667 + (0.021 * oM_corr_SoilType[(item.Key, "Zand")]);
                }
            }
            return soilBulkDensities;
        }

        private Dictionary<(string, string, int), ClimateVariable> InitClimateFile(string knmiFile, string climateFile = null, int climateScenarioNumber = 0)
        {
            var data = new List<string>();
            using (StreamReader r = new StreamReader(knmiFile))
            {
                string line;
                while ((line = r.ReadLine()) != null)
                {
                    data.Add(line);
                }
            }


            var dataClimate = new List<string>();
            if (climateFile != null && climateScenarioNumber > 0)
            {
                using (StreamReader r = new StreamReader(climateFile))
                {
                    string line;
                    while ((line = r.ReadLine()) != null)
                    {
                        dataClimate.Add(line);
                    }
                }
            }

            // climate (zone, month, year)
            var climate = new Dictionary<(string, string, int), ClimateVariable>();

            if (dataClimate.Count() > 0)
            {
                foreach (var row in dataClimate)
                {
                    if (string.IsNullOrEmpty(row) || row.Split(';').Length == 0 || row.Split(';')[0] == "KNMI_zone")
                    {
                        continue;
                    }
                    var rowArr = row.Split(';');
                    if (int.Parse(rowArr[3]) == climateScenarioNumber)
                    {
                        climate.Add(($"zone{rowArr[0]}", $"M{rowArr[2]}", int.Parse(rowArr[1])), new ClimateVariable(double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture), double.Parse(rowArr[6], CultureInfo.InvariantCulture)));
                    }
                }

                foreach (var row in data)
                {
                    if (string.IsNullOrEmpty(row) || row.Split(';').Length == 0 || string.IsNullOrEmpty(row.Split(';')[0]))
                    {
                        continue;
                    }
                    var rowArr = row.Split(';');
                    if (int.Parse(rowArr[2]) <= 2023)
                    {
                        if (climate.Any(x => x.Key.Item1 == rowArr[0] && x.Key.Item2 == rowArr[1] && x.Key.Item3 == int.Parse(rowArr[2])))
                        {
                            climate[(rowArr[0], rowArr[1], int.Parse(rowArr[2]))].Temperature = double.Parse(rowArr[3], CultureInfo.InvariantCulture);
                            climate[(rowArr[0], rowArr[1], int.Parse(rowArr[2]))].Precipitation = double.Parse(rowArr[4], CultureInfo.InvariantCulture);
                            climate[(rowArr[0], rowArr[1], int.Parse(rowArr[2]))].Evapotranspiration = double.Parse(rowArr[5], CultureInfo.InvariantCulture);
                        }
                        else
                        {
                            climate.Add((rowArr[0], rowArr[1], int.Parse(rowArr[2])), new ClimateVariable(double.Parse(rowArr[3], CultureInfo.InvariantCulture), double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture)));
                        }

                    }
                    else
                    {
                        if (climate.Any(x => x.Key.Item1 == rowArr[0] && x.Key.Item2 == rowArr[1] && x.Key.Item3 == int.Parse(rowArr[2])))
                        {
                            continue;
                        }
                        climate.Add((rowArr[0], rowArr[1], int.Parse(rowArr[2])), new ClimateVariable(double.Parse(rowArr[3], CultureInfo.InvariantCulture), double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture)));
                    }
                }
            }
            else
            {
                foreach (var row in data)
                {
                    if (string.IsNullOrEmpty(row) || row.Split(';').Length == 0 || string.IsNullOrEmpty(row.Split(';')[0]))
                    {
                        continue;
                    }
                    var rowArr = row.Split(';');
                    climate.Add((rowArr[0], rowArr[1], int.Parse(rowArr[2])), new ClimateVariable(double.Parse(rowArr[3], CultureInfo.InvariantCulture), double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture)));
                }
            }

            return climate;
        }

        private static Dictionary<string, List<ClimateVariable>> GetClimateVariable2(string knmiFile, SetsForDataTransformation dataTransformationSet, GIS_Soils_Sets gisSoilSet, int loopYear, CarbonRequest farmInput, string climateFile = null, int climateScenarioNumber = 0, Dictionary<(string, string, int), ClimateVariable> climate = null)
        {
            var climateVariable = new Dictionary<string, List<ClimateVariable>>();

            var farmPostalCode = farmInput.PostalCode;
            //if (farmInput.geometry != null)
            //{
            //    // determine poatalcode from geometry
            //    string res = GetPostalCodeFromGeometry(farmInput.geometry);
            //    farmPostalCode = 8211;
            //}
            var farmZone = dataTransformationSet.KNMI_zone_Reg.SingleOrDefault(x => x.Reg.Contains(farmPostalCode)).KNMI_zone;
            foreach (var field in farmInput.CropFields.Select(s => s.Name))
            {
                var monthClimateList = new List<ClimateVariable>();
                foreach (var month in gisSoilSet.AllMonths)
                {
                    var climateYear = loopYear > 2050 || loopYear < 2011 ? 2012 : loopYear;
                    var monthData = climate.Single(x => x.Key.Item1 == farmZone && x.Key.Item2 == month && x.Key.Item3 == climateYear).Value;
                    var newMonthData = new ClimateVariable(monthData.Temperature, monthData.Precipitation, monthData.Evapotranspiration * 1.25);
                    monthClimateList.Add(newMonthData);
                }
                climateVariable.Add(field, monthClimateList);
            }

            return climateVariable;

        }

        private static Dictionary<string, List<ClimateVariable>> GetClimateVariable(string knmiFile, SetsForDataTransformation dataTransformationSet, GIS_Soils_Sets gisSoilSet, int loopYear, CarbonRequest farmInput, string climateFile = null, int climateScenarioNumber = 0)
        {
            var data = new List<string>();
            using (StreamReader r = new StreamReader(knmiFile))
            {
                string line;
                while ((line = r.ReadLine()) != null)
                {
                    data.Add(line);
                }
            }


            var dataClimate = new List<string>();
            if (climateFile != null && climateScenarioNumber > 0)
            {
                using (StreamReader r = new StreamReader(climateFile))
                {
                    string line;
                    while ((line = r.ReadLine()) != null)
                    {
                        dataClimate.Add(line);
                    }
                }
            }

            var farmPostalCode = farmInput.PostalCode;
            //if (farmInput.geometry != null)
            //{
            //    // determine poatalcode from geometry
            //    string res = GetPostalCodeFromGeometry(farmInput.geometry);
            //    farmPostalCode = 8211;
            //}

            var farmZone = dataTransformationSet.KNMI_zone_Reg.SingleOrDefault(x => x.Reg.Contains(farmPostalCode)).KNMI_zone;
            // climate (zone, month, year)
            var climate = new Dictionary<(string, string, int), ClimateVariable>();

            if (dataClimate.Count() > 0)
            {
                foreach (var row in dataClimate)
                {
                    if (string.IsNullOrEmpty(row) || row.Split(';').Length == 0 || row.Split(';')[0] == "KNMI_zone")
                    {
                        continue;
                    }
                    var rowArr = row.Split(';');
                    if (int.Parse(rowArr[3]) == climateScenarioNumber)
                    {
                        climate.Add(($"zone{rowArr[0]}", $"M{rowArr[2]}", int.Parse(rowArr[1])), new ClimateVariable(double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture), double.Parse(rowArr[6], CultureInfo.InvariantCulture)));
                    }
                }

                foreach (var row in data)
                {
                    if (string.IsNullOrEmpty(row) || row.Split(';').Length == 0 || string.IsNullOrEmpty(row.Split(';')[0]))
                    {
                        continue;
                    }
                    var rowArr = row.Split(';');
                    if (climate.Any(x => x.Key.Item1 == rowArr[0] && x.Key.Item2 == rowArr[1] && x.Key.Item3 == int.Parse(rowArr[2])))
                    {
                        continue;
                    }
                    climate.Add((rowArr[0], rowArr[1], int.Parse(rowArr[2])), new ClimateVariable(double.Parse(rowArr[3], CultureInfo.InvariantCulture), double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture)));
                }
            }
            else
            {
                foreach (var row in data)
                {
                    if (string.IsNullOrEmpty(row) || row.Split(';').Length == 0 || string.IsNullOrEmpty(row.Split(';')[0]))
                    {
                        continue;
                    }
                    var rowArr = row.Split(';');
                    climate.Add((rowArr[0], rowArr[1], int.Parse(rowArr[2])), new ClimateVariable(double.Parse(rowArr[3], CultureInfo.InvariantCulture), double.Parse(rowArr[4], CultureInfo.InvariantCulture), double.Parse(rowArr[5], CultureInfo.InvariantCulture)));
                }
            }


            var climateVariable = new Dictionary<string, List<ClimateVariable>>();

            foreach (var field in farmInput.CropFields.Select(s => s.Name))
            {
                var monthClimateList = new List<ClimateVariable>();
                foreach (var month in gisSoilSet.AllMonths)
                {
                    var climateYear = loopYear > 2050 || loopYear < 2011 ? 2012 : loopYear;
                    var monthData = climate.Single(x => x.Key.Item1 == farmZone && x.Key.Item2 == month && x.Key.Item3 == climateYear).Value;
                    var newMonthData = new ClimateVariable(monthData.Temperature, monthData.Precipitation, monthData.Evapotranspiration * 1.25);
                    monthClimateList.Add(newMonthData);
                }
                climateVariable.Add(field, monthClimateList);
            }

            return climateVariable;
        }

        private string GetProvinceCode(SetsForDataTransformation dataTransformationSet, int postalCode)
        {
            // get number of postalcode based on geometry?
            var farm_reg = dataTransformationSet.NUTS0NUTSII.SingleOrDefault(s => s.reg.Any(x => x == postalCode));
            if (farm_reg == null)
            {
                throw new Exception($"reg {postalCode} not found");
            }
            var provinceCode = farm_reg.prov;
            return provinceCode;
        }

        private Dictionary<(string, int), List<ColumnValue>> GetCarbonInputGreenManure(int loopYear, CarbonRequest farmInput, List<YieldGreenManureModel> yieldGreenManureConfig)
        {
            var carbonInputGreenManure = new Dictionary<(string, int), List<ColumnValue>>();

            if (farmInput == null)
            {
            }
            else
            {
                foreach (var field in farmInput.CropFields)
                {
                    foreach (var manures in field.GreenManures)
                    {
                        foreach (var manure in manures.GreenManureTypes)
                        {
                            var manureQ = yieldGreenManureConfig.SingleOrDefault(x => x.ManureName.ToLower() == manure.Type.ToLower());
                            double manureQuality2 = manure.Quality == Quality.Good ? manureQ.GoodQuality : manure.Quality == Quality.Average ? manureQ.AverageQuality : manure.Quality == Quality.Bad ? manureQ.BadQuality : 0;
                            if (manureQuality2 > 0)
                            {
                                carbonInputGreenManure.Add((field.Name, manures.Year), new List<ColumnValue> { new ColumnValue(manure.Type, manureQuality2) });
                            }
                        }
                    }
                }
            }

            return carbonInputGreenManure;
        }

        //private static string GetPostalCodeFromGeometry(NetTopologySuite.Geometries.Geometry geometry)
        //{
        //    //// Set crs default geometry factory to EPSG:4326
        //    NetTopologySuite.NtsGeometryServices.Instance = new NetTopologySuite.NtsGeometryServices(NetTopologySuite.Geometries.Implementation.CoordinateArraySequenceFactory.Instance,
        //                                                                                             new PrecisionModel(PrecisionModels.Floating), 4326);
        //    // Register all vector drivers
        //    Ogr.RegisterAll();

        //    // item.geometry is always in WGS84 so reproject to EPSG:28992
        //    var localReference = new LocalReference("EPSG:28992");
        //    var localGeometry = localReference.ToLocalPolygon(geometry as Polygon);

        //    // get postal code from shape file
        //    // open shape file
        //    using (var postalcodes = Ogr.Open("config/PC4/PC4.shp", 0))
        //    {
        //        // get first layer
        //        var pc4Layer = postalcodes.GetLayerByIndex(0);

        //        // set spatial filter to geometry ( only intersecting features are retrieved )
        //        pc4Layer.SetSpatialFilter(localGeometry.Centroid.ToOGRGeometry());

        //        // get next feature
        //        var pc4Feature = pc4Layer.GetNextFeature();

        //        // if feature retrieved get the PC4 attribute from it
        //        if (pc4Feature is object)
        //        {
        //            return pc4Feature.GetFieldAsString("PC4");
        //        }
        //    }

        //    return "";
        //}

        private string MapSoil(string soil)
        {
            switch (soil)
            {
                case "6":
                case "7":
                    return "Klei";
                case "1":
                    return "Zand";
                default:
                    return "Loss";
            }
        }

        private static double CalculateMedian(List<double> numbers)
        {
            if (numbers == null || numbers.Count == 0)
            {
                throw new InvalidOperationException("The list is empty or null.");
            }

            numbers.Sort();

            int count = numbers.Count;
            if (count % 2 == 0)
            {
                // Even number of elements
                double mid1 = numbers[count / 2 - 1];
                double mid2 = numbers[count / 2];
                return (mid1 + mid2) / 2.0;
            }
            else
            {
                // Odd number of elements
                return numbers[count / 2];
            }
        }
    }
}
