using System;
using System.Linq;
using System.Threading.Tasks;

using FMSAdmin.Data;
using FMSAdmin.Models;
using FMSAdmin.Entities;
using FMSApp.Services;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authorization;
using FMSAdmin.Helpers;
using System.Data;
using System.IO;
using OfficeOpenXml;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using FMSAdmin.Models.Formula;
using FMSAdmin.Helpers.Formula;

namespace FMSApp.Controllers {
    [Authorize]
    [ApiController]
    [ApiVersion("1")]
    [Route("api/app/[controller]")]
    public class FormulaController : Controller {

        private static Regex regexParameter = new Regex(@"\b[A-Z]\b", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
        private static Regex regexFunction = new Regex(@"\$([\w]+).+\$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
        private static FormulaTableManager tableManager;
        private readonly ILogger<FormulaController> _logger;
        private readonly FormulaService _service;
        private readonly FMSContext _context;
        public FormulaController(
            ILogger<FormulaController> logger,
            FormulaService service,
            FMSContext context
            ) {
            _logger = logger;
            _service = service;
            _context = context;
            tableManager = new FormulaTableManager(context);
        }

        /// <summary>
        // 성능분석-챠트에 사용되는듯
        ///</summary>
        [HttpGet("[action]")]
        public IActionResult Calculation([FromQuery] int siteId, [FromQuery] int facilityTypeId, [FromQuery] int formulaId, [FromQuery] int facilityCode, [FromQuery] int timeIntervalType, [FromQuery] string sDate, [FromQuery] string eDate) {
            DateTime startDate = DateTime.Parse(sDate);
            DateTime endDate = DateTime.Parse(eDate);

            List<CalculationResult> list = new List<CalculationResult>();

            BemsFormula formula = GetFormula(Util.ToInt(siteId), Util.ToInt(facilityTypeId), Util.ToInt(facilityCode), Util.ToInt(formulaId));
            if (formula == null) return Ok(new { });

            TimeInterval timeInterval = (TimeInterval)timeIntervalType;

            ArrayList parameters = new ArrayList();

            MathCalculator calculator = new MathCalculator();

            if (false == calculator.Compile(formula.Formula)) {
                throw new Exception("Failed to compile formula: " + formula.Formula);
            }
            var match = regexParameter.Match(formula.Formula);
            if (match.Success) {
                var queryParameters = _context.BemsFormulaParameter.Where(x =>
                    x.SiteId == siteId &&
                    x.FacilityTypeId == facilityTypeId &&
                    (facilityTypeId >= 100 || x.FacilityCode == facilityCode) &&
                    x.FormulaId == formulaId);

                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>();
                var test = queryParameters.ToList();
                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 = _service.GetPointValues(siteId, p.ParameterFacilityCode,
                        p.ParameterPropertyId, timeInterval, startDate, endDate);

                    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) {
                        list.Add(new CalculationResult {
                            DateTime = parameterBox.DateTime,
                            ShortDateTime = timeInterval == TimeInterval.Day ? parameterBox.DateTime.ToString("yyyy.MM.dd") : parameterBox.DateTime.ToString("yyyy.MM"),
                            Value = Math.Round((double)value, 2)
                            //Value = (double)value
                        });
                    }
                    if (parameterBox.NextStage() == false) {
                        break;
                    }
                }
            } else {
                IList emptyArr = new List<CalculationResult>();
                return Ok(new {
                    total = 0,
                    page = 1,
                    limit = 0,
                    list = emptyArr,
                    maxValue = 0
                });
            }

            //list.Select(x=>x.Value).Max();
            return Ok(new {
                total = list.Count(),
                page = 1,
                limit = 0,
                list = list,
                maxValue = list.Select(x => x.Value).Max()
            });

        }

        public BemsFormula GetFormula(int siteId, int facilityTypeId, int facilityCode, int formulaId) {
            var queryFormula = from f in _context.BemsFormula
                               where f.SiteId == siteId && f.FacilityTypeId == facilityTypeId &&
                                   (facilityTypeId >= 100 || f.FacilityCode == facilityCode) && f.FormulaId == formulaId
                               select f;
            if (queryFormula == null || queryFormula.Any() == false) {
                return null;
            }
            return queryFormula.Single();
        }
        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;
            }
        }
    }

}
