package Sidef::Time::Date {

    use utf8;
    use 5.016;
    use parent qw(
      Sidef::Object::Object
    );

    require Time::Piece;

    use overload
      q{""}   => \&ctime,
      q{0+}   => sub { $_[0]->{time}->epoch },
      q{bool} => sub { $_[0]->{time} };

    use Sidef::Types::String::String;
    use Sidef::Types::Number::Number;

    sub new {
        my (undef, $sec) = @_;

        if (defined $sec) {
            $sec = CORE::int($sec) if ref($sec);
        }
        else {
            $sec = CORE::time;
        }

        bless {time => Time::Piece->new($sec),};
    }

    *call = \&new;

    sub get_value {
        $_[0]->{time} // Time::Piece->new(CORE::time);
    }

    {
        no strict 'refs';

        foreach my $name (qw(sec min hour mon year yy epoch wday mday yday isdst julian_day week month_last_day)) {
            *{__PACKAGE__ . '::' . $name} = sub {
                my ($self) = @_;
                Sidef::Types::Number::Number->new($self->{time}->$name);
            };
        }

        *day              = \&mday;
        *month            = \&mon;
        *minute           = \&min;
        *second           = \&sec;
        *month_day        = \&mday;
        *week_day         = \&wday;
        *year_day         = \&yday;
        *daylight_savings = \&isdst;

        foreach my $name (qw(monname fullmonth wdayname date)) {
            *{__PACKAGE__ . '::' . $name} = sub {
                my ($self) = @_;
                Sidef::Types::String::String->new($self->{time}->$name);
            };
        }

        foreach my $name (qw(ymd mdy dmy)) {
            *{__PACKAGE__ . '::' . $name} = sub {
                my ($self, $sep) = @_;
                Sidef::Types::String::String->new($self->{time}->$name(defined($sep) ? "$sep" : ()));
            };
        }

        foreach my $name ("year", "quarter", "month", "day", "hour", "minute", "second") {
            *{__PACKAGE__ . '::' . "truncate_to_" . $name} = sub {
                my ($self) = @_;
                bless {time => scalar $self->{time}->truncate(to => $name)};
            };
        }
    }

    sub today {
        __PACKAGE__->new(time);
    }

    *now = \&today;

    sub time {
        my ($self) = @_;
        Sidef::Time::Time->new(scalar $self->{time}->epoch);
    }

    sub localtime {
        my ($self, $sec) = @_;
        $sec //= $self->{time}->epoch;
        bless {time => scalar Time::Piece::localtime($sec)};
    }

    *local = \&localtime;

    sub gmtime {
        my ($self, $sec) = @_;
        $sec //= $self->{time}->epoch;
        bless {time => scalar Time::Piece::gmtime($sec)};
    }

    *gmt = \&gmtime;

    sub ctime {
        my ($self) = @_;
        Sidef::Types::String::String->new(scalar $self->{time}->cdate);
    }

    *to_s   = \&ctime;
    *to_str = \&ctime;
    *cdate  = \&ctime;

    sub strftime {
        my ($self, $format) = @_;
        Sidef::Types::String::String->new(scalar $self->{time}->strftime("$format"));
    }

    *format = \&strftime;

    sub strptime {
        my ($self, $string, $format) = @_;
        __PACKAGE__->new(Time::Piece->strptime("$string", "$format")->epoch);
    }

    *parse = \&strptime;

    sub valid_date {
        my ($self, $string, $format) = @_;

        $format = "$format";
        $string = "$string";

        eval { Time::Piece->strptime($string, $format)->strftime($format) eq $string }
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    *valid = \&valid_date;

    sub add_seconds {
        my ($self, $sec) = @_;
        bless {time => scalar $self->{time}->add(CORE::int($sec))};
    }

    *add = \&add_seconds;

    sub subtract {
        my ($this, $that) = @_;

        if (ref($that) eq __PACKAGE__) {
            return Sidef::Types::Number::Number->new(scalar $this->{time}->subtract($that->{time}));
        }

        bless {time => scalar $this->{time}->subtract(CORE::int($that))};
    }

    *sub = \&subtract;

    sub add_days {
        my ($self, $days) = @_;
        $self->add_seconds(86400 * CORE::int($days));
    }

    sub add_months {
        my ($self, $months) = @_;
        bless {time => scalar $self->{time}->add_months(CORE::int($months))};
    }

    sub add_years {
        my ($self, $years) = @_;
        bless {time => scalar $self->{time}->add_years(CORE::int($years))};
    }

    sub cmp {
        my ($this, $that) = @_;
        Sidef::Types::Number::Number->new(CORE::int($this) <=> CORE::int($that));
    }

    sub eq {
        my ($this, $that) = @_;
        (CORE::int($this) <=> CORE::int($that)) == 0
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    sub ne {
        my ($this, $that) = @_;
        (CORE::int($this) <=> CORE::int($that)) != 0
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    sub lt {
        my ($this, $that) = @_;
        (CORE::int($this) <=> CORE::int($that)) < 0
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    sub le {
        my ($this, $that) = @_;
        (CORE::int($this) <=> CORE::int($that)) <= 0
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    sub gt {
        my ($this, $that) = @_;
        (CORE::int($this) <=> CORE::int($that)) > 0
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    sub ge {
        my ($this, $that) = @_;
        (CORE::int($this) <=> CORE::int($that)) >= 0
          ? Sidef::Types::Bool::Bool::TRUE
          : Sidef::Types::Bool::Bool::FALSE;
    }

    sub dump {
        my ($self) = @_;
        Sidef::Types::String::String->new('Date(' . CORE::int($self) . ')');
    }

    {
        no strict 'refs';

        *{__PACKAGE__ . '::' . '+'}   = \&add;
        *{__PACKAGE__ . '::' . '-'}   = \&subtract;
        *{__PACKAGE__ . '::' . '<=>'} = \&cmp;
        *{__PACKAGE__ . '::' . '=='}  = \&eq;
        *{__PACKAGE__ . '::' . '!='}  = \&ne;
        *{__PACKAGE__ . '::' . '<'}   = \&lt;
        *{__PACKAGE__ . '::' . '>'}   = \&gt;
        *{__PACKAGE__ . '::' . '<='}  = \&le;
        *{__PACKAGE__ . '::' . '>='}  = \&ge;
    }
};

1;
