using Microsoft.Extensions.Logging;
using FMSAdmin.Data;
using System.Linq;
using FMSAdmin.Helpers;
using FMSAdmin.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using FMSAdmin.Models.Formula;
using FMSAdmin.Helpers.Formula;
using System.Collections;
using System.Text.RegularExpressions;
using FMSAdmin.Models;

namespace FMSAdmin.Services {
    public class EnergyUsageService {
        private readonly ILogger<EnergyUsageService> _logger;
        private readonly FMSContext _context;

        public EnergyUsageService(
            ILogger<EnergyUsageService> logger,
            FMSContext context
        ) {
            _logger = logger;
            _context = context;
        }

        public IList<CalculationResult> GetEnergyDailyList(int siteId, EnergyInterval interval, EnergyType type, DateTime startDate, DateTime endDate) {
            var dic = GetEnergyDailyDic(siteId, interval, type, startDate, endDate);
            return dic.Values.ToList();
        }

        public IDictionary<string, CalculationResult> GetEnergyDailyDic(int siteId, EnergyInterval interval, EnergyType type, DateTime startDate, DateTime endDate) {
            FormulaTableManager tableManager = new FormulaTableManager(_context);

            var formulaId = 1;
            int facilityTypeId = 100;
            int facilityCode = 1;
            if (type == EnergyType.전기) {
                facilityTypeId = 100;
                facilityCode = 1;
            } else if (type == EnergyType.가스) {
                facilityTypeId = 101;
                facilityCode = 2;
            } else if (type == EnergyType.수도) {
                facilityTypeId = 102;
                facilityCode = 3;
            } else if (type == EnergyType.등유) {
                facilityTypeId = 104;
                facilityCode = 4;
            } else if (type == EnergyType.LPG) {
                facilityTypeId = 105;
                facilityCode = 5;
            } else if (type == EnergyType.중온수) {
                facilityTypeId = 106;
                facilityCode = 6;
            }
            var timeInterval = interval == EnergyInterval.일 ?
                                TimeInterval.Day
                                : interval == EnergyInterval.월 ?
                                TimeInterval.Month
                                : TimeInterval.Day;

            IDictionary<string, CalculationResult> list = new Dictionary<string, CalculationResult>();

            var queryFormula = from f in _context.BemsFormula
                               where f.SiteId == siteId && f.FacilityTypeId == facilityTypeId &&
                                   (facilityTypeId >= 100 || f.FacilityCode == facilityCode) && f.FormulaId == formulaId
                               select f;

            var formula = queryFormula.FirstOrDefault();
            if (formula == null) {
                throw new Exception("수식정보가 없습니다.");
            }

            ArrayList parameters = new ArrayList();
            MathCalculator calculator = new MathCalculator();

            if (false == calculator.Compile(formula.Formula)) {
                throw new Exception("Failed to compile formula: " + formula.Formula);
            }

            var regexParameter = new Regex(@"\b[A-Z]\b", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
            var match = regexParameter.Match(formula.Formula);
            if (match.Success) {
                var queryParameters = from x in _context.BemsFormulaParameter
                                      where x.SiteId == siteId && x.FacilityTypeId == facilityTypeId &&
                                      (facilityTypeId >= 100 || x.FacilityCode == facilityCode) && x.FormulaId == formulaId
                                      select x;
                if (queryParameters == null || queryParameters.Any() == false) {
                    throw new Exception(string.Format("Not Found Formula Parameters: {0},{1},{2},{3}", siteId, facilityTypeId, facilityCode, formulaId));
                }
                List<MathParameterValue> parametersForArray = new List<MathParameterValue>();
                foreach (var p in queryParameters.ToList()) {
                    char parameter = p.ParameterId[0];
                    var pValue = new MathParameterValue {
                        Variable = (MathCalculator.Parameter)(parameter - 'A'),
                        Value = 0,
                    };
                    parametersForArray.Add(pValue);

                    var values = _CreateCalculationValues(startDate, endDate, timeInterval);

                    var pointvalues = timeInterval == TimeInterval.Day ?
                                        GetMonitoringPointHistoryDaily(siteId, p.ParameterFacilityCode,
                                        p.ParameterPropertyId, startDate, endDate)
                                        : timeInterval == TimeInterval.Month ?
                                        GetMonitoringPointHistoryMonthly(siteId, p.ParameterFacilityCode,
                                        p.ParameterPropertyId, startDate, endDate)
                                        : null;

                    if (pointvalues == null)
                        continue;

                    CalculationValue pp = null;
                    CalculationValue v = null;
                    for (var i = 0; i < values.Length; i++) {
                        v = values[i];
                        for (var j = 0; j < pointvalues.Length; j++) {
                            pp = pointvalues[j];
                            if (pp.DateTime == v.DateTime) {
                                v.Value += pp.Value;
                            }
                        }
                    }

                    parameters.Add(new CalculationParameter {
                        ParameterValue = pValue,
                        Values = values
                    });
                }
                calculator.SetParameter(parametersForArray.ToArray());
            }

            {
                var functions = calculator.GetFunctions();
                if (functions.Any()) {
                    foreach (var f in functions) {
                        var tableValues = tableManager.GetTableValues(f.Function.FunctionName);
                        if (tableValues == null) {
                            throw new Exception("Not Found Function.");
                        }

                        f.Function.FunctionDelegate = (x) => {
                            var index = Array.BinarySearch(tableValues, new FormulaTableManager.TableComparerValue { XValue = (double)x });
                            if (index < 0) {
                                index = ~index;
                                if (index == 0) {
                                    return Math.Round((decimal)tableValues[0].YValue, 2);
                                } else if (index == tableValues.Length) {
                                    return Math.Round((decimal)tableValues[tableValues.Length - 1].YValue, 2);
                                }
                            }
                            return Math.Round((decimal)tableValues[index].YValue, 2);
                        };
                    }
                }
            }

            DateTime dateTime = DateTime.Now;
            if (parameters.Count > 0) {
                var ps = parameters.Cast<CalculationParameter>().ToArray();
                ParameterBox parameterBox = new ParameterBox(ps);

                while (parameterBox.Sync()) {
                    var value = calculator.Calculate();
                    if (value != null) {
                        var date = parameterBox.DateTime.ToString("yyyy-MM-dd");
                        list.Add(date, new CalculationResult {
                            DateTime = parameterBox.DateTime,
                            ShortDateTime = date,
                            Value = value < 0 ? 0 : Math.Round((double)value, 2)
                        });
                    }
                    if (parameterBox.NextStage() == false) {
                        break;
                    }
                }
            }

            return list;
        }

        public CalculationValue[] GetMonitoringPointHistoryDaily(int siteId, int facilityCode, int propertyId, DateTime startDate, DateTime endDate) {
            var query = _context.BemsMonitoringPointHistoryDaily.Where(x =>
                    x.SiteId == siteId &&
                    x.FacilityCode == facilityCode &&
                    x.PropertyId == propertyId &&
                    startDate <= x.CreatedDateTime && x.CreatedDateTime <= endDate);

            if (query.Any() == false) {
                return null;
            }

            return query.Select(x => new CalculationValue {
                DateTime = x.CreatedDateTime,
                Value = x.DailyValue
            }).ToArray();
        }

        public CalculationValue[] GetMonitoringPointHistoryMonthly(int siteId, int facilityCode, int propertyId, DateTime startDate, DateTime endDate) {
            var groupQuery = from data in _context.BemsMonitoringPointHistoryDaily
                             where data.SiteId == siteId &&
                                   data.FacilityCode == facilityCode &&
                                   data.PropertyId == propertyId &&
                                   startDate <= data.CreatedDateTime && data.CreatedDateTime <= endDate
                             group data by new { Year = data.CreatedDateTime.Year, Month = data.CreatedDateTime.Month } into g
                             // select g;
                             select new {
                                 Year = g.Key.Year,
                                 Month = g.Key.Month,
                                 SumDailyValue = g.Sum(x => x.DailyValue),
                                 AverageDailyValue = g.Average(x => x.DailyValue)
                             };

            if (groupQuery == null || groupQuery.Any() == false) {
                return null;
            }

            //CalculationValue[] values = { };// = new CalculationValue[groupQuery.ToList().Count()];
            CalculationValue[] values = new CalculationValue[groupQuery.ToList().Count()];

            var i = 0;
            foreach (var g in groupQuery.ToList()) {
                double dValue = 0;
                if (_CheckPointValueType(siteId, facilityCode, propertyId) == 0) dValue = g.SumDailyValue;
                else if (_CheckPointValueType(siteId, facilityCode, propertyId) == 1) dValue = g.AverageDailyValue;
                else if (_CheckPointValueType(siteId, facilityCode, propertyId) == 2) {
                    dValue = g.SumDailyValue;
                    dValue = (dValue > 0) ? 1 : 0;
                }
                values[i] = new CalculationValue {
                    DateTime = new DateTime(g.Year, g.Month, 1),
                    //Value = g.Sum(x => x.CurrentValue)
                    Value = dValue
                };

                i++;
            }

            return values;
        }

        private int _CheckPointValueType(int siteId, int facilityCode, int propertyId) {
            // return 0 -> 누적, 1 -> 평균,  2 -> On,Off
            var Query = from data in _context.BemsMonitoringPoint
                        where data.SiteId == siteId &&
                                data.FacilityCode == facilityCode &&
                                data.PropertyId == propertyId
                        select data;

            if (Query.FirstOrDefault().ValueType == 2) return 2;

            if (Query.FirstOrDefault().ValueType == 0 && Query.FirstOrDefault().FuelTypeId > 0) return 0;
            if (Query.FirstOrDefault().ValueType == 0 && Query.FirstOrDefault().FuelTypeId == 0) return 1;
            return 0;
        }

        private CalculationValue[] _CreateCalculationValues(DateTime startDate, DateTime endDate, TimeInterval timeIntervalType) {
            DateTime datetime = startDate;
            int count = _GetCountInTimeRange(startDate, endDate, timeIntervalType);
            CalculationValue[] values = new CalculationValue[count];

            for (var i = 0; i < values.Length; i++) {
                values[i] = new CalculationValue {
                    DateTime = datetime,
                    Value = 0
                };
                _NextTimeFromTimeInterval(ref datetime, timeIntervalType);
            }
            return values;
        }

        private int _GetCountInTimeRange(DateTime startDate, DateTime endDate, TimeInterval timeIntervalType) {
            TimeSpan ts = endDate - startDate;
            switch (timeIntervalType) {
                case TimeInterval.QuarterMin:
                    return (int)ts.TotalMinutes / 15 + 1;
                case TimeInterval.Hour:
                    return (int)ts.TotalHours + 1;
                case TimeInterval.Day:
                    return (int)ts.TotalDays + 1;
                case TimeInterval.Month: {
                        int count = 0;
                        DateTime date = startDate;
                        //while (date.Year <= endDate.Year && date.Month <= endDate.Month)
                        while (date <= endDate) {
                            count++;
                            date = date.AddMonths(1);
                        }
                        return count;
                    }
                default: // TimeInterval.Year:
                    {
                        int count = 0;
                        DateTime date = startDate;
                        while (date.Year <= endDate.Year) {
                            count++;
                            date = date.AddYears(1);
                        }
                        return count;
                    }
            }

        }
        private void _NextTimeFromTimeInterval(ref DateTime datetime, TimeInterval timeIntervalType) {
            switch (timeIntervalType) {
                case TimeInterval.QuarterMin:
                    datetime = datetime.AddMinutes(15);
                    break;
                case TimeInterval.Hour:
                    datetime = datetime.AddHours(1);
                    break;
                case TimeInterval.Day:
                    datetime = datetime.AddDays(1);
                    break;
                case TimeInterval.Month:
                    datetime = datetime.AddMonths(1);
                    break;
                default: // TimeInterval.Year:
                    datetime = datetime.AddYears(1);
                    break;
            }
        }

        public IQueryable<BemsFuelType> GetFuelType() {
            var data = _context.BemsFuelType.Where(x => x.FuelTypeId != 0);
            return data;
        }
    }
}