'use strict';

//@ Ox.getDate <f> Get the day of a date, optionally UTC
// see Ox.setSeconds for source code

/*@
Ox.getDateInWeek <f> Get the date that falls on a given weekday in the same week
    # Usage
    (date, weekday)      -> <d> Date
    (date, weekday, utc) -> <d> Date
    # Arguments
    date    <d> Date
    weekday <n|s> 1-7 (Monday-Sunday) or name, full ("Monday") or short ("Sun")
    utc     <b> If true, all dates are UTC
    # Examples
    > Ox.formatDate(Ox.getDateInWeek(new Date("January 1 2000"), "Sunday"), "%A, %B %e, %Y")
    "Sunday, January  2, 2000"
    > Ox.formatDate(Ox.getDateInWeek(new Date("Jan 1 2000"), "Fri"), "%A, %B %e, %Y")
    "Friday, December 31, 1999"
    > Ox.formatDate(Ox.getDateInWeek(new Date("1/1/2000"), 1), "%A, %B %e, %Y")
    "Monday, December 27, 1999"
@*/
// fixme: why is this Monday first? shouldn't it then be "getDateInISOWeek"??
Ox.getDateInWeek = function(date, weekday, utc) {
    date = Ox.makeDate(date);
    var sourceWeekday = Ox.getISODay(date, utc),
        targetWeekday = Ox.isNumber(weekday) ? weekday
            : Ox.indexOf(Ox.WEEKDAYS, function(v) {
                return v.slice(0, 3) == weekday.slice(0, 3);
            }) + 1;
    return Ox.setDate(date, Ox.getDate(date, utc) - sourceWeekday + targetWeekday, utc);
}

//@ Ox.getDay <f> Get the weekday of a date, optionally UTC
// see Ox.setSeconds for source code

/*@
Ox.getDayOfTheYear <f> Get the day of the year for a given date
    # Usage
    (date)      -> <d> Date
    (date, utc) -> <d> Date
    # Arguments
    date <d> Date
    utc  <b> If true, all dates are UTC
    # Examples
    > Ox.getDayOfTheYear(new Date("12/31/2000"))
    366
    > Ox.getDayOfTheYear(new Date("12/31/2002"))
    365
    > Ox.getDayOfTheYear(new Date("12/31/2004"))
    366
@*/
Ox.getDayOfTheYear = function(date, utc) {
    date = Ox.makeDate(date);
    var month = Ox.getMonth(date, utc),
        year = Ox.getFullYear(date, utc);
    return Ox.sum(Ox.range(month).map(function(i) {
        return Ox.getDaysInMonth(year, i + 1);
    })) + Ox.getDate(date, utc);
};

/*@
Ox.getDaysInMonth <f> Get the number of days in a given month
    > Ox.getDaysInMonth(2000, 2)
    29
    > Ox.getDaysInMonth("2002", "Feb")
    28
    > Ox.getDaysInMonth(new Date('01/01/2004'), "February")
    29
@*/
Ox.getDaysInMonth = function(year, month) {
    year = Ox.makeYear(year);
    month = Ox.isNumber(month) ? month
        : Ox.indexOf(Ox.MONTHS, function(v) {
            return v.slice(0, 3) == month.slice(0, 3);
        }) + 1;
    return new Date(year, month, 0).getDate();
}

/*@
Ox.getDaysInYear <f> Get the number of days in a given year
    > Ox.getDaysInYear(1900)
    365
    > Ox.getDaysInYear('2000')
    366
    > Ox.getDaysInYear(new Date('01/01/2004'))
    366
@*/
Ox.getDaysInYear = function(year, utc) {
    return 365 + Ox.isLeapYear(Ox.makeYear(year, utc));
};

/*@
Ox.getFirstDayOfTheYear <f> Get the weekday of the first day of a given year
    Returns the decimal weekday of January 1 (0-6, Sunday as first day)
    > Ox.getFirstDayOfTheYear(new Date('01/01/2000'))
    6
@*/
Ox.getFirstDayOfTheYear = function(date, utc) {
    date = Ox.makeDate(date);
    date = Ox.setMonth(date, 0, utc);
    date = Ox.setDate(date, 1, utc);
    return Ox.getDay(date, utc)
};

//@ Ox.getFullYear <f> Get the year of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.getHours <f> Get the hours of a date, optionally UTC
// see Ox.setSeconds for source code

/*@
Ox.getISODate <f> Get the ISO date string for a given date
    > Ox.getISODate(new Date('01/01/2000'))
    '2000-01-01T00:00:00Z'
@*/
Ox.getISODate = function(date, utc) {
    return Ox.formatDate(Ox.makeDate(date), '%FT%TZ', utc);
};

/*@
Ox.getISODay <f> Get the ISO weekday of a given date
    Returns the decimal weekday (1-7, Monday as first day)
    > Ox.getISODay(new Date('01/01/2000'))
    6
    > Ox.getISODay(new Date('01/02/2000'))
    7
    > Ox.getISODay(new Date('01/03/2000'))
    1
@*/
Ox.getISODay = function(date, utc) {
    return Ox.getDay(Ox.makeDate(date), utc) || 7;
};

/*@
Ox.getISOWeek <f> Get the ISO week of a given date
    See <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>
    > Ox.getISOWeek(new Date('01/01/2000'))
    52
    > Ox.getISOWeek(new Date('01/02/2000'))
    52
    > Ox.getISOWeek(new Date('01/03/2000'))
    1
@*/

Ox.getISOWeek = function(date, utc) {
    date = Ox.makeDate(date);
    // set date to Thursday of the same week
    return Math.floor((Ox.getDayOfTheYear(Ox.setDate(
        date, Ox.getDate(date, utc) - Ox.getISODay(date, utc) + 4, utc
    ), utc) - 1) / 7) + 1;
};

/*@
Ox.getISOYear <f> Get the ISO year of a given date
    See <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>
    > Ox.getISOYear(new Date("01/01/2000"))
    1999
    > Ox.getISOYear(new Date("01/02/2000"))
    1999
    > Ox.getISOYear(new Date("01/03/2000"))
    2000
@*/

Ox.getISOYear = function(date, utc) {
    date = Ox.makeDate(date);
    // set date to Thursday of the same week
    return Ox.getFullYear(Ox.setDate(
        date, Ox.getDate(date, utc) - Ox.getISODay(date, utc) + 4, utc
    ));
};

//@ Ox.getMilliseconds <f> Get the milliseconds of a date
// see Ox.setSeconds for source code
//@ Ox.getMinutes <f> Get the minutes of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.getMonth <f> Get the month of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.getSeconds <f> Get the seconds of a date
// see Ox.setSeconds for source code

//@ Ox.getTime <f> Alias for `+new Date()`
Ox.getTime = function(utc) {
    return +new Date() - (utc ? Ox.getTimezoneOffset() : 0);
};

/*@
Ox.getTimezoneOffset <f> Get the local time zone offset in milliseconds
    ([date]) -> <n> Offset in milliseconds
    date <d|u> Return offset at this date (if undefined, return current offset)
@*/
Ox.getTimezoneOffset = function(date) {
    return Ox.makeDate(date).getTimezoneOffset() * 60000;
};

/*@
Ox.getTimezoneOffsetString <f> Get the local time zone offset as a string
    Returns a time zone offset string (from around '-1200' to around '+1200').
    ([date]) -> <s> Offset as a string
    date <d|u> Return offset at this date (if undefined, return current offset)
    > Ox.getTimezoneOffsetString(new Date('01/01/2000')).length
    5
@*/
Ox.getTimezoneOffsetString = function(date) {
    var offset = Ox.makeDate(date).getTimezoneOffset();
    return (offset <= 0 ? '+' : '-')
        + Ox.pad(Math.floor(Math.abs(offset) / 60), 2)
        + Ox.pad(Math.abs(offset) % 60, 2);
};

/*@
Ox.getWeek <f> Get the week of a given day
    Returns the week of the year (0-53, Sunday as first day)
    > Ox.getWeek(new Date('01/01/2000'))
    0
    > Ox.getWeek(new Date('01/02/2000'))
    1
    > Ox.getWeek(new Date('01/03/2000'))
    1
@*/
Ox.getWeek = function(date, utc) {
    date = Ox.makeDate(date);
    return Math.floor((Ox.getDayOfTheYear(date, utc)
        + Ox.getFirstDayOfTheYear(date, utc) - 1) / 7);
};

/*@
Ox.isLeapYear <f> Returns true if a given year is a leap year
    > Ox.isLeapYear(1900)
    false
    > Ox.isLeapYear('2000')
    true
    > Ox.isLeapYear(new Date('01/01/2004'))
    true
@*/
Ox.isLeapYear = function(year, utc) {
    year = Ox.makeYear(year, utc);
    return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
};

/*@
Ox.makeDate <f> Takes a date, number or string, returns a date
    > Ox.formatDate(Ox.makeDate(0), '%m/%d/%Y', true)
    '01/01/1970'
    > Ox.formatDate(Ox.makeDate('01/01/1970'), '%m/%d/%Y')
    '01/01/1970'
    > Ox.formatDate(Ox.makeDate(new Date('01/01/1970')), '%m/%d/%Y')
    '01/01/1970'
@*/
Ox.makeDate = function(date) {
    // if date is a date, new Date(date) makes a clone
    return Ox.isUndefined(date) ? new Date() : new Date(date);
};

/*@
Ox.makeYear <f> Takes a date, number or string, returns a year
    > Ox.makeYear(new Date('01/01/1970'))
    1970
    > Ox.makeYear(1970)
    1970
    > Ox.makeYear('1970')
    1970
@*/
Ox.makeYear = function(date, utc) {
    return Ox.isDate(date) ? Ox.getFullYear(date, utc) : parseInt(date, 10);
};

/*@
Ox.parseDate <f> Takes a string ('YYYY-MM-DD HH:MM:SS.MMM') and returns a date
    string <s> String
    utc <b|false> If true, date is UTC
    > +Ox.parseDate('1970-01-01 01:01:01.01', true)
    3661010
    > +Ox.parseDate('1970', true)
    0
    > Ox.parseDate('50', true).getUTCFullYear()
    50
@*/
Ox.parseDate = function(string, utc) {
    var date,
        defaults = [, 1, 1, 0, 0, 0, 0],
        values = /(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?\.?(\d+)?/
            .exec(string);
    if (values) {
        values.shift();
        date = new Date();
        values = values.map(function(v, i) {
            return v ? (i == 6 ? Ox.pad(v, 3, '0') : v) : defaults[i];
        });
        values[1]--;
        [
            'FullYear', 'Month', 'Date',
            'Hours', 'Minutes', 'Seconds', 'Milliseconds'
        ].forEach(function(part, i) {
            Ox['set' + part](date, values[i], utc);
        });
    } else {
        date = null;
    }
    return date;
};

/*
Ox.parseDateRange = function(start, end, utc) {
    var dates = [
            Ox.parseDate(start, utc),
            Ox.parseDate(end, utc)
        ],
        part = [
            'FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'
        ][
            Ox.compact(
                /(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/.exec(end)
            ).length - 2
        ];
    Ox['set' + part](dates[1], Ox['get' + part](dates[1], utc) + 1, utc);
    return dates;
};
*/

//@ Ox.setDate <f> Set the day of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setDay <f> Set the weekday of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setFullYear <f> Set the year of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setHours <f> Set the hours of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setMilliseconds <f> Set the milliseconds of a date
// see Ox.setSeconds for source code
//@ Ox.setMinutes <f> Set the minutes of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setMonth <f> Set the month of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setSeconds <f> Set the seconds of a date
[
    'FullYear', 'Month', 'Date', 'Day',
    'Hours', 'Minutes', 'Seconds', 'Milliseconds'
].forEach(function(part) {
    Ox['get' + part] = function(date, utc) {
        return Ox.makeDate(date)['get' + (utc ? 'UTC' : '') + part]();
    }
    // Ox.setPart(date) modifies date
    Ox['set' + part] = function(date, num, utc) {
        return (
            Ox.isDate(date) ? date : new Date(date)
        )['set' + (utc ? 'UTC' : '') + part](num);
    }
});
