using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Http;
using System.Web.Http.Description;
using DevExpress.Office.Utils;
using iBemsDataService.Model;
using iBemsDataService.Util;
using System.Data.Entity.Validation;
using System.Text;
using System.Text.RegularExpressions;
using iBemsDataService.Controllers.Bems.Formula;
using System.Diagnostics;
using iBemsDataService.Controllers.Bems.Common;
using System.Transactions;

namespace iBemsDataService.Controllers
{


    public class BemsFormulaController : ApiController
    {
        private iBemsEntities db;
        private PointHistoryValueManager pointHistoryValueManager;
        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 = new FormulaTableManager();
        //private BemsMonitoringPointHistoryController pointHistoryController;

        public BemsFormulaController()
        {
            db = new iBemsEntities();
            pointHistoryValueManager = new PointHistoryValueManager(db);
        }

        public BemsFormulaController(iBemsEntities bemsDb)
        {
            db = bemsDb;
        }

        [ActionName("Calculation")]
        public List<CalculationResult> GetBemsFormula()
        {
            Uri uri = Request.RequestUri;
            var uriQuery = HttpUtility.ParseQueryString(uri.Query);

            string uriSiteId = uriQuery.Get("SiteId");
            string uriFacilityTypeId = uriQuery.Get("FacilityTypeId");
            string uriFormulaId = uriQuery.Get("FormulaId");
            string uriTimeIntervalType = uriQuery.Get("TimeIntervalType");
            string uriStartDate = uriQuery.Get("StartDate");
            string uriEndDate = uriQuery.Get("EndDate");
            string uriFacilityCode = uriQuery.Get("FacilityCode");

            int siteId = -1, facilityTypeId = -1, formulaId = -1, timeIntervalType = -1, facilityCode = -1;

            DateTime startDate, endDate;

            if (int.TryParse(uriSiteId, out siteId) == false ||
                int.TryParse(uriFacilityTypeId, out facilityTypeId) == false ||
                int.TryParse(uriFormulaId, out formulaId) == false ||
                int.TryParse(uriTimeIntervalType, out timeIntervalType) == false ||
                int.TryParse(uriFacilityCode, out facilityCode) == false ||
                DateTime.TryParse(uriStartDate, out startDate) == false ||
                DateTime.TryParse(uriEndDate, out endDate) == false)
            {
                throw new Exception(string.Format("Not Found FFFF Parameters: {0},{1},{2},{3},{4},{5},{6}",
                    uriSiteId, uriFacilityTypeId, facilityCode, uriFormulaId, uriTimeIntervalType, uriStartDate, uriEndDate));
            }

            return GetBemsFormulaReal(siteId, facilityTypeId, facilityCode, formulaId, startDate, endDate, timeIntervalType);

        }

        public List<CalculationResult> GetBemsFormulaReal(int siteId, int facilityTypeId, int facilityCode, int formulaId, DateTime startDate, DateTime endDate, int timeIntervalType)
        {
            List<CalculationResult> list = new List<CalculationResult>();
            BemsFormula formula = GetFormula(siteId, facilityTypeId, facilityCode, formulaId);
            if (formula == null) return list;

            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 = db.BemsFormulaParameter.Where(x =>
                    x.SiteId == siteId &&
                    x.FacilityTypeId == facilityTypeId &&
                        //x.FacilityCode == facilityCode &&
                        //(facilityTypeId >= 24 || x.FacilityCode == facilityCode) && // hcLee 2015 04 21
                    (facilityTypeId >= 100 || x.FacilityCode == facilityCode) && // hcLee 2015 04 28
                    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>();
                foreach (var p in queryParameters)
                {
                    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);  //2015 04 22 hcLee

                    var pointvalues = pointHistoryValueManager.GetPointValues(siteId, p.ParameterFacilityCode,
                        p.ParameterPropertyId, timeInterval, startDate, endDate);

                    if (pointvalues == null)
                        continue;


                    int index = 0;
                    CalculationValue pp = null;
                    CalculationValue v = null;
                    for (var i = 0; i < values.Length; i++)
                    {
                        v = values[i];
                        if (pp == null)
                        {
                            if (pointvalues.Length <= index) break;

                            pp = pointvalues[index];
                        }

                        if (pp.DateTime == v.DateTime)
                        {
                            v.Value += pp.Value;
                            pp = null;
                            index++;
                            if (pointvalues.Length <= index) break;
                        }
                    }

                    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) =>
                        {
                            //Trace.WriteLine( typeof( x ) );

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

            //Trace.WriteLine( calculator.Calculate() );


            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,
                            Value = Math.Round((double)value, 2)
                            //Value = (double)value
                        });
                    }
                    if (parameterBox.NextStage() == false)
                    {
                        break;
                    }
                }
            }
            else
            {
                /*       2015 06 12 hcLee 막음,  데이터가 하나도 없는 경우 현재시간으로 이상한 값이 한개 오는 현상 
                var value = calculator.Calculate();
                if( value != null )
                {

                    list.Add( new CalculationResult
                    {

                        DateTime = dateTime ,
                        Value = (double)value
                    } );
                }     */
            }
            return list;
        }

        [ResponseType(typeof(void))]
        [ActionName("ControlPoint")]
        public IHttpActionResult PostControlPoint()
        {
            Uri uri = Request.RequestUri;
            var uriQuery = HttpUtility.ParseQueryString(uri.Query);

            string uriSiteId = uriQuery.Get("SiteId");
            string uriFacilityTypeId = uriQuery.Get("FacilityTypeId");
            string uriFacilityCode = uriQuery.Get("FacilityCode");
            string uriFormulaId = uriQuery.Get("FormulaId");
            string uriControlValue = uriQuery.Get("ControlValue");

            int siteId = -1, facilityTypeId = -1, formulaId = -1, facilityCode = -1;
            double controlValue = 0;

            if (int.TryParse(uriSiteId, out siteId) == false ||
                int.TryParse(uriFacilityTypeId, out facilityTypeId) == false ||
                int.TryParse(uriFormulaId, out formulaId) == false ||
                int.TryParse(uriFacilityCode, out facilityCode) == false ||
                double.TryParse(uriControlValue, out controlValue) == false)
            {
                throw new Exception(string.Format("Not Found FFFF Parameters: {0},{1},{2},{3},{4} ", uriSiteId, uriFacilityTypeId, facilityCode, uriFormulaId, uriControlValue));
            }

            BemsFormula formula = GetFormula(siteId, facilityTypeId, facilityCode, formulaId);
            if (formula == null) return StatusCode(HttpStatusCode.NoContent);

            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 = db.BemsFormulaParameter.Where(x =>
                    x.SiteId == siteId &&
                    x.FacilityTypeId == facilityTypeId &&
                        //x.FacilityCode == facilityCode &&
                        //(facilityTypeId >= 24 || x.FacilityCode == facilityCode) && // hcLee 2015 04 21
                    (facilityTypeId >= 100 || x.FacilityCode == facilityCode) && // hcLee 2015 04 28
                    x.FormulaId == formulaId);

                if (queryParameters.Any() == false)
                {
                    throw new Exception(string.Format("Not Found 2222 Parameters: {0},{1},{2},{3},{4}", siteId, facilityTypeId, facilityCode, formulaId, uriControlValue));
                }
                List<MathParameterValue> parametersForArray = new List<MathParameterValue>();

                using (var transaction = new TransactionScope())
                {
                    foreach (var p in queryParameters)
                    {
                        char parameter = p.ParameterId[0];
                        var pValue = new MathParameterValue
                        {
                            Variable = (MathCalculator.Parameter)(parameter - 'A'),
                            Value = 0,
                        };
                        DateTime currentTime = DateTime.Now;
                        try
                        {
                            {
                                var control = new BemsControlPointHistory
                                {
                                    SiteId = siteId,

                                    //FacilityTypeId = facilityTypeId,    // 2015 08 05 두소장과 통화중 일단 결정함.
                                    FacilityTypeId = facilityTypeId,    // 2015 08 05 두소장과 통화중 일단 결정함.

                                    FacilityCode = p.ParameterFacilityCode,
                                    PropertyId = p.ParameterPropertyId,    // ???? hcLee
                                    CreateDateTime = currentTime,
                                    ControlValue = controlValue,
                                    WriteServiceName = null,
                                    ActionDateTime = null
                                };
                                db.BemsControlPointHistory.Add(control);
                                db.SaveChanges();
                            }
                        }
                        catch (Exception e)
                        {
                            Trace.WriteLine(e.Message);
                            //throw e;
                            throw;
                        }
                    }
                    transaction.Complete();
                }
                //calculator.SetParameter(parametersForArray.ToArray());
            }
            return StatusCode(HttpStatusCode.NoContent);
        }

        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 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;
            }
        }

        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;
                    }
            }

        }

        /*
                [ActionName("ServiceType")]
                public List<CalculationResult> GetBemsFormulaService()
                {
                    Uri uri = Request.RequestUri;
                    var uriQuery = HttpUtility.ParseQueryString(uri.Query);

                    string uriSiteId = uriQuery.Get("SiteId");
                    string uriFacilityTypeId = uriQuery.Get("FacilityTypeId");
                    string uriFormulaId = uriQuery.Get("FormulaId");
                    string uriTimeIntervalType = uriQuery.Get("TimeIntervalType");
                    string uriStartDate = uriQuery.Get("StartDate");
                    string uriEndDate = uriQuery.Get("EndDate");
                    string uriFacilityCode = uriQuery.Get("FacilityCode");

                    string uriServiceTypeId = uriQuery.Get("ServiceTypeId");

                    int siteId = -1, facilityTypeId = -1, formulaId = -1, timeIntervalType = -1, facilityCode = -1;
                    int serviceTypeId = -1;
                    DateTime startDate, endDate;

                    if (int.TryParse(uriSiteId, out siteId) == false ||
                        int.TryParse(uriFacilityTypeId, out facilityTypeId) == false ||
                        int.TryParse(uriFormulaId, out formulaId) == false ||
                        int.TryParse(uriTimeIntervalType, out timeIntervalType) == false ||
                        int.TryParse(uriFacilityCode, out facilityCode) == false ||

                        DateTime.TryParse(uriStartDate, out startDate) == false ||
                        DateTime.TryParse(uriEndDate, out endDate) == false
                        || int.TryParse(uriServiceTypeId, out serviceTypeId) == false)
                    {
                        throw new Exception(string.Format("Not Found Parameters: {0},{1},{2},{3},{4},{5},{6}, {7}",
                            uriSiteId, uriFacilityTypeId, facilityCode, uriFormulaId, uriTimeIntervalType, uriStartDate, uriEndDate, uriServiceTypeId));
                    }
                    TimeInterval timeInterval = (TimeInterval)timeIntervalType;

                    BemsFormula formula = GetFormula(siteId, facilityTypeId, facilityCode, formulaId);

                    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 = db.BemsFormulaParameter.Where(x =>
                            x.SiteId == siteId &&
                            x.FacilityTypeId == facilityTypeId &&
                            x.FacilityCode == facilityCode &&
                            x.FormulaId == formulaId);

                        if (queryParameters.Any() == false)
                        {
                            throw new Exception(string.Format("Not Found Parameters: {0},{1},{2},{3}", siteId, facilityTypeId, facilityCode, formulaId));
                        }

                        List<MathParameterValue> parametersForArray = new List<MathParameterValue>();
                        foreach (var p in queryParameters)
                        {
                            char parameter = p.ParameterId[0];
                            var pValue = new MathParameterValue
                            {
                                Variable = (MathCalculator.Parameter)(parameter - 'A'),
                                Value = 0,
                            };
                            parametersForArray.Add(pValue);

                            var values = pointHistoryValueManager.GetPointValues(siteId, p.ParameterFacilityCode,
                                p.ParameterPropertyId, timeInterval, startDate, endDate);

                            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) =>
                                {
                                    //Trace.WriteLine( typeof( x ) );

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

                    //Trace.WriteLine( calculator.Calculate() );
                    List<CalculationResult> list = new List<CalculationResult>();

                    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,
                                    Value = (double)value
                                });
                            }
                            if (parameterBox.NextStage() == false)
                            {
                                break;
                            }
                        }
                    }
                    else
                    {
                        var value = calculator.Calculate();
                        if (value != null)
                        {

                            list.Add(new CalculationResult
                            {

                                DateTime = dateTime,
                                Value = (double)value
                            });
                        }
                    }

                    return list;
                }
        */
        [ResponseType(typeof(void))]
        public IHttpActionResult PostBemsFormula(FormulaData formula)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var queryFormula = db.BemsFormula.Where(x =>
                x.SiteId == formula.SiteId &&
                x.FacilityTypeId == formula.FacilityTypeId &&
                x.FacilityCode == formula.FacilityCode &&
                x.FormulaId == formula.FormulaId);

            BemsFormula f = null;
            bool isNewFormula = false;
            if (queryFormula.Any())
            {
                f = queryFormula.First();
            }
            else
            {
                f = new BemsFormula();
                isNewFormula = true;
            }
            f.SiteId = formula.SiteId;
            f.FacilityTypeId = formula.FacilityTypeId;
            f.FacilityCode = formula.FacilityCode;
            f.FormulaId = formula.FormulaId;
            f.Formula = formula.Formula;

            if (isNewFormula)
            {
                db.BemsFormula.Add(f);
            }

            var parameterQuery = db.BemsFormulaParameter.Where(x =>
                x.SiteId == formula.SiteId &&
                x.FacilityTypeId == formula.FacilityTypeId &&
                x.FacilityCode == formula.FacilityCode &&
                x.FormulaId == formula.FormulaId);


            var qm = new QueryManager<BemsFormulaParameter>(db);
            try
            {
                qm.DeleteAndInsert(db.BemsFormulaParameter, formula.Paramters, parameterQuery);

                if (formula.Paramters.Count == 0)
                {
                    db.BemsFormula.Remove(queryFormula.FirstOrDefault());
                }

            }
            catch (DbEntityValidationException ex)
            {
                StringBuilder sb = new StringBuilder();

                foreach (var failure in ex.EntityValidationErrors)
                {
                    sb.AppendFormat("{0} failed validation\n", failure.Entry.Entity.GetType());
                    foreach (var error in failure.ValidationErrors)
                    {
                        sb.AppendFormat("- {0} : {1}", error.PropertyName, error.ErrorMessage);
                        sb.AppendLine();
                    }
                }
                return BadRequest(sb.ToString());
                //                throw ex;
            }
            catch (Exception) { throw; }

            db.SaveChanges();


            return StatusCode(HttpStatusCode.NoContent);
        }


        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }


        //private BemsFormula GetFormula( int siteId , int facilityTypeId ,
        public BemsFormula GetFormula(int siteId, int facilityTypeId,
            int facilityCode, int formulaId)
        {
            /*
            var queryFormula = db.BemsFormula.Where( x =>
                x.SiteId == siteId &&
                x.FacilityTypeId == facilityTypeId &&
                //x.FacilityCode == facilityCode) &&
                //(facilityTypeId >= 24 || x.FacilityCode == facilityCode) && // hcLee 2015 04 16
                (facilityTypeId >= 100 || x.FacilityCode == facilityCode) && // hcLee 2015 04  28
                x.FormulaId == formulaId );*/
            var queryFormula = from f in db.BemsFormula
                               where f.SiteId == siteId && f.FacilityTypeId == facilityTypeId &&
                                   (facilityTypeId >= 100 || f.FacilityCode == facilityCode) && f.FormulaId == formulaId
                               select f;
            if (queryFormula == null || queryFormula.Any() == false)
            {
                //throw new Exception(string.Format("Not Found FFFF Formula: {0},{1},{2},{3}", siteId, facilityTypeId, facilityCode, formulaId));
                //throw new Exception(string.Format("설비 {0}의 {1}번 성능분석 수식이 필요합니다!", facilityCode, formulaId));
                //throw new Exception(string.Format("성능분석 수식이 필요합니다!", facilityCode, formulaId));
                //return null;
                //throw new Exception("성능분석 수식이 필요합니다!");
                //throw new Exception("Make Fornula please");
                return null;
            }
            //if (queryFormula == null || queryFormula.Any() == false) return null;
            return queryFormula.Single();
        }

    }

    public class CalculationParameter
    {
        public MathParameterValue ParameterValue { get; set; }
        //public string FacilityCode { get; set; }
        //public int PropertyId { get; set; }
        public CalculationValue[] Values { get; set; }

        public DateTime SetValueFromIndex(int index)
        {
            var value = this.Values[index];

            this.ParameterValue.Value = (decimal)value.Value;
            return value.DateTime;
        }
    };

    public class FormulaData
    {
        public int SiteId { get; set; }
        public int FacilityTypeId { get; set; }
        public int FacilityCode { get; set; }
        public int FormulaId { get; set; }
        public string Formula { get; set; }
        public List<FormulaParameterData> Paramters { get; set; }
    };

    public class FormulaParameterData
    {
        public int SiteId { get; set; }
        public int FacilityTypeId { get; set; }
        public int FacilityCode { get; set; }
        public int FormulaId { get; set; }
        public string ParameterId { get; set; }

        public int ParameterFacilityCode { get; set; }
        public int ParameterPropertyId { get; set; }
    };


}