using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.Utilities;
using System.IO;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;

namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
    class TimeZoneManager
    {
        private bool                 _isInitialized;
        private TimeZoneRule         _myRules;
        private string               _deviceLocationName;
        private UInt128              _timeZoneRuleVersion;
        private uint                 _totalLocationNameCount;
        private SteadyClockTimePoint _timeZoneUpdateTimePoint;
        private object               _lock;

        public TimeZoneManager()
        {
            _isInitialized       = false;
            _deviceLocationName  = "UTC";
            _timeZoneRuleVersion = new UInt128();
            _lock                = new object();

            // Empty rules
            _myRules = new TimeZoneRule
            {
                Ats   = new long[TzMaxTimes],
                Types = new byte[TzMaxTimes],
                Ttis  = new TimeTypeInfo[TzMaxTypes],
                Chars = new char[TzCharsArraySize]
            };

            _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
        }

        public bool IsInitialized()
        {
            bool res;

            lock (_lock)
            {
                res = _isInitialized;
            }

            return res;
        }

        public void MarkInitialized()
        {
            lock (_lock)
            {
                _isInitialized = true;
            }
        }

        public ResultCode GetDeviceLocationName(out string deviceLocationName)
        {
            ResultCode result = ResultCode.UninitializedClock;

            deviceLocationName = null;

            lock (_lock)
            {
                if (_isInitialized)
                {
                    deviceLocationName = _deviceLocationName;
                    result             = ResultCode.Success;
                }
            }

            return result;
        }

        public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream)
        {
            ResultCode result = ResultCode.TimeZoneConversionFailed;

            lock (_lock)
            {
                bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream);

                if (timeZoneConversionSuccess)
                {
                    _deviceLocationName = locationName;
                    _myRules            = rules;
                    result              = ResultCode.Success;
                }
            }

            return result;
        }

        public void SetTotalLocationNameCount(uint totalLocationNameCount)
        {
            lock (_lock)
            {
                _totalLocationNameCount = totalLocationNameCount;
            }
        }

        public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount)
        {
            ResultCode result = ResultCode.UninitializedClock;

            totalLocationNameCount = 0;

            lock (_lock)
            {
                if (_isInitialized)
                {
                    totalLocationNameCount = _totalLocationNameCount;
                    result                 = ResultCode.Success;
                }
            }

            return result;
        }

        public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false)
        {
            ResultCode result = ResultCode.UninitializedClock;

            lock (_lock)
            {
                if (_isInitialized || bypassUninitialized)
                {
                    _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint;
                    result                   = ResultCode.Success;
                }
            }

            return result;
        }

        public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint)
        {
            ResultCode result;

            lock (_lock)
            {
                if (_isInitialized)
                {
                    timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint;
                    result                   = ResultCode.Success;
                }
                else
                {
                    timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom();
                    result                   = ResultCode.UninitializedClock;
                }
            }

            return result;
        }

        public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream)
        {
            ResultCode result = ResultCode.Success;

            lock (_lock)
            {
                bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream);

                if (!timeZoneConversionSuccess)
                {
                    result = ResultCode.TimeZoneConversionFailed;
                }
            }

            return result;
        }

        public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion)
        {
            lock (_lock)
            {
                _timeZoneRuleVersion = timeZoneRuleVersion;
            }
        }

        public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion)
        {
            ResultCode result;

            lock (_lock)
            {
                if (_isInitialized)
                {
                    timeZoneRuleVersion = _timeZoneRuleVersion;
                    result              = ResultCode.Success;
                }
                else
                {
                    timeZoneRuleVersion = new UInt128();
                    result              = ResultCode.UninitializedClock;
                }
            }

            return result;
        }

        public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
        {
            ResultCode result;

            lock (_lock)
            {
                if (_isInitialized)
                {
                    result = ToCalendarTime(_myRules, time, out calendar);
                }
                else
                {
                    calendar = new CalendarInfo();
                    result   = ResultCode.UninitializedClock;
                }
            }

            return result;
        }

        public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
        {
            ResultCode result;

            lock (_lock)
            {
                result = TimeZone.ToCalendarTime(rules, time, out calendar);
            }

            return result;
        }

        public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
        {
            ResultCode result;

            lock (_lock)
            {
                if (_isInitialized)
                {
                    result = ToPosixTime(_myRules, calendarTime, out posixTime);
                }
                else
                {
                    posixTime = 0;
                    result    = ResultCode.UninitializedClock;
                }
            }

            return result;
        }

        public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
        {
            ResultCode result;

            lock (_lock)
            {
                result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
            }

            return result;
        }
    }
}