// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename       : DateHandlers.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Helpers.
//
// --[ Description ] ----------------------------------------------------------
//
//     Convert several timestamp formats to C# DateTime.
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2017 Natalia Portillo
// ****************************************************************************/

using System;
using DiscImageChef.Console;
using System.Text;

namespace DiscImageChef
{
    public static class DateHandlers
    {
        static readonly DateTime LisaEpoch = new DateTime(1901, 1, 1, 0, 0, 0);
        static readonly DateTime MacEpoch = new DateTime(1904, 1, 1, 0, 0, 0);
        static readonly DateTime UNIXEpoch = new DateTime(1970, 1, 1, 0, 0, 0);
        // Day 0 of Julian Date system
        static readonly DateTime JulianEpoch = new DateTime(1858, 11, 17, 0, 0, 0);
        static readonly DateTime AmigaEpoch = new DateTime(1978, 1, 1, 0, 0, 0);
        static readonly DateTime AppleDoubleEpoch = new DateTime(1970, 1, 1, 0, 0, 0);

        public static DateTime MacToDateTime(ulong MacTimeStamp)
        {
            return MacEpoch.AddTicks((long)(MacTimeStamp * 10000000));
        }

        public static DateTime LisaToDateTime(uint LisaTimeStamp)
        {
            return LisaEpoch.AddSeconds(LisaTimeStamp);
        }

        public static DateTime UNIXToDateTime(int UNIXTimeStamp)
        {
            return UNIXEpoch.AddSeconds(UNIXTimeStamp);
        }

        public static DateTime UNIXToDateTime(long UNIXTimeStamp)
        {
            return UNIXEpoch.AddSeconds(UNIXTimeStamp);
        }

        public static DateTime UNIXUnsignedToDateTime(uint UNIXTimeStamp)
        {
            return UNIXEpoch.AddSeconds(UNIXTimeStamp);
        }

        public static DateTime UNIXUnsignedToDateTime(uint seconds, uint nanoseconds)
        {
            return UNIXEpoch.AddSeconds(seconds).AddTicks((long)nanoseconds / 100);
        }

        public static DateTime UNIXUnsignedToDateTime(ulong UNIXTimeStamp)
        {
            return UNIXEpoch.AddSeconds(UNIXTimeStamp);
        }

        public static DateTime ISO9660ToDateTime(byte[] VDDateTime)
        {
            int year, month, day, hour, minute, second, hundredths;
            byte[] twocharvalue = new byte[2];
            byte[] fourcharvalue = new byte[4];

            fourcharvalue[0] = VDDateTime[0];
            fourcharvalue[1] = VDDateTime[1];
            fourcharvalue[2] = VDDateTime[2];
            fourcharvalue[3] = VDDateTime[3];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "year = \"{0}\"", StringHandlers.CToString(fourcharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(fourcharvalue, Encoding.ASCII), out year))
                year = 0;
            //			year = Convert.ToInt32(StringHandlers.CToString(fourcharvalue, Encoding.ASCII));

            twocharvalue[0] = VDDateTime[4];
            twocharvalue[1] = VDDateTime[5];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "month = \"{0}\"", StringHandlers.CToString(twocharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(twocharvalue, Encoding.ASCII), out month))
                month = 0;
            //			month = Convert.ToInt32(StringHandlers.CToString(twocharvalue, Encoding.ASCII));

            twocharvalue[0] = VDDateTime[6];
            twocharvalue[1] = VDDateTime[7];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "day = \"{0}\"", StringHandlers.CToString(twocharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(twocharvalue, Encoding.ASCII), out day))
                day = 0;
            //			day = Convert.ToInt32(StringHandlers.CToString(twocharvalue, Encoding.ASCII));

            twocharvalue[0] = VDDateTime[8];
            twocharvalue[1] = VDDateTime[9];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "hour = \"{0}\"", StringHandlers.CToString(twocharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(twocharvalue, Encoding.ASCII), out hour))
                hour = 0;
            //			hour = Convert.ToInt32(StringHandlers.CToString(twocharvalue, Encoding.ASCII));

            twocharvalue[0] = VDDateTime[10];
            twocharvalue[1] = VDDateTime[11];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "minute = \"{0}\"", StringHandlers.CToString(twocharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(twocharvalue, Encoding.ASCII), out minute))
                minute = 0;
            //			minute = Convert.ToInt32(StringHandlers.CToString(twocharvalue, Encoding.ASCII));

            twocharvalue[0] = VDDateTime[12];
            twocharvalue[1] = VDDateTime[13];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "second = \"{0}\"", StringHandlers.CToString(twocharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(twocharvalue, Encoding.ASCII), out second))
                second = 0;
            //			second = Convert.ToInt32(StringHandlers.CToString(twocharvalue, Encoding.ASCII));

            twocharvalue[0] = VDDateTime[14];
            twocharvalue[1] = VDDateTime[15];
            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "hundredths = \"{0}\"", StringHandlers.CToString(twocharvalue, Encoding.ASCII));
            if(!int.TryParse(StringHandlers.CToString(twocharvalue, Encoding.ASCII), out hundredths))
                hundredths = 0;
            //			hundredths = Convert.ToInt32(StringHandlers.CToString(twocharvalue, Encoding.ASCII));

            DicConsole.DebugWriteLine("ISO9600ToDateTime handler", "decodedDT = new DateTime({0}, {1}, {2}, {3}, {4}, {5}, {6}, DateTimeKind.Unspecified);", year, month, day, hour, minute, second, hundredths * 10);
            DateTime decodedDT = new DateTime(year, month, day, hour, minute, second, hundredths * 10, DateTimeKind.Unspecified);

            return decodedDT;
        }

        // C# works in UTC, VMS on Julian Date, some displacement may occur on disks created outside UTC
        public static DateTime VMSToDateTime(ulong vmsDate)
        {
            double delta = vmsDate * 0.0001; // Tenths of microseconds to milliseconds, will lose some detail
            return JulianEpoch.AddMilliseconds(delta);
        }

        public static DateTime AmigaToDateTime(uint days, uint minutes, uint ticks)
        {
            DateTime temp = AmigaEpoch.AddDays(days);
            temp = temp.AddMinutes(minutes);
            return temp.AddMilliseconds(ticks * 20);
        }

        public static DateTime UCSDPascalToDateTime(short dateRecord)
        {
            int year = ((dateRecord & 0xFE00) >> 9) + 1900;
            int day = (dateRecord & 0x01F0) >> 4;
            int month = (dateRecord & 0x000F);

            DicConsole.DebugWriteLine("UCSDPascalToDateTime handler", "dateRecord = 0x{0:X4}, year = {1}, month = {2}, day = {3}", dateRecord, year, month, day);
            return new DateTime(year, month, day);
        }

        public static DateTime DOSToDateTime(ushort date, ushort time)
        {
            int year = ((date & 0xFE00) >> 9) + 1980;
            int month = (date & 0x1E0) >> 5;
            int day = date & 0x1F;
            int hour = (time & 0xF800) >> 11;
            int minute = (time & 0x7E0) >> 5;
            int second = (time & 0x1F) * 2;

            DicConsole.DebugWriteLine("DOSToDateTime handler", "date = 0x{0:X4}, year = {1}, month = {2}, day = {3}", date, year, month, day);
            DicConsole.DebugWriteLine("DOSToDateTime handler", "time = 0x{0:X4}, hour = {1}, minute = {2}, second = {3}", time, hour, minute, second);

            DateTime dosdate;
            try
            {
                dosdate = new DateTime(year, month, day, hour, minute, second);
            }
            catch(ArgumentOutOfRangeException)
            {
                dosdate = new DateTime(1980, 1, 1, 0, 0, 0);
            }

            return dosdate;
        }

        public static DateTime CPMToDateTime(byte[] timestamp)
        {
            ushort days = BitConverter.ToUInt16(timestamp, 0);
            int hours = timestamp[2];
            int minutes = timestamp[3];

            DateTime temp = AmigaEpoch.AddDays(days);
            temp = temp.AddHours(hours);
            temp = temp.AddMinutes(minutes);

            return temp;
        }

        public static DateTime AppleDoubleToDateTime(ulong AppleDoubleTimeStamp)
        {
            return AppleDoubleEpoch.AddSeconds(AppleDoubleTimeStamp);
        }

        public static DateTime ECMAToDateTime(ushort typeAndTimeZone, short year, byte month, byte day, byte hour, byte minute, byte second, byte centiseconds, byte hundredsOfMicroseconds, byte microseconds)
        {
            byte specification = (byte)((typeAndTimeZone & 0xF000) >> 12);
            long ticks = (long)centiseconds * 100000 + (long)hundredsOfMicroseconds * 1000 + (long)microseconds * 10;
            if(specification == 0)
                return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc).AddTicks(ticks);

            ushort preOffset = (ushort)(typeAndTimeZone & 0xFFF);
            short offset;

            if((preOffset & 0x800) == 0x800)
            {
                offset = (short)(preOffset | 0xF000);
            }
            else
                offset = (short)(preOffset & 0x7FF);

            if(offset == -2047)
                return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified).AddTicks(ticks);
            
            if(offset < -1440 || offset > 1440)
                offset = 0;

            return new DateTimeOffset(year, month, day, hour, minute, second, new TimeSpan(0, offset, 0)).AddTicks(ticks).DateTime;
        }

        public static DateTime UNIXHrTimeToDateTime(ulong HRTimeStamp)
        {
            return UNIXEpoch.AddTicks((long)(HRTimeStamp / 100));
        }

        public static DateTime OS9ToDateTime(byte[] date)
        {
            if(date == null || (date.Length != 3 && date.Length != 5))
                return DateTime.MinValue;

            DateTime os9date;

            try
            {
                os9date = date.Length == 5 ? new DateTime(1900 + date[0], date[1], date[2], date[3], date[4], 0) : new DateTime(1900 + date[0], date[1], date[2], 0, 0, 0);
            }
            catch(ArgumentOutOfRangeException)
            {
                os9date = new DateTime(1900, 0, 0, 0, 0, 0);
            }

            return os9date;
        }
    }
}

