// vim: et:ts=4:sw=4:sts=4:ft=javascript

/*@
Ox.ListCalendar <f> ListCalendar object
@*/

Ox.ListCalendar = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
        .defaults({
            addEvent: null,
            editPlace: null,
            events: [],
            height: 256,
            pageLength: 100,
            removePlace: null,
            selected: [],
            showControls: false,
            sort: [{key: 'name', operator: '+'}],
            width: 256
        })
        .options(options || {})
        .addClass('OxListCalendar')
        .css({
            width: self.options.width + 'px',
            height: self.options.height + 'px'
        });

    self.durationCache = {};

    Ox.Log('Calendar', 'EVENT[0]', self.options.events[0])

    self.columns = [
        {
            id: 'name',
            operator: '+',
            removable: false,
            title: 'Name',
            visible: true,
            width: 144
        },
        {
            editable: false,
            format: function(value) {
                return value.join('; ');
            },
            id: 'alternativeNames',
            operator: '+',
            title: 'Alternative Names',
            visible: true,
            width: 144
        },
        {
            format: function(value) {
                return Ox.toTitleCase(value);
            },
            id: 'type',
            operator: '+',
            title: 'Type',
            visible: true,
            width: 64
        },
        {
            id: 'start',
            map: function(value) {
                return Ox.parseDate(value);
            },
            operator: '-',
            title: 'Start',
            visible: true,
            width: 144
        },
        {
            id: 'end',
            map: function(value) {
                return Ox.parseDate(value);
            },
            operator: '-',
            title: 'End',
            visible: true,
            width: 144
        },
        {
            format: function(value, data) {
                // return Ox.formatDateRangeDuration(data.start, data.end, true);
                var key = data.start + ' - ' + data.end;
                if (!self.durationCache[key]) {
                    self.durationCache[key] = Ox.formatDateRangeDuration(data.start, data.end, true);
                }
                return self.durationCache[key];
            },
            id: 'id',
            map: function(value, data) {
                return Ox.parseDate(data.end) - Ox.parseDate(data.start);
            },
            operator: '-',
            title: 'Duration',
            unique: true,
            visible: true,
            width: 256
        },
        {
            id: 'user',
            operator: '+',
            title: 'User',
            visible: false,
            width: 96
        },
        {
            format: function(value) {
                return value.replace('T', ' ').replace('Z', '');
            },
            id: 'created',
            operator: '-',
            title: 'Date Created',
            visible: false,
            width: 128,
        },
        {
            format: function(value) {
                return value.replace('T', ' ').replace('Z', '');
            },
            id: 'modified',
            operator: '-',
            title: 'Date Modified',
            visible: false,
            width: 128,
        },
        {
            align: 'right',
            id: 'matches',
            operator: '-',
            title: 'Matches',
            visible: false,
            width: 64,
        }
    ];

    self.$listToolbar = Ox.Bar({
        size: 24
    });

    self.$findElement = Ox.FormElementGroup({
            elements: [
                self.$findSelect = Ox.Select({
                        items: [
                            {id: 'all', title: 'Find: All'},
                            {id: 'name', title: 'Find: Name'},
                            {id: 'alternativeNames', title: 'Find: Alternative Names'},
                        ],
                        overlap: 'right',
                        type: 'image'
                    })
                    .bindEvent({
                        change: function(data) {
                            var key = data.selected[0].id,
                                value = self.$findInput.value();
                            value && updateList(key, value);
                        }
                    }),
                self.$findInput = Ox.Input({
                        clear: true,
                        placeholder: 'Find in List',
                        width: 234
                    })
                    .bindEvent({
                        submit: function(data) {
                            var key = self.$findSelect.value(),
                                value = data.value;
                            updateList(key, value);
                        }
                    })
            ]
        })
        .css({float: 'right', margin: '4px'})
        .bindEvent({
            change: function(data) {

            }
        })
        .appendTo(self.$listToolbar);

    self.$list = Ox.TextList({
            columns: self.columns,
            columnsRemovable: true,
            columnsVisible: true,
            // we have to clone so that when self.options.events changes,
            // self.$list.options({items: self.options.events}) still
            // registers as a change
            items: Ox.clone(self.options.events, true),
            pageLength: self.options.pageLength,
            scrollbarVisible: true,
            sort: self.options.sort
        })
        .bindEvent({
            'delete': removeEvent,
            init: initList, // fixme: won't fire from static list
            key_0: function() {
                self.$calendar.panToEvent();
            },
            key_equal: function() {
                self.$calendar.zoom(1);
            },
            key_minus: function() {
                self.$calendar.zoom(-1);
            },
            key_shift_0: function() {
                self.$calendar.zoomToPlace();
            },
            load: function() {
                that.triggerEvent('loadlist');
            },
            open: openItem,
            select: selectItem
        });

    self.$listStatusbar = Ox.Bar({
        size: 16
    });

    self.$status = Ox.Element()
        .css({paddingTop: '2px', margin: 'auto', fontSize: '9px', textAlign: 'center'})
        .html(
            Ox.formatNumber(self.options.events.length) + ' Event' + (
                self.options.events.length == 1 ? '' : 's'
            )
        )
        .appendTo(self.$listStatusbar);

    self.$calendar = Ox.Calendar({
            date: new Date(0),
            events: Ox.clone(self.options.events, true),
            height: self.options.height,
            showControls: self.options.showControls,
            width: self.options.width - 514,
            zoom: 4
        })
        .bindEvent({
            resize: function(data) {
                // triggered by SplitPanel
                $element.resizeCalendar();
            },
            select: selectEvent
        });

    self.$eventTitlebar = Ox.Bar({
        size: 24
    });
    self.$eventTitle = $('<div>')
        .hide()
        .appendTo(self.$eventTitlebar);
    self.$eventName = Ox.Label({
            title: '',
            width: 228
        })
        .css({float: 'left', margin: '4px'})
        .appendTo(self.$eventTitle)
        .bindEvent({
            singleclick: function() {
                self.$calendar.panToEvent();
            },
            doubleclick: function() {
                self.$calendar.zoomToEvent();
            }
        });
    self.$deselectEventButton = Ox.Button({
            title: 'close',
            tooltip: 'Done',
            type: 'image'
        })
        .css({float: 'left', margin: '4px 4px 4px 0'})
        .bindEvent({
            click: function() {
                self.$calendar.options({selected: null});
            }
        })
        .appendTo(self.$eventTitle);

    self.$eventForm = Ox.Form({
            items: [
                self.$nameInput = Ox.Input({
                    id: 'name',
                    label: 'Name',
                    labelWidth: 64,
                    width: 240
                }),
                self.$alternativeNamesInput = Ox.ArrayInput({
                    id: 'alternativeNames',
                    label: 'Alternative Names',
                    max: 10,
                    values: [],
                    width: 240
                }),
                Ox.Select({
                    id: 'type',
                    items: [
                        {id: 'date', title: 'Date'},
                        {id: 'place', title: 'Place'},
                        {id: 'person', title: 'Person'},
                        {id: 'other', title: 'Other'}
                    ],
                    label: 'Type',
                    labelWidth: 64,
                    width: 240
                }),
                self.$startInput = Ox.Input({
                    id: 'start',
                    label: 'Start',
                    labelWidth: 64,
                    width: 240
                }),
                self.$endInput = Ox.Input({
                    id: 'end',
                    label: 'End',
                    labelWidth: 64,
                    width: 240
                }),
                self.$durationInput = Ox.Input({
                    disabled: true,
                    id: 'durationText',
                    label: 'Duration',
                    labelWidth: 64,
                    width: 240
                })
            ],
            width: 240
        })
        .css({margin: '8px'})
        .hide()
        .bindEvent({
            change: function(data) {
                Ox.Log('Calendar', 'CHANGE', data);
                var exists = false, values
                if (['name', 'alternativeNames'].indexOf(data.id) > -1) {
                    exists = '';
                    values = data.id == 'name' ? [data.data.value] : data.data.value;
                    Ox.forEach(self.options.events, function(event) {
                        Ox.forEach(values, function(value) {
                            if (
                                event.name == data.data.value
                                || event.alternativeNames.indexOf(data.data.value) > -1
                            ) {
                                exists = value;
                                return false;
                            }
                        });
                        return !exists;
                    });
                }
                if (data.id == 'name') {
                    if (!exists) {
                        // FIXME: can we change this to data.value?
                        editEvent('name', data.data.value);
                    } else {
                        self.$nameInput.addClass('OxError');
                    }
                } else if (data.id == 'alternativeNames') {
                    if (!exists) {
                        editEvent('alternativeNames', data.data.value);
                    } else {
                        self.$alternativeNamesInput.setErrors([exists]);
                    }
                } else if (data.id == 'type') {
                    editEvent('type', data.data.selected[0].id);
                } else if (data.id == 'start') {
                    editEvent('start', data.data.value);
                } else if (data.id == 'end') {
                    editEvent('end', data.data.value);
                }
            }
        });

    self.$eventStatusbar = Ox.Bar({
        size: 24
    });

    self.$newEventButton = Ox.Button({
            title: 'New Event',
            width: 96
        })
        .css({float: 'left', margin: '4px 2px 4px 4px'})
        .bindEvent({
            click: addEvent
        })
        .appendTo(self.$eventStatusbar);

    self.$removeEventButton = Ox.Button({
            title: 'Remove Event',
            width: 96
        })
        .css({float: 'right', margin: '4px 4px 4px 2px'})
        .bindEvent({
            click: removeEvent
        })
        .hide()
        .appendTo(self.$eventStatusbar);

    that.$element = Ox.SplitPanel({
        elements: [
            {
                collapsible: true,
                element: Ox.SplitPanel({
                    elements: [
                        {
                            element: self.$listToolbar,
                            size: 24
                        },
                        {
                            element: self.$list
                        },
                        {
                            element: self.$listStatusbar,
                            size: 16
                        }
                    ],
                    orientation: 'vertical'
                }),
                resizable: true,
                resize: [256, 384, 512],
                size: 256
            },
            {
                element: self.$calendar,
            },
            {
                collapsible: true,
                element: Ox.SplitPanel({
                    elements: [
                        {
                            element: self.$eventTitlebar,
                            size: 24
                        },
                        {
                            element: self.$eventForm
                        },
                        {
                            element: self.$eventStatusbar,
                            size: 24
                        }
                    ],
                    orientation: 'vertical'
                })
                .bindEvent({
                    resize: function(data) {
                        self.$placeTitleName.options({width: data.size - 48});
                        // fixme: pass width through form
                        self.$placeFormItems.forEach(function($item) {
                            $item.options({width: data.size - 16});
                        });
                    }
                }),
                resizable: true,
                resize: [204, 256, 384],
                size: 256
            }
        ],
        orientation: 'horizontal'
    });

    function addEvent() {
        Ox.Log('Calendar', 'ADD', self.$calendar.getBounds())
        var bounds = self.$calendar.getBounds(),
            middle = +self.$calendar.options('date'),
            startTime = +new Date((+bounds.startTime + middle) / 2),
            endTime = +new Date((+bounds.endTime + middle) / 2),
            event = {},
            i = 1;
        event.name = 'Untitled';
        while (nameExists(event.name)) {
            event.name = 'Untitled [' + (++i) + ']';
        };
        event.alternativeNames = [];
        event.type = 'other';
        event.start = Ox.formatDate(startTime, '%Y-%m-%d %H:%M:%S', true);
        event.end = Ox.formatDate(endTime, '%Y-%m-%d %H:%M:%S', true);
        Ox.Log('Calendar', event);
        self.options.addEvent(event, function(result) {
            if (result.status.code == '200') {
                event.id = result.data.id;
                self.options.events.push(event);
                var time0 = +new Date()
                self.$list.options({items: Ox.clone(self.options.events, true)});
                Ox.Log('Calendar', 'TIME TO SET LIST OPTIONS:', +new Date() - time0);
                self.$calendar.addEvent(event);
                selectEvent(event);
                self.$nameInput.focusInput();
            } else {
                alert(result.status.text);
            }
        });
    }

    function editEvent(key, value) {
        var id = self.selectedEvent,
            index = Ox.getPositionById(self.options.events, id),
            data = {id: id};
        data[key] = value;
        self.options.editEvent(data, function(result) {
            if (result.status.code == 200) {
                self.options.events[index][key] = value;
                self.$list.value(id, key, value);
                if (['name', 'type', 'start', 'end'].indexOf(key) > -1) {
                    self.$calendar.editEvent(id, key, value);
                }
                if (key == 'name') {
                    self.$eventName.options({title: value});
                } else if (['start', 'end'].indexOf(key) > -1) {
                    self.$durationInput.options({
                        value: Ox.formatDateRangeDuration(
                            self.$startInput.options('value'),
                            self.$endInput.options('value')
                                || Ox.formatDate(new Date(), '%Y-%m-%d %H%:%M:%S'),
                            true
                        )
                    });
                }
            } else {
                alert(result.status.text);
            }
        });
    }

    function initList(data) {
        self.$status.html(
            Ox.formatNumber(data.items) + ' Event' + (
                data.items == 1 ? '' : 's'
            )
        );
    }

    function nameExists(name) {
        var exists = false;
        Ox.forEach(self.options.events, function(event) {
            if (
                event.name == name
                || event.alternativeNames.indexOf(name) > -1
            ) {
                exists = true;
                return false;
            }
        });
        return exists;
    }

    function openItem(data) {
        selectItem(data);
        self.$calendar.zoomToEvent(data.ids[0]);
    }

    function removeEvent() {
        var id = self.selectedEvent,
            index = Ox.getPositionById(self.options.events, id);
        self.options.removeEvent({id: id}, function(result) {
            if (result.status.code == '200') {
                self.options.events.splice(index, 1);
                var time0 = +new Date();
                self.$list.options({items: Ox.clone(self.options.events, true)});
                Ox.Log('Calendar', 'TIME TO SET LIST OPTIONS:', +new Date() - time0);
                self.$calendar.removeEvent();
                selectEvent({});
            } else {
                alert(result.status.text);
            }
        });
    }

    function selectEvent(event) {
        self.$list.options({
            selected: event.id ? [event.id] : []
        });
        if (event.id) {
            var end = event
            self.selectedEvent = event.id;
            self.$eventName.options({title: event.name});
            self.$eventTitle.show();
            self.$eventForm.values(Ox.extend({}, event, {
                end: event.current ? '' : event.end
            })).show();
            self.$removeEventButton.show();
        } else {
            self.selectedEvent = null;
            self.$eventTitle.hide();
            self.$eventForm.hide();
            self.$removeEventButton.hide();
        }
    }

    function selectItem(data) {
        var id = data.ids.length ? data.ids[0] : null;
        self.$calendar.options({selected: id});
        id && self.$calendar.panToEvent();
    }

    function updateList(key, value) {
        var events;
        if (value === '') {
            events = Ox.clone(self.options.events);
        } else {
            events = [];
            self.options.events.forEach(function(event) {
                if ((
                    ['all', 'name'].indexOf(key) > -1
                    && event.name.toLowerCase().indexOf(value) > -1
                ) || (
                    ['all', 'alternativeNames'].indexOf(key) > -1
                    && event.alternativeNames.join('\n').toLowerCase().indexOf(value) > -1
                )) {
                    events.push(event)
                }
            });
        }
        self.$list.options({items: events});
    }

    self.setOption = function(key, value) {
        if (key == 'height') {
            // fixme: should be .resizeList
            self.$list.size();
        } else if (key == 'width') {
            self.$calendar.resizeCalendar();
        }
    };

    return that;

};