var unapproved = false;
var showAllDay = true;

function toggleUnapproved()
{
    unapproved = !unapproved;
    updateEvents(true);
}

function toggleAllDay()
{
    showAllDay = !showAllDay;
    updateEvents(true);
}

/* returns whether a time is within the currently displayed week. */
function withinDisplayedWeek(time)
{
    return displayed_week_start <= time && time < getRelWeek(1);
}

/* getRelDay() adds n days to displayed_day_start */
function getRelDay(n, start=displayed_week_start)
{
    var nd = new Date(start);
    nd.setHours(n*24);
    return nd;
}

/* getRelWeek() adds n weeks to displayed_week_start */
function getRelWeek(n, start=displayed_week_start)
{
    var new_start = new Date(start);
    new_start.setHours(n*24*7);
    return new_start;
}

function showNext()
{
    if (view == "day")
    {
        showNextDay();
    } else {
        showNextWeek();
    }
    setTimeout(redisplayEvents, 1000);
}

function showPrev()
{
    if (view == "day")
    {
        showPrevDay();
    } else {
        showPrevWeek();
    }
    setTimeout(redisplayEvents, 1000);
}

function showNextDay()
{
    /* If current day is Sunday, first switch week. */
    if (displayed_day_start.getDay() == 0)
    {
        showNextWeek();
        dayView(0);
    } else {
        displayed_day_start = getRelDay(1, displayed_day_start);
        dayView(mod(displayed_day_start.getDay() - 1, 7))
    }
}

/* showPrevDay() switches to the previous day in day view. */
function showPrevDay()
{
    /* If current day is Monday, first switch week. */
    if (displayed_day_start.getDay() == 1)
    {
        showPrevWeek();
        dayView(6);
    } else {
        displayed_day_start = getRelDay(-1, displayed_day_start);
        dayView(mod(displayed_day_start.getDay() - 1, 7))
    }
}

/* showNextWeek() changes the start of the current week
 * to one week later, then updates the events. */
function showNextWeek()
{
    displayed_week_start = getRelWeek(1);
    fixDates();
    updateEvents(true);
}

/* showPrevWeek() does the same, but backwards. */
function showPrevWeek()
{
    displayed_week_start = getRelWeek(-1);
    fixDates();
    updateEvents(true);
}

/* getCurrentWeek() returns 12am monday for the localtime week. */
function getCurrentWeek()
{
    var date = new Date();
    date.setHours(0);
    date.setMinutes(0);
    date.setSeconds(0);
    date.setMilliseconds(0);
    var day = date.getDay() || 7;
    if (day !== 1)
        date.setHours(-24 * (day - 1));
    return date;
}

/* Checks whether an event has one of a certain set of labels. */
function matchesLabel(event, labels)
{
    if (!labels.length)
    {
        return true;
    }

    for (let label of labels)
    {
        /* Shouldn't happen, but just in case. */
        if (event.label_ids == null)
        {
            return false;
        }

        if (event.label_ids.split(',').indexOf(label) != -1)
        {
            return true;
        }
    }
    return false;
}

/* onCalendar() checks whether an event (as a parsed JSON object)
 * should be visible. True is returned when the start_date is
 * at least displayed_week_start and at most
 * displayed_week_start + 1 week.
 *
 * */
function onCalendar(event)
{
    var date = new Date(event['start_date']);
    var end = new Date(event['end_date']);

    var sew = date < displayed_week_start && end < getRelWeek(1) && end > displayed_week_start;
    var swe = date >= displayed_week_start && end >= getRelWeek(1) && date < getRelWeek(1);
    var complete = date < displayed_week_start && end > getRelWeek(1);
    var inner = date >= displayed_week_start && end < getRelWeek(1);

    if ((sew || swe || complete || inner) &&
        matchesLabel(event, selected_labels))
    {
        return true;
    }
    return false;
}

function eventApproved(event)
{
    return event.status_id == 2;
}

function getMaximum(a, b)
{
    if (typeof a === "string")
    {
        a = new Date(a);
    }
    return new Date(Math.max(a, b));
}

function getMinimum(a, b)
{
    if (typeof a === "string")
    {
        a = new Date(a);
    }
    return new Date(Math.min(a, b));
}

function createMultidayEvent(event)
{
    var startDay = mod(getMaximum(event.start_date, displayed_week_start).getDay() - 1, 7);
    var endWeek = getRelWeek(1);
    endWeek.setHours(-24);
    var endDay = mod(getMinimum(event.end_date, endWeek).getDay() - 1, 7);

    var nDays = 1 + (endDay - startDay);

    var element = createAlldayEvent(event);
    var currentWidth = parseInt(element.style.width);
    element.style.width = nDays * (currentWidth + 10) - 10 + "px";
    element.style.left = startDay * (currentWidth + 10) + "px";
}

function createAlldayEvent(event)
{
    var classes = "ev-content";
    if (event.status_id == 1)
    {
        return;
    }

    if (!eventApproved(event))
    {
        if (event.user_id == getUserID() || unapproved)
        {
            classes = "ev-content unapproved";
        } else
        {
            return;
        }
    }

    $(".all-day").css('display', 'flex');

    var start_date = new Date(event['start_date']);
    var end_date = new Date(event['end_date']);
    var ev_element = document.createElement("div");
    ev_element.className = "event";
    var parent = $(".all-day-ev");

    // Added id here so I can reference for deletion on click.
    ev_element.id = event['event_id'];

    if (event['tclr'][0] == '1')
    {
        ev_element.style.color = "#fff";
    }

    ev_element.style["background-color"] = "#" + event['clr'].split(',')[0];

    var day = mod(start_date.getDay() - 1, 7);
    var row_width = $("#week .day-column .hour-row").width() + 1;

    ev_element.style.left = row_width * day + "px";
    ev_element.style.width = row_width - 10 + "px";

    parent.append(ev_element);

    var ev_title = document.createElement("span");

    ev_element.innerHTML = `<div class="${classes}"><span class='event-title bold'>${event['title']}</span></div>`;
    return ev_element;
}

/* createEvent() takes an event (as a parsed JSON object) and adds it to the
 * calendar. It does not check whether the event is on the calendar, for that
 * see onCalendar().
 */
function createEvent(event)
{
    var start_date = new Date(event['start_date']);
    var end_date = new Date(event['end_date']);

    var opacity = 1;
    if (event.status_id == 1)
    {
        return;
    }

    if (!eventApproved(event))
    {
        if (event.user_id == getUserID() || unapproved)
        {
            opacity = 0.5;
        } else
        {
            return;
        }
    }

    if (end_date.getDate() !== start_date.getDate())
    {
        if (showAllDay)
            createMultidayEvent(event);
        return;
    }

    if (event['all_day'] == '1')
    {
        if (showAllDay)
            createAlldayEvent(event);
        return;
    }

    var parent = $("#week .day-column")[mod(start_date.getDay() - 1, 7)];

    var hour_height = parent.querySelector('.hour-row').offsetHeight + 1;
    var minute_height = hour_height / 60;

    var ev_element = document.createElement("div");
    ev_element.className = "event";

    // Added id here so I can reference for deletion on click.
    ev_element.id = event['event_id'];

    ev_element.style.top = hour_height * start_date.getHours() + minute_height * start_date.getMinutes() + 1 + "px";

    var duration = (60 * (end_date.getHours() - start_date.getHours()) - start_date.getMinutes() + end_date.getMinutes());

    ev_element.style['height'] = minute_height * duration - 3 + "px";

    if (event['tclr'][0] == '1')
    {
        ev_element.style.color = "#fff";
    }

    ev_element.style["background-color"] = "#" + event['clr'].split(',')[0];

    parent.append(ev_element);

    var ev_title = document.createElement("span");

    var timedelta;
    if (duration < 60)
    {
        timedelta = `${start_date.getHours()}:${start_date.getMinutes().toString().padStart(2, '0')}`;
    } else {
        timedelta = `${start_date.getHours()}:${start_date.getMinutes().toString().padStart(2, '0')} - ${end_date.getHours()}:${end_date.getMinutes().toString().padStart(2, "0")}`;
    }
    var classes = (opacity < 1) ? "ev-content unapproved" : "ev-content";
    ev_element.innerHTML = `<div class="${classes}"><span class="timedelta">${timedelta} </span><span class='event-title'>${event['title']}</span></div>`;
}

/* removes all events from the page. */
function removeAllEvents()
{
    $(".event").remove();
}

var previous_hash = [];

function fixMobileAllDay()
{
    $(".all-day-ev .event").map((_, x) =>
    {
        if (new Date(findEventData(x.id).start_date).getDay()
            != displayed_day_start.getDay() &&
            new Date(findEventData(x.id).end_date).getDay()
            != displayed_day_start.getDay())
        {
            x.style.display = 'none';
        }
    });

    $(".all-day-ev")[0].style.width = 'calc(100% - 3px - ' + $('.hour-column').width() + "px" + ' - '
                                    + $('.fake-scrollbar').width() + 'px)';
}

function groupElements()
{
    var arr = [];
    var prev_shift = -1;
    var n = -1;

    for (let element of $('.all-day-ev .event'))
    {
        if (parseInt(element.style.left) == prev_shift)
        {
            arr[n].push(element);
        } else {
            n += 1;
            prev_shift = parseInt(element.style.left);
            arr.push([element]);
        }
    }

    return arr;
}

/* updateEvents() does the following:
 *
 *  1. Fetches a list of events from the server, potentially with a label
 *     id specified.
 *  2. Checks if those are different from the current ones.
 *  3. If so, removes the current ones and displays those using onCalendar()
 *     and createEvent().
 */
function updateEvents(clientChanged = false)
{
    if ($("#agenda").length === 0)
    {
        return;
    }
    $.get("https://rcendpoint.bami.party/calendar/robin", function(data)
    {
        events = $.parseJSON(data);
        // var hash = events.pop();

        // if (hash == previous_hash && !clientChanged)
        // {
        //     return;
        // } else {
        //     previous_hash = hash;
        // }

        redisplayEvents();

        /* May have added all day events, so need resize. */
        contentResize(false);
    });
}

function redisplayEvents()
{
    if ($("#agenda").length === 0)
    {
        return;
    }
    removeAllEvents();
    $(".all-day").css('display', 'none');
    for (let event of events)
    {
        // console.log(event);
        if (onCalendar(event))
        {
            // console.log("On calendar!");
            createEvent(event);
        }
    }
    shiftOverlappingElements();

    if (view == "day")
    {
        fixMobileAllDay();
    }
}

function elementOverlaps(a, b)
{
    var a_data = findEventData(a.id);
    var b_data = findEventData(b.id);

    /* We have the guarantee that a_data.start_date
     * is less than b_data.start_date. */
    a_start_date = new Date(a_data.start_date);
    a_end_date = new Date(a_data.end_date);

    b_start_date = new Date(b_data.start_date);
    b_end_date = new Date(b_data.end_date);
    return (a_end_date > b_start_date &&
            a_start_date < b_end_date);
}

function startDateSortReverse(a, b)
{
    return new Date(findEventData(a.id).start_date) -
           new Date(findEventData(b.id).start_date);
}

function shiftLeftAmount(x)
{
    return (1/(1+Math.E**((-(x/2))+Math.E)));
}

function shiftSingleElement(element, rest, chain = 0)
{
    hour_width = $(".day-column .hour-row").filter(
        function (_, x) {
            return x.offsetHeight != 0;
        })[0];

    if (hour_width)
    {
        hour_width = hour_width.offsetWidth - 10;
    }

    var i = 0;
    for (; i < rest.length; ++i)
    {
        second = rest[i];
        if (elementOverlaps(element, second))
        {
            i += shiftSingleElement(second, rest.slice(i+1), chain + 1);
        } else {
            break;
        }
    }
    if (chain != 0)
        element.style.left = shiftLeftAmount(chain) * hour_width + "px";
    return i;
}

function shiftOverlappingElements()
{
    var sorted_elements = $("#week .event");

    for(var j = 0; j < sorted_elements.length; ++j)
    {
        j += shiftSingleElement(sorted_elements[j], sorted_elements.slice(j+1));
    }
}

/* Find event data for an event ID. */
function findEventData(id)
{
    for (let event of events)
    {
        if (event.event_id == id)
        {
            return event;
        }
    }
    return null;
}
