using System;
using System.Collections.Generic;
using System.Linq;
using Siger.Middlelayer.Common;
using Siger.Middlelayer.Common.Extensions;
using Siger.Middlelayer.Common.Helpers;
using Siger.Middlelayer.Common.ModuleEnum;
using Siger.Middlelayer.Share.Models;

namespace Siger.Middlelayer.Share.Utilities
{
    public class RestTimeManager
    {
        private class RestExtractInfo
        {
            public int StartPoint { get; set; }

            public int EndPoint { get; set; }
        }

        private static List<RestExtractInfo> _restExtractInfos = new List<RestExtractInfo>();

        public static TimeType GetWeekofDay(int dateTime)
        {
            var dt = DateTime.Parse(UnixTimeHelper.ConvertIntDateTime(dateTime));
            var week = dt.DayOfWeek;
            switch (week)
            {
                case DayOfWeek.Monday:
                    return TimeType.Monday;
                case DayOfWeek.Tuesday:
                    return TimeType.Tuesday;
                case DayOfWeek.Wednesday:
                    return TimeType.Wednesday;
                case DayOfWeek.Thursday:
                    return TimeType.Thursday;
                case DayOfWeek.Friday:
                    return TimeType.Friday;
                case DayOfWeek.Saturday:
                    return TimeType.Saturday;
                case DayOfWeek.Sunday:
                    return TimeType.Sunday;
                default:
                    return TimeType.Holiday;
            }
        }

        public static TimeType GetWeekofDay(DateTime dateTime)
        {
            var week = dateTime.DayOfWeek;
            switch (week)
            {
                case DayOfWeek.Monday:
                    return TimeType.Monday;
                case DayOfWeek.Tuesday:
                    return TimeType.Tuesday;
                case DayOfWeek.Wednesday:
                    return TimeType.Wednesday;
                case DayOfWeek.Thursday:
                    return TimeType.Thursday;
                case DayOfWeek.Friday:
                    return TimeType.Friday;
                case DayOfWeek.Saturday:
                    return TimeType.Saturday;
                case DayOfWeek.Sunday:
                    return TimeType.Sunday;
                default:
                    return TimeType.Holiday;
            }
        }

        public static string GetDayofWeek(DateTime dateTime)
        {
            var week = dateTime.DayOfWeek;
            switch (week)
            {
                case DayOfWeek.Monday:
                    return "周一(Monday)";
                case DayOfWeek.Tuesday:
                    return "周二(Tuesday)";
                case DayOfWeek.Wednesday:
                    return "周三(Wednesday)";
                case DayOfWeek.Thursday:
                    return "周四(Thursday)";
                case DayOfWeek.Friday:
                    return "周五(Friday)";
                case DayOfWeek.Saturday:
                    return "周六(Saturday)";
                case DayOfWeek.Sunday:
                    return "周日(Sunday)";
                default:
                    return "";
            }
        }

        private static void MergedRestTimeInfo(List<RestExtractInfo> list, int num)
        {
            for (var i = 0; i < list.Count; i++)
            {
                if (i == num) continue;
                if (list[i] != null && list[num] != null)
                {
                    if (list[i].StartPoint >= list[num].StartPoint && list[i].StartPoint <= list[num].EndPoint)
                    {
                        if (list[i].EndPoint > list[num].EndPoint)
                        {
                            var extractTemp = new RestExtractInfo
                            {
                                StartPoint = list[num].StartPoint,
                                EndPoint = list[i].EndPoint
                            };
                            list[num] = extractTemp;
                            num = 0;
                            MergedRestTimeInfo(list, num);
                        }

                        list[i] = null;
                    }
                }
            }

            num++;
            if (num < list.Count)
            {
                MergedRestTimeInfo(list, num);
            }
            else
            {
                _restExtractInfos = list;
            }
        }

        public static IEnumerable<RestTimeInfo> GetMachineRestSeconds(IEnumerable<MachineRestInfo> times, DateTime day, DateTime dtEnd)
        {
            var timeType = GetWeekofDay(day);
            var weekTimes = times.Where(q => q.TimeType == timeType).OrderBy(q => q.BingTime).ToList();
            var holidays = times.Where(q => q.TimeType == TimeType.Holiday).OrderBy(q => q.BingTime).ToList();
            var dayStart = day;
            var dayEnd = dtEnd;

            var hilodayInfos = new List<RestTimeInfo>();
            foreach (var holiday in holidays)
            {
                var holidayStart = DateTime.Parse(holiday.BingTime);
                var holidayEnd = DateTime.Parse(holiday.EndTime);
                if (holidayEnd < dayStart || holidayStart > dayEnd)
                {
                    continue;
                }

                //四种包括关系
                if (holidayStart <= dayStart && holidayEnd >= dayEnd) //节假日全包含当天
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, dayStart, dayEnd));
                    break;
                }

                if (holidayStart <= dayStart && holidayEnd <= dayEnd)
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, dayStart, holidayEnd));
                }

                if (holidayStart >= dayStart && holidayEnd <= dayEnd)
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, holidayStart, holidayEnd));
                }

                if (holidayStart >= dayStart && holidayEnd >= dayEnd)
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, holidayStart, dayEnd));
                }
            }

            foreach (var weekTime in weekTimes)
            {
                var weekStart = DateTime.Parse(day.ToString(ParameterConstant.DateFormat) + " " + weekTime.BingTime);
                var weekEnd = DateTime.Parse(day.ToString(ParameterConstant.DateFormat) + " " + weekTime.EndTime);
                hilodayInfos.Add(new RestTimeInfo(dayStart, weekStart, weekEnd));
            }

            var list = new List<RestExtractInfo>();
            foreach (var info in hilodayInfos)
            {
                list.Add(new RestExtractInfo
                {
                    StartPoint = (int)UnixTimeHelper.ConvertDataTimeLong(info.StartTime),
                    EndPoint = (int)UnixTimeHelper.ConvertDataTimeLong(info.EndTime)
                });
            }

            list = list.OrderBy(q => q.StartPoint).ToList();
            MergedRestTimeInfo(list, 0);
            var result = new List<RestTimeInfo>();
            foreach (var info in _restExtractInfos)
            {
                if (info != null)
                {
                    result.Add(new RestTimeInfo
                    {
                        Date = dayStart,
                        StartTime = UnixTimeHelper.ConvertStringDateTime(info.StartPoint.ToString()),
                        EndTime = UnixTimeHelper.ConvertStringDateTime(info.EndPoint.ToString()),
                    });
                }
            }

            return result;
        }

        public static void GetStartTime(IEnumerable<MachineRestInfo> times, ref int dtStart)
        {
            var date = UnixTimeHelper.ConvertStringDateTime(dtStart.ToString());
            var restTimes = GetRestTimeInfoes(times, date);

            foreach (var weekTime in restTimes)
            {
                if (date >= weekTime.StartTime && date <= weekTime.EndTime)
                {
                    date = weekTime.EndTime;
                }
            }

            dtStart = (int)UnixTimeHelper.ConvertDataTimeLong(date);
        }

        public static int GetEndTime(IEnumerable<MachineRestInfo> times, int startTime, int needTimes)
        {
            var startDate = UnixTimeHelper.ConvertStringDateTime(startTime.ToString());
            var newEnd = ResetTime(times, startDate, needTimes);

            return (int)UnixTimeHelper.ConvertDataTimeLong(newEnd);
        }

        private static DateTime ResetTime(IEnumerable<MachineRestInfo> times, DateTime startTime, double needTimes)
        {
            DateTime endTime = startTime;
            double produceTimes = GetProduceTime(times, startTime);
            var restTimes = GetRestTimeInfoes(times, startTime).Where(q => q.EndTime > startTime).ToList();

            if (produceTimes >= needTimes) //当天可以结束
            {
                double totalProduce = 0;
                double previousProduce = 0;
                double previousRest = 0;
                if (!restTimes.Any())
                {
                    totalProduce = DateTime.Parse(startTime.AddDays(1).ToShortDateString()).Subtract(startTime).TotalSeconds;
                    if (totalProduce >= needTimes)
                    {
                        endTime = startTime.AddSeconds(needTimes);
                    }
                }
                else
                {
                    for (var i = 0; i < restTimes.Count; i++)
                    {
                        if (previousProduce > needTimes)
                        {
                            break;
                        }
                        var reseTime = restTimes[i];
                        double produce = 0;
                        if (i == 0)
                        {
                            produce = reseTime.StartTime.Subtract(startTime).TotalSeconds;
                        }
                        else
                        {
                            produce = reseTime.StartTime.Subtract(restTimes[i - 1].EndTime).TotalSeconds;
                        }

                        totalProduce += produce;
                        if (totalProduce - needTimes >= 0)
                        {
                            var left = needTimes - previousProduce;
                            endTime = startTime.AddSeconds(previousRest).AddSeconds(previousProduce).AddSeconds(left);
                        }

                        previousProduce += produce;
                        previousRest += reseTime.EndTime.Subtract(reseTime.StartTime).TotalSeconds;
                    }
                }
                if (totalProduce < needTimes)
                {
                    var lastRest = restTimes.LastOrDefault();
                    if (lastRest != null)
                    {
                        endTime = lastRest.EndTime.AddSeconds(needTimes - totalProduce);
                    }
                }
                return endTime;
            }

            var leftTimes = needTimes - produceTimes;
            if (leftTimes > 0)
            {
                return ResetTime(times, DateTime.Parse(startTime.AddDays(1).ToShortDateString()), leftTimes);
            }
            return endTime;
        }

        private static double GetProduceTime(IEnumerable<MachineRestInfo> times, DateTime startTime)
        {
            double rest = 0;
            var restTimes = GetRestTimeInfoes(times, startTime);
            foreach (var restTime in restTimes)
            {
                if (restTime.EndTime >= startTime && restTime.StartTime <= startTime)
                {
                    rest += restTime.EndTime.Subtract(startTime).TotalSeconds;
                }
                if (restTime.StartTime > startTime)
                {
                    rest += restTime.EndTime.Subtract(restTime.StartTime).TotalSeconds;
                }
            }

            return DateTime.Parse(startTime.AddDays(1).ToShortDateString()).Subtract(startTime).TotalSeconds - rest;
        }

        private static IEnumerable<RestTimeInfo> GetRestTimeInfoes(IEnumerable<MachineRestInfo> times, DateTime day)
        {
            var timeType = GetWeekofDay(day);
            var weekTimes = times.Where(q => q.TimeType == timeType).OrderBy(q => q.BingTime).ToList();
            var holidays = times.Where(q => q.TimeType == TimeType.Holiday).OrderBy(q => q.BingTime).ToList();
            var dayStart = day.Date;
            var dayEnd = dayStart.AddDays(1).AddSeconds(-1);

            var hilodayInfos = new List<RestTimeInfo>();
            foreach (var holiday in holidays)
            {
                var holidayStart = DateTime.Parse(holiday.BingTime);
                var holidayEnd = DateTime.Parse(holiday.EndTime);
                if (holidayEnd < dayStart || holidayStart > dayEnd)
                {
                    continue;
                }

                //四种包括关系
                if (holidayStart <= dayStart && holidayEnd >= dayEnd) //节假日全包含当天
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, dayStart, dayEnd));
                    break;
                }

                if (holidayStart <= dayStart && holidayEnd <= dayEnd)
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, dayStart, holidayEnd));
                }

                if (holidayStart >= dayStart && holidayEnd <= dayEnd)
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, holidayStart, holidayEnd));
                }

                if (holidayStart >= dayStart && holidayEnd >= dayEnd)
                {
                    hilodayInfos.Add(new RestTimeInfo(dayStart, holidayStart, dayEnd));
                }
            }

            foreach (var weekTime in weekTimes)
            {
                var weekStart = DateTime.Parse(day.ToString(ParameterConstant.DateFormat) + " " + weekTime.BingTime);
                var weekEnd = DateTime.Parse(day.ToString(ParameterConstant.DateFormat) + " " + weekTime.EndTime);
                hilodayInfos.Add(new RestTimeInfo(dayStart, weekStart, weekEnd));
            }

            var list = new List<RestExtractInfo>();
            foreach (var info in hilodayInfos)
            {
                list.Add(new RestExtractInfo
                {
                    StartPoint = (int)UnixTimeHelper.ConvertDataTimeLong(info.StartTime),
                    EndPoint = (int)UnixTimeHelper.ConvertDataTimeLong(info.EndTime)
                });
            }

            list = list.OrderBy(q => q.StartPoint).ToList();
            MergedRestTimeInfo(list, 0);
            var result = new List<RestTimeInfo>();
            foreach (var info in _restExtractInfos)
            {
                if (info != null)
                {
                    result.Add(new RestTimeInfo
                    {
                        Date = dayStart,
                        StartTime = UnixTimeHelper.ConvertStringDateTime(info.StartPoint.ToString()),
                        EndTime = UnixTimeHelper.ConvertStringDateTime(info.EndPoint.ToString()),
                    });
                }
            }

            return result;
        }

        public static double GetRestSeconds(IEnumerable<MachineRestInfo> times, DateTime dtStart, DateTime dtEnd)
        {
            var totalSeconds = dtEnd.Subtract(dtStart).TotalSeconds;
            foreach (var restInfo in times)
            {       
                if (restInfo.TimeType != TimeType.Holiday)
                {
                    var startTime = DateTime.Parse(dtStart.ToString(ParameterConstant.DateFormat) + " " + restInfo.BingTime);
                    var endTime = DateTime.Parse(dtEnd.ToString(ParameterConstant.DateFormat) + " " + restInfo.EndTime);
                    if (startTime >= dtEnd || endTime <= dtStart)
                    {
                        continue;
                    }
                }
                else
                {
                    DateTime start = DateTime.Parse(restInfo.BingTime);
                    DateTime end = DateTime.Parse(restInfo.EndTime);
                    if (start >= dtEnd || end <= dtStart)
                    {
                        continue;
                    }
                }

                if (restInfo.TimeType == TimeType.Holiday)
                {
                    DateTime start = DateTime.Parse(restInfo.BingTime);
                    DateTime end = DateTime.Parse(restInfo.EndTime);
                    if (start < dtStart)
                    {
                        start = dtStart;
                    }
                    if (end > dtEnd)
                    {
                        end = dtEnd;
                    }
                    totalSeconds -= end.Subtract(start).TotalSeconds;
                }
                else
                {
                    DateTime dtDay;
                    for (dtDay = dtStart; dtDay.CompareTo(dtEnd) <= 0; dtDay = dtDay.AddDays(1).Date)
                    {
                        if ((int)restInfo.TimeType == (int)dtDay.DayOfWeek)
                        {
                            var reststart = UnixTimeHelper.GetUnixAllTime(restInfo.BingTime);
                            var restend = UnixTimeHelper.GetUnixAllTime(restInfo.EndTime);
                            var start = UnixTimeHelper.GetUnixAllTime(dtStart.ToString(ParameterConstant.TimeFormat));
                            var end = UnixTimeHelper.GetUnixAllTime(dtEnd.ToString(ParameterConstant.TimeFormat));
                            if (restend > start && start > reststart && restend < end)
                            {
                                totalSeconds -= restend - start;
                            }
                            if (end > reststart && end < restend && reststart> start)
                            {
                                totalSeconds -= end - reststart;
                            }
                            if (start <= reststart && end >= restend)
                            {
                                totalSeconds -= restend - reststart;
                            }
                            if (reststart <= start && restend >= end)
                            {
                                totalSeconds -= end - start;
                            }
                        }                        
                    }
                }
            }

            return totalSeconds;
        }

        public static double GetNewRun(int machineId, DateTime dtStart, DateTime dtEnd, IEnumerable<MachineRestInfo> timeEntities)
        {
            var times = timeEntities.Where(q => q.MachineId == machineId);

            return GetRestSeconds(times, dtStart, dtEnd);
        }

        public static List<RestTimeInfo> GetRunTimeByPrograms(IEnumerable<MachineRestInfo> times, MachineProgramNo machineProgramNo, 
            DateTime stime, DateTime endtime, ref double runTimes)
        {
            double totalSeconds = 0;
            var rests = new List<RestTimeInfo>();
            var machineTimes = times.Where(q => q.MachineId == machineProgramNo.machineId);
            var restTimes = GetMachineRestSeconds(machineTimes, stime, endtime).ToList();
            foreach (var timeRange in machineProgramNo.TimeRanges)
            {
                totalSeconds += timeRange.EndTime.Subtract(timeRange.StartTime).TotalSeconds;
                foreach (var restTime in restTimes)
                {
                    if (timeRange.StartTime >= restTime.EndTime) continue;
                    if (timeRange.EndTime <= restTime.StartTime) continue;
                    if (restTime.StartTime <= timeRange.StartTime && restTime.EndTime <= timeRange.EndTime && restTime.EndTime >= timeRange.StartTime)
                    {
                        rests.Add(new RestTimeInfo(stime, timeRange.StartTime, restTime.EndTime));
                    }

                    else if (restTime.StartTime >= timeRange.StartTime && restTime.EndTime <= timeRange.EndTime)
                    {
                        rests.Add(new RestTimeInfo(stime, restTime.StartTime, restTime.EndTime));
                    }

                    else if (restTime.StartTime >= timeRange.StartTime && restTime.EndTime >= timeRange.EndTime && restTime.StartTime <= timeRange.EndTime)
                    {
                        rests.Add(new RestTimeInfo(stime, restTime.StartTime, timeRange.EndTime));
                    }

                    else if (restTime.StartTime <= timeRange.StartTime && restTime.EndTime >= timeRange.EndTime)
                    {
                        rests.Add(new RestTimeInfo(stime, timeRange.StartTime, timeRange.EndTime));
                    }
                }
            }

            var rest = rests.Distinct().Sum(restTime => restTime.EndTime.Subtract(restTime.StartTime).TotalSeconds);
            runTimes = totalSeconds - rest;
            return rests;
        }
    }
}
