#include "date.h"

namespace xs { namespace date {

using panda::string;
using panda::string_view;

void hash2vals (const Hash& hash, ptime_t vals[8], TimezoneSP* zoneref) {
    auto end = hash.end();
    for (auto it = hash.begin(); it != end; ++it) {
        auto key = it->key();
        switch (key[0]) {
            case 'y':
                if (key == "year") vals[0] = xs::in<ptime_t>(it->value());
                break;
            case 'm':
                if      (key == "month") vals[1] = xs::in<ptime_t>(it->value());
                else if (key == "min")   vals[4] = xs::in<ptime_t>(it->value());
                else if (key == "mksec") vals[6] = xs::in<ptime_t>(it->value());
                break;
            case 'd':
                if (key == "day") vals[2] = xs::in<ptime_t>(it->value());
                break;
            case 'h':
                if (key == "hour") vals[3] = xs::in<ptime_t>(it->value());
                break;
            case 's':
                if (key == "sec") vals[5] = xs::in<ptime_t>(it->value());
                break;
            case 'i':
                if (key == "isdst") vals[7] = SvIV(it->value());
                break;
            case 't':
                if (key[1] == 'z' && zoneref && !*zoneref) *zoneref = tzget_required(it->value());
                break;
        }
    }
}

void array2vals (const Array& array, ptime_t vals[8]) {
    auto sz = array.size();
    if (sz > 8) sz = 8;
    for (size_t i = 0; i < sz; ++i) {
        auto val = array.fetch(i);
        if (val.defined()) vals[i] = xs::in<ptime_t>(val);
    }
}

Date sv2date (const Sv& arg, const TimezoneSP& zone, bool keep_object_zone, bool no_throw) {
    if (!arg.defined()) return Date(0, zone);

    if (arg.is_ref()) {
        auto sv = Ref(arg).value();
        if (sv.is_object()) return keep_object_zone ? *xs::in<Date*>(sv) : Date(*xs::in<Date*>(sv), zone);
        ptime_t vals[] = {2000, 1, 1, 0, 0, 0, 0, -1};
        TimezoneSP hzone = zone;

        if (sv.is_hash()) {
            Hash hash = sv.get<HV>();
            hash2vals(hash, vals, &hzone);
        }
        else if (sv.is_array()) {
            Array arr = sv.get<AV>();
            array2vals(arr, vals);
        }
        else if (no_throw) return invalid_date();
        else throw "bad date provided";

        return Date(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5], vals[6], vals[7], hzone);
    }

    if (arg.is_like_number()) {
        if (SvNOK(arg)) return Date((double)SvNV(arg), zone);
        return Date(xs::in<ptime_t>(arg), zone);
    }
    return Date(xs::in<string_view>(arg), zone);
}

DateRel sv2daterel (const Sv& arg, const Sv& arg2) {
    if (arg2) return DateRel(sv2date(arg), sv2date(arg2));
    if (!arg.defined()) return DateRel();

    if (arg.is_ref()) {
        auto sv = Ref(arg).value();
        if (sv.is_object()) return *xs::in<const DateRel*>(sv);

        ptime_t vals[] = {0, 0, 0, 0, 0, 0};

        if (sv.is_hash()) {
            Hash hash = sv.get<HV>();
            hash2vals(hash, vals, NULL);
        }
        else if (sv.is_array()) {
            Array arr = sv.get<AV>();
            array2vals(arr, vals);
        } else {
            throw "bad argument type";
        }

        return DateRel(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
    }

    if (arg.is_like_number()) return DateRel(0, 0, 0, 0, 0, xs::in<ptime_t>(arg));
    return DateRel(xs::in<string_view>(arg));
}

DateInt sv2dateint (const Sv& arg, const Sv& arg2) {
    if (arg2) return DateInt(sv2date(arg), sv2date(arg2));
    if (arg.is_ref()) {
        auto sv = Ref(arg).value();
        if (sv.is_object()) return *xs::in<DateInt*>(sv);
        if (sv.is_array()) {
            Array a = sv.get<AV>();
            Scalar from = a.fetch(0);
            Scalar till = a.fetch(1);
            if (from && till) return DateInt(sv2date(from), sv2date(till));
        }
    }
    else if (arg.is_string()) return DateInt(xs::in<string_view>(arg));
    throw "bad argument type";
}

}}
