package calendar;

use strict;
use warnings;
no warnings 'redefine';

use Data::Dumper;
use Date::Calc();

use template();
use events();

our @EXPORT_OK = qw(init get_cached_or_render get render get_calendar_weeks configure_cache);

sub init() {
}

sub get_cached_or_render($$$) {
    #   my $output  = $_[0]
    my $config  = $_[1];
    my $request = $_[2];

    my $parms = $request->{params}->{checked};
    my $calendar = calendar::get($config, $request);
    calendar::render($_[0], $config, $request, $calendar);
}

sub get($$) {
    my ($config, $request) = @_;

    my $params = $request->{params}->{checked};
    my $language = $config->{date}->{language} || 'en';

    my $date      = $params->{date}      || '';
    my $template  = $params->{template}  || '';
    my $from_time = $params->{from_time} || '';
    my $till_time = $params->{till_time} || '';

    my @today = localtime(time());
    my $today = sprintf('%04d-%02d-%02d', 1900 + $today[5], $today[4] + 1, $today[3]);

    my $weekday_names       = time::getWeekdayNames($language);
    my $weekday_short_names = time::getWeekdayNamesShort($language);
    my $week_label          = {};
    my $c                   = 0;
    for my $weekday (@$weekday_short_names) {
        $week_label->{$weekday} = $weekday_names->[$c] || '';
        $c++;
    }

    $template =~ s/\'//gi;
    $from_time =~ s/\'//gi;
    $till_time =~ s/\'//gi;

    #put "clear all" filter into final results
    my $day_result   = {};
    my $clear_filter = $day_result;

    #put "week day" filter into final results
    my $days = [];
    $c = 0;
    for my $weekday (@$weekday_short_names) {
        my $day_result = {
            label              => $week_label->{$weekday},
            weekday            => $c + 1,
            weekday_parameter  => 'weekday=' . $c,
            weekday_short_name => $weekday_short_names->[$c] || '',
            weekday_name       => $weekday_names->[$c] || '',
            description        => qq{alle $week_label->{$weekday}-Termine anzeigen},
        };
        push @$days, $day_result;
        $c++;
    }

    #weeks and days array
    my $weekAndDayResults = [];

    #weekday array
    my $weekdayResults = $days;

    #week array
    my $weekResults = [];

    #info hash by timedate
    my $dateInfo = {};

    #generate content for each day in a week in a month in a year
    #get today
    my $start_date = '';
    my $end_date   = '';
    if ($date =~ /(\d{4})\-(\d{2})/) {
        my $year  = $1;
        my $month = $2;
        $start_date = "$year-$month-01";
        $end_date = "$year-$month-" . Date::Calc::Days_in_Month($year, $month);
    } else {
        $start_date = $params->{start_date};
        $end_date   = $params->{end_date};
    }

    my $previous_month = $start_date;
    if ($previous_month =~ /(\d{4})\-(\d{2})/) {
        my $year  = $1;
        my $month = $2 - 1;
        $month = '0' . $month if (length($month) < 2);
        if ($month lt '01') {
            $year -= 1;
            $month = '12';
        }
        $previous_month = "$year-$month-01";
        $previous_month = $params->{start_date} if ($previous_month lt $params->{start_date});
    }

    my $next_month = $end_date;
    if ($next_month =~ /(\d{4})\-(\d{2})/) {
        my $year  = $1;
        my $month = $2 + 1;
        $month = '0' . $month if (length($month) < 2);
        if ($month gt '12') {
            $year += 1;
            $month = '01';
        }
        $next_month = "$year-$month-01";
        $next_month = $params->{end_date} if ($next_month gt $params->{end_date});
    }

    my $start_year  = undef;
    my $start_month = undef;
    if ($start_date =~ /(\d{4})\-(\d{2})/) {
        $start_year  = $1;
        $start_month = $2;
    }
    my $monthNames       = time::getMonthNamesShort($language);
    my $start_month_name = $monthNames->[ $start_month - 1 ];

    if ($params->{month_only} eq '1') {
        return {
            next_month       => $next_month,
            previous_month   => $previous_month,
            start_year       => $start_year,
            start_month      => $start_month,
            start_month_name => $start_month_name
        };
    }

    my $years = calendar::get_calendar_weeks($config, $start_date, $end_date);

    my $dbh = db::connect($config, $request);

    my $used_days = events::get_by_date_range(
        $dbh, $config,
        $start_date,
        $end_date,
        {
            exclude_projects  => 1,
            exclude_locations => 1,
        }
    );
    my $used_day = { map { $_->{start_date} => 1 } @$used_days };

    for my $year (sort { $a <=> $b } keys %$years) {
        my $months = $years->{$year};

        for my $month (sort { $a <=> $b } keys %$months) {
            my $weeks = $months->{$month};

            my $weekCounter = 1;
            for my $week (@$weeks) {
                my $dayResults = [];

                my $week_end   = undef;
                my $week_start = undef;

                my $week_of_year = undef;
                my $woy_year     = undef;

                for my $date (@$week) {
                    my ($year, $month, $day) = split(/\-/, $date, 3);
                    my $weekday    = 0;
                    my $day_result = undef;

                    ($week_of_year, $woy_year) = Date::Calc::Week_of_Year($year, $month, $day)
                      unless defined $week_of_year;

                    $day_result = {
                        date           => $date,
                        date_parameter => 'date=' . $date,
                        day            => $day,
                        year           => $year,
                        month          => $month,
                    };
                    $day_result->{time} = $from_time if defined $from_time;

                    $day_result->{class} .= ' calcms_today' if $date eq $today;
                    $day_result->{class} .= ' selected'     if defined $used_day->{$date};
                    $day_result->{class} .= " week_$weekCounter";
                    $day_result->{class} .= " other_month" if ($weekCounter < 2) && ($day gt "15");
                    $day_result->{class} .= " other_month" if ($weekCounter > 3) && ($day lt "15");
                    $day_result->{class} =~ s/^\s+//g;

                    $week_start = $day unless defined $week_start;
                    $week_end = $day;

                    $day_result->{weekday_name}       = $weekday_names->[$weekday];
                    $day_result->{weekday_short_name} = $weekday_short_names->[$weekday];
                    $day_result->{weekday}            = $weekday + 1;

                    $dateInfo->{ $day_result->{date} } = $day_result->{weekday} if defined $day_result->{date};

                    push @$dayResults, $day_result;
                    $weekday++;

                }    #end for days

                #week filter
                my $start_date = $week->[0];
                my $end_date   = $week->[-1];

                my $week_result = {
                    from_date    => $start_date,
                    till_date    => $end_date,
                    week_start   => $week_start,
                    week_end     => $week_end,
                    week_month   => sprintf("%2d", $month),
                    week_year    => $year,
                    week_of_year => $week_of_year,
                };

                $week_result->{class} .= ' selected'
                  if ((defined $params->{from_date}) && ($start_date eq $params->{from_date}))
                  || ((defined $params->{till_date}) && ($end_date eq $params->{till_date}));
                $week_result->{class} .= " week_$weekCounter";
                $week_result->{class} =~ s/^\s+//g;

                push @$weekResults, $week_result;

                push @$weekAndDayResults,
                  {
                    days => $dayResults,
                    week => [$week_result]
                  };
                $weekCounter++;

            }    #end week

        }    #end month

    }    #end year

    for my $weekday (@$weekdayResults) {
        $weekday->{start_date} = $start_date;
        $weekday->{end_date}   = $end_date;
    }

    return {
        week_and_days    => $weekAndDayResults,
        weekdays         => $weekdayResults,
        weeks            => $weekResults,
        days             => $dateInfo,
        next_month       => $next_month,
        previous_month   => $previous_month,
        start_date       => $start_date,
        end_date         => $end_date,
        start_month_name => $start_month_name,
        start_month      => $start_month,
        start_year       => $start_year,
        base_url         => $config->{locations}->{base_url},
        widget_render_url=> $config->{locations}->{widget_render_url},
        controllers      => $config->{controllers},
    };

}

sub render($$$$) {

    #    my $out     = $_[0];
    my $config   = $_[1];
    my $request  = $_[2];
    my $calendar = $_[3];

    my $parms = $request->{params}->{checked};
    my $template_parameters = $calendar;
    $template_parameters->{base_url}         = $config->{locations}->{base_url};
    $template_parameters->{widget_render_url}= $config->{locations}->{widget_render_url};

    template::process($config, $_[0], $parms->{template}, $template_parameters);
}

sub get_calendar_weeks($$$) {
    my ($config, $start, $end) = @_;

    $start = time::date_to_array($start);
    $end   = time::date_to_array($end);

    my $start_year = int($start->[0]);
    my $end_year   = int($end->[0]);

    my $start_month = int($start->[1]);
    my $end_month   = int($end->[1]);

    my $years = {};
    for my $year ($start_year .. $end_year) {
        my $months = {};
        for my $month ($start_month .. $end_month) {

            #get week arrays of days of the month
            my $weeks = getWeeksOfMonth($year, $month);
            $months->{$month} = $weeks;
        }
        $years->{$year} = $months;
    }
    return $years;
}

sub getWeeksOfMonth($$) {
    my ($thisYear, $thisMonth) = @_;

    my $thisDay   = 1;

    # get weekday of 1st of month
    my $thisMonthWeekday = Date::Calc::Day_of_Week($thisYear, $thisMonth, 1);

    # get next month date
    my ($nextYear, $nextMonth, $nextDay) = Date::Calc::Add_Delta_YM($thisYear, $thisMonth, $thisDay, 0, 1);

    # get weekday of 1st of next month
    my $nextMonthWeekday = Date::Calc::Day_of_Week($nextYear, $nextMonth, $nextDay);
    my ($lastYear, $lastMonth, $lastDayOfMonth) = Date::Calc::Add_Delta_Days($nextYear, $nextMonth, $nextDay, -1);

    # get date of 1st of row
    my ($week, $year) = Date::Calc::Week_of_Year($thisYear, $thisMonth, $thisDay);
    ($year, my $month, my $day) = Date::Calc::Monday_of_Week($week, $year);

    my @weeks   = ();
    my $weekday = 1;

    {
        # first week
        my @days = ();
        for $weekday (0 .. $thisMonthWeekday - 2) {
            push @days, sprintf("%04d-%02d-%02d", $year, $month, $day);
            $day++;
        }

        # set current month
        $month = $thisMonth;
        $year  = $thisYear;
        $day   = 1;
        for $weekday ($thisMonthWeekday .. 7) {
            push @days, sprintf("%04d-%02d-%02d", $year, $month, $day);
            $day++;
        }

        # next week
        push @weeks, \@days;
    }

    # weeks until end of month
    while (scalar(@weeks) < 6) {
        my @days = ();
        $weekday = 1;
        while ($weekday <= 7) {
            push @days, sprintf("%04d-%02d-%02d", $year, $month, $day);
            $day++;
            $weekday++;
            last if $day > $lastDayOfMonth;
        }

        if ($day > $lastDayOfMonth) {

            # set next month
            $month = $nextMonth;
            $year  = $nextYear;
            $day   = 1;

            if ($nextMonthWeekday != 1) {

                # finish end week
                if ($weekday <= 7) {
                    while ($weekday <= 7) {
                        push @days, sprintf("%04d-%02d-%02d", $year, $month, $day);
                        $day++;
                        $weekday++;
                    }
                }
            }
            push @weeks, \@days;
            last;
        }
        push @weeks, \@days if $weeks[-1]->[-1] ne $days[-1];
    }

    #coming weeks
    while (scalar(@weeks) < 6) {
        my @days = ();
        for $weekday (1 .. 7) {
            push @days, sprintf("%04d-%02d-%02d", $year, $month, $day);
            $day++;
        }
        push @weeks, \@days;
    }
    return \@weeks;
}

sub check_params($$) {
    my ($config, $params) = @_;

    #get start and stop from projects
    my $range      = project::get_date_range($config);
    my $start_date = $range->{start_date};
    my $end_date   = $range->{end_date};

    #switch off limiting end date by project
    my $open_end = 0;
    if ((defined $params->{'open_end'}) && ($params->{'open_end'} =~ /(\d+)/)) {
        $open_end = $1;
        $end_date = time::add_days_to_datetime(time::time_to_datetime(), 365);
    }

    my $month_only = $params->{month_only} || '';

    #filter for date
    my $date = time::check_date($params->{date});

    $date = $start_date if ($date lt $start_date);
    $date = $end_date   if ($date gt $end_date);
    log::error($config, "no valid year-month format given!") if ($date eq "-1");

    my $time = time::check_time($params->{time});
    log::error($config, "no valid time format given!") if ($time eq "-1");

    my $from_date = time::check_date($params->{from_date}) || '';
    log::error($config, "no valid date format given!") if (defined $from_date && $from_date eq "-1");
    $from_date = $start_date if ($from_date lt $start_date);
    $from_date = $end_date   if ($from_date gt $end_date);

    my $till_date = time::check_date($params->{till_date} || '');
    log::error($config, "no valid date format given!") if (defined $till_date && $till_date eq "-1");
    $till_date = $start_date if ($till_date lt $start_date);
    $till_date = $end_date   if ($till_date gt $end_date);

    my $template = template::check($config, $params->{template}, 'calendar.html');

    return {
        template   => $template,
        date       => $date,
        from_date  => $from_date,
        till_date  => $till_date,
        month_only => $month_only,
        open_end   => $open_end,
        start_date => $start_date,
        end_date   => $end_date
    };
}

#do not delete last line!
1;

