using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace mame
{
    public struct Atime
    {
        public int seconds;
        public long attoseconds;
        public Atime(int i, long l)
        {
            seconds = i;
            attoseconds = l;
        }
    }
    public class Attotime
    {
        public static int ATTOTIME_MAX_SECONDS = 1000000000, ATTOSECONDS_PER_SECOND_SQRT = 1000000000;
        public static long ATTOSECONDS_PER_SECOND = (long)(1e18);
        public static Atime ATTOTIME_ZERO = new Atime(0, 0);
        public static Atime ATTOTIME_NEVER = new Atime(1000000000, 0);
        public static long ATTOSECONDS_PER_NANOSECOND=(long)1e9;
        public static Atime ATTOTIME_IN_NSEC(long ns)
        {
            return new Atime((int)(ns / 1000000000), (long)((ns % 1000000000) * ATTOSECONDS_PER_NANOSECOND));
        }
        public static Atime ATTOTIME_IN_HZ(int hz)
        {
            return new Atime(0, (long)(ATTOSECONDS_PER_SECOND / hz));
        }
        public static long attotime_to_attoseconds(Atime _time)
        {
            if (_time.seconds == 0)
            {
                return _time.attoseconds;
            }
            else if (_time.seconds == -1)
            {
                return _time.attoseconds - Attotime.ATTOSECONDS_PER_SECOND;
            }
            else if (_time.seconds > 0)
            {
                return Attotime.ATTOSECONDS_PER_SECOND;
            }
            else
            {
                return -Attotime.ATTOSECONDS_PER_SECOND;
            }
        }
        public static Atime attotime_add(Atime _time1, Atime _time2)
        {
            Atime result = new Atime();

            /* if one of the items is attotime_never, return attotime_never */
            if (_time1.seconds >= ATTOTIME_MAX_SECONDS || _time2.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;

            /* add the seconds and attoseconds */
            result.attoseconds = _time1.attoseconds + _time2.attoseconds;
            result.seconds = _time1.seconds + _time2.seconds;

            /* normalize and return */
            if (result.attoseconds >= ATTOSECONDS_PER_SECOND)
            {
                result.attoseconds -= ATTOSECONDS_PER_SECOND;
                result.seconds++;
            }

            /* overflow */
            if (result.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;
            return result;
        }
        public static Atime attotime_add_attoseconds(Atime _time1, long _attoseconds)
        {
            Atime result;

            /* if one of the items is attotime_never, return attotime_never */
            if (_time1.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;

            /* add the seconds and attoseconds */
            result.attoseconds = _time1.attoseconds + _attoseconds;
            result.seconds = _time1.seconds;

            /* normalize and return */
            if (result.attoseconds >= ATTOSECONDS_PER_SECOND)
            {
                result.attoseconds -= ATTOSECONDS_PER_SECOND;
                result.seconds++;
            }

            /* overflow */
            if (result.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;
            return result;
        }
        public static Atime attotime_sub(Atime _time1, Atime _time2)
        {
            Atime result;

            /* if time1 is attotime_never, return attotime_never */
            if (_time1.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;

            /* add the seconds and attoseconds */
            result.attoseconds = _time1.attoseconds - _time2.attoseconds;
            result.seconds = _time1.seconds - _time2.seconds;

            /* normalize and return */
            if (result.attoseconds < 0)
            {
                result.attoseconds += ATTOSECONDS_PER_SECOND;
                result.seconds--;
            }
            return result;
        }
        public static Atime attotime_sub_attoseconds(Atime _time1, long _attoseconds)
        {
            Atime result;

            /* if time1 is attotime_never, return attotime_never */
            if (_time1.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;

            /* add the seconds and attoseconds */
            result.attoseconds = _time1.attoseconds - _attoseconds;
            result.seconds = _time1.seconds;

            /* normalize and return */
            if (result.attoseconds < 0)
            {
                result.attoseconds += ATTOSECONDS_PER_SECOND;
                result.seconds--;
            }
            return result;
        }
        public static Atime attotime_mul(Atime _time1, uint factor)
        {
            uint attolo, attohi, reslo, reshi;
            ulong temp;

            /* if one of the items is attotime_never, return attotime_never */
            if (_time1.seconds >= ATTOTIME_MAX_SECONDS)
                return ATTOTIME_NEVER;

            /* 0 times anything is zero */
            if (factor == 0)
                return ATTOTIME_ZERO;

            /* split attoseconds into upper and lower halves which fit into 32 bits */
            attohi = divu_64x32_rem((ulong)_time1.attoseconds, 1000000000,out attolo);

            /* scale the lower half, then split into high/low parts */
            temp = mulu_32x32(attolo, factor);
            temp = divu_64x32_rem(temp, 1000000000, out reslo);

            /* scale the upper half, then split into high/low parts */
            temp += mulu_32x32(attohi, factor);
            temp = divu_64x32_rem(temp, 1000000000, out reshi);

            /* scale the seconds */
            temp += mulu_32x32((uint)_time1.seconds, factor);
            if (temp >= 1000000000)
                return ATTOTIME_NEVER;

            /* build the result */
            return new Atime((int)temp, (long)reslo + mul_32x32((int)reshi, 1000000000));
        }
        private static uint divu_64x32_rem(ulong a, uint b, out uint remainder)
        {
            remainder = (uint)(a % (ulong)b);
            return (uint)(a / (ulong)b);
        }
        private static ulong mulu_32x32(uint a, uint b)
        {
            return (ulong)a * (ulong)b;
        }
        private static long mul_32x32(int a, int b)
        {
            return (long)a * (long)b;
        }
        public static Atime attotime_div(Atime _time1, uint factor)
        {
            uint attolo, attohi, reshi, reslo, remainder;
            Atime result;
            ulong temp;

            /* if one of the items is attotime_never, return attotime_never */
            if (_time1.seconds >= ATTOTIME_MAX_SECONDS)
                return new Atime(ATTOTIME_MAX_SECONDS, 0);

            /* ignore divide by zero */
            if (factor == 0)
                return _time1;

            /* split attoseconds into upper and lower halves which fit into 32 bits */
            attohi = divu_64x32_rem((ulong)_time1.attoseconds, 1000000000, out attolo);

            /* divide the seconds and get the remainder */
            result.seconds = (int)divu_64x32_rem((ulong)_time1.seconds, factor,  out remainder);

            /* combine the upper half of attoseconds with the remainder and divide that */
            temp = (ulong)attohi + mulu_32x32(remainder, 1000000000);
            reshi = divu_64x32_rem(temp, factor, out remainder);

            /* combine the lower half of attoseconds with the remainder and divide that */
            temp = attolo + mulu_32x32(remainder, 1000000000);
            reslo = divu_64x32_rem(temp, factor, out remainder);

            /* round based on the remainder */
            result.attoseconds = (long)reslo + (long)mulu_32x32(reshi, 1000000000);
            if (remainder >= factor / 2)
                if (++result.attoseconds >= ATTOSECONDS_PER_SECOND)
                {
                    result.attoseconds = 0;
                    result.seconds++;
                }
            return result;
        }
        public static int attotime_compare(Atime _time1, Atime _time2)
        {
            if (_time1.seconds > _time2.seconds)
                return 1;
            if (_time1.seconds < _time2.seconds)
                return -1;
            if (_time1.attoseconds > _time2.attoseconds)
                return 1;
            if (_time1.attoseconds < _time2.attoseconds)
                return -1;
            return 0;
        }
    }
}