/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://gre/modules/Preferences.jsm");
Components.utils.import("resource://calendar/modules/calViewUtils.jsm");

this.EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
cal.print = {
    /**
     * Returns a simple key in the format YYYY-MM-DD for use in the table of
     * dates to day boxes
     *
     * @param dt    The date to translate
     * @return      YYYY-MM-DD
     */
    getDateKey: function(date) {
        return date.year + "-" + date.month + "-" + date.day;
    },

    /**
     * Add category styles to the document's "sheet" element. This is needed
     * since the HTML created is serialized, so we can't dynamically set the
     * styles and can be changed if the print formatter decides to return a
     * DOM document instead.
     *
     * @param document      The document that contains <style id="sheet"/>.
     * @param categories    Array of categories to insert rules for.
     */
    insertCategoryRules: function(document, categories) {
        let sheet = document.getElementById("sheet");
        sheet.insertedCategoryRules = sheet.insertedCategoryRules || {};

        for (let category of categories) {
            let prefName = cal.formatStringForCSSRule(category);
            let color = Preferences.get("calendar.category.color." + prefName) || "transparent";
            if (!(prefName in sheet.insertedCategoryRules)) {
                sheet.insertedCategoryRules[prefName] = true;
                let ruleAdd = ' .category-color-box[categories~="' + prefName + '"] { ' +
                              " border: 2px solid " + color + "; }\n";
                sheet.textContent += ruleAdd;
            }
        }
    },

    /**
     * Add calendar styles to the document's "sheet" element. This is needed
     * since the HTML created is serialized, so we can't dynamically set the
     * styles and can be changed if the print formatter decides to return a
     * DOM document instead.
     *
     * @param document      The document that contains <style id="sheet"/>.
     * @param categories    The calendar to insert a rule for.
     */
    insertCalendarRules: function(document, calendar) {
        let sheet = document.getElementById("sheet");
        let color = calendar.getProperty("color") || "#A8C2E1";
        sheet.insertedCalendarRules = sheet.insertedCalendarRules || {};

        if (!(calendar.id in sheet.insertedCalendarRules)) {
            sheet.insertedCalendarRules[calendar.id] = true;
            let formattedId = cal.formatStringForCSSRule(calendar.id);
            let ruleAdd = ' .calendar-color-box[calendar-id="' + formattedId + '"] { ' +
                          " background-color: " + color + "; " +
                          " color: " + cal.getContrastingTextColor(color) + "; }\n";
            sheet.textContent += ruleAdd;
        }
    },

    /**
     * Serializes the given item by setting marked nodes to the item's content.
     * Has some expectations about the DOM document (in CSS-selector-speak), all
     * following nodes MUST exist.
     *
     * - #item-template will be cloned and filled, and modified:
     *   - .item-interval gets the time interval of the item.
     *   - .item-title gets the item title
     *   - .category-color-box gets a 2px solid border in category color
     *   - .calendar-color-box gets background color of the calendar
     *
     * @param document          The DOM Document to set things on
     * @param item              The item to serialize
     * @param dayContainer      The DOM Node to insert the container in
     */
    addItemToDaybox: function(document, item, boxDate, dayContainer) {
        // Clone our template
        let itemNode = document.getElementById("item-template").cloneNode(true);
        itemNode.removeAttribute("id");
        itemNode.item = item;

        // Fill in details of the item
        let itemInterval = cal.print.getItemIntervalString(item, boxDate);
        itemNode.querySelector(".item-interval").textContent = itemInterval;
        itemNode.querySelector(".item-title").textContent = item.title;

        // Fill in category details
        let categoriesArray = item.getCategories({});
        if (categoriesArray.length > 0) {
            let cssClassesArray = categoriesArray.map(cal.formatStringForCSSRule);
            itemNode.querySelector(".category-color-box")
                    .setAttribute("categories", cssClassesArray.join(" "));

            cal.print.insertCategoryRules(document, categoriesArray);
        }

        // Fill in calendar color
        itemNode.querySelector(".calendar-color-box")
                .setAttribute("calendar-id", cal.formatStringForCSSRule(item.calendar.id));
        cal.print.insertCalendarRules(document, item.calendar);

        // Add it to the day container in the right order
        cal.binaryInsertNode(dayContainer, itemNode, item, cal.view.compareItems);
    },

    /**
     * Serializes the given item by setting marked nodes to the item's
     * content. Should be used for tasks with no start and due date. Has
     * some expectations about the DOM document (in CSS-selector-speak),
     * all following nodes MUST exist.
     *
     * - Nodes will be added to #task-container.
     * - #task-list-box will have the "hidden" attribute removed.
     * - #task-template will be cloned and filled, and modified:
     *   - .task-checkbox gets the "checked" attribute set, if completed
     *   - .task-title gets the item title.
     *
     * @param document          The DOM Document to set things on
     * @param item              The item to serialize
     */
    addItemToDayboxNodate: function(document, item) {
        let taskContainer = document.getElementById("task-container");
        let taskNode = document.getElementById("task-template").cloneNode(true);
        taskNode.removeAttribute("id");
        taskNode.item = item;

        let taskListBox = document.getElementById("tasks-list-box");
        if (taskListBox.hasAttribute("hidden")) {
            let tasksTitle = document.getElementById("tasks-title");
            taskListBox.removeAttribute("hidden");
            tasksTitle.textContent = cal.calGetString("calendar", "tasksWithNoDueDate");
        }

        // Fill in details of the task
        if (item.isCompleted) {
            taskNode.querySelector(".task-checkbox").setAttribute("checked", "checked");
        }

        taskNode.querySelector(".task-title").textContent = item.title;

        let collator = cal.createLocaleCollator();
        cal.binaryInsertNode(taskContainer, taskNode, item, (a, b) => collator.compareString(0, a, b), node => node.item.title);
    },

    /**
     * Get time interval string for the given item. Returns an empty string for all-day items.
     *
     * @param aItem     The item providing the interval
     * @return          The string describing the interval
     */
    getItemIntervalString: function(aItem, aBoxDate) {
        // omit time label for all-day items
        let startDate = aItem[cal.calGetStartDateProp(aItem)];
        let endDate = aItem[cal.calGetEndDateProp(aItem)];
        if ((startDate && startDate.isDate) || (endDate && endDate.isDate)) {
            return "";
        }

        // check for tasks without start and/or due date
        if (!startDate || !endDate) {
            return cal.getDateFormatter().formatItemTimeInterval(aItem);
        }

        let dateFormatter = cal.getDateFormatter();
        let defaultTimezone = cal.calendarDefaultTimezone();
        startDate = startDate.getInTimezone(defaultTimezone);
        endDate = endDate.getInTimezone(defaultTimezone);
        let start = startDate.clone();
        let end = endDate.clone();
        start.isDate = true;
        end.isDate = true;
        if (start.compare(end) == 0) {
            // Events that start and end in the same day.
            return dateFormatter.formatTimeInterval(startDate, endDate);
        } else {
            // Events that span two or more days.
            let compareStart = aBoxDate.compare(start);
            let compareEnd = aBoxDate.compare(end);
            if (compareStart == 0) {
                return "\u21e4 " + dateFormatter.formatTime(startDate); // unicode '⇤'
            } else if (compareStart > 0 && compareEnd < 0) {
                return "\u21ff";                                        // unicode '↔'
            } else if (compareEnd == 0) {
                return "\u21e5 " + dateFormatter.formatTime(endDate);   // unicode '⇥'
            } else {
                return "";
            }
        }
    }
};
