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

'use strict';

/*@
Ox.ListMap <f:Ox.Element> ListMap object
    () ->              <f> ListMap object
    (options) ->       <f> ListMap object
    (options, self) -> <f> ListMap object
    options <o> Options object
        height <n|256> Height in px
        labels <b|false> If true, show labels
        places <a|f|null> Array of places, or function that returns places
        selected <a|[]> Selected places
        width <n|256> Width in px
    self <o> Shared private variable
@*/

Ox.ListMap = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
        .defaults({
            addPlace: null,
            editPlace: null,
            getMatches: null,
            height: 256,
            labels: false,
            pageLength: 100,
            places: null,
            removePlace: null,
            selected: [],
            showControls: false,
            showLabels: false,
            showTypes: false,
            sort: [{key: 'geoname', operator: '+'}],
            width: 256
        })
        .options(options || {})
        .addClass('OxListMap')
        .css({
            width: self.options.width + 'px',
            height: self.options.height + 'px'
        });

    self.isAsync = Ox.isFunction(self.options.places);

    self.columns = [
        {
            addable: false, // fixme: implement
            id: 'id',
            title: 'Id',
            unique: true,
            visible: false,
            width: 64
        },
        {
            format: function(value, data) {
                return $('<img>')
                    .attr({
                        src: Ox.getFlagByGeoname(data.geoname, 16)
                    })
                    .css({
                        width: '14px',
                        height: '14px',
                        borderRadius: '4px',
                        marginLeft: '-3px',
                        marginTop: 0
                    });
            },
            id: 'countryCode',
            resizable: false, // fixme: implement
            title: 'Flag',
            titleImage: 'flag',
            visible: true,
            width: 16
        },
        {
            id: 'name',
            operator: '+',
            removable: false,
            title: 'Name',
            visible: true,
            width: 144
        },
        {
            format: function(value) {
                return value.join('; ');
            },
            id: 'alternativeNames',
            operator: '+',
            title: 'Alternative Names',
            visible: true,
            width: 144
        },
        {
            id: 'geoname',
            map: function(value) {
                var names = value.split(', ');
                if (!Ox.getCountryByGeoname(names[names.length - 1])) {
                    names.push('~');
                }
                return names.reverse().join(', ');
            },
            operator: '+',
            title: 'Geoname',
            visible: true,
            width: 192
        },
        {
            format: function(value) {
                return Ox.toTitleCase(value);
            },
            id: 'type',
            operator: '+',
            title: 'Type',
            visible: true,
            width: 64
        },
        {
            align: 'right',
            format: toFixed,
            id: 'south',
            operator: '+',
            title: 'South',
            visible: false,
            width: 96
        },
        {
            align: 'right',
            id: 'west',
            operator: '+',
            title: 'West',
            visible: false,
            width: 96
        },
        {
            align: 'right',
            format: toFixed,
            id: 'north',
            operator: '+',
            title: 'North',
            visible: false,
            width: 96
        },
        {
            align: 'right',
            format: toFixed,
            id: 'east',
            operator: '+',
            title: 'East',
            visible: false,
            width: 96
        },
        {
            align: 'right',
            format: toFixed,
            id: 'lat',
            operator: '+',
            title: 'Latitude',
            visible: true,
            width: 96
        },
        {
            align: 'right',
            format: toFixed,
            id: 'lng',
            operator: '+',
            title: 'Longitude',
            visible: true,
            width: 96
        },
        {
            align: 'right',
            format: {type: 'area', args: []},
            id: 'area',
            operator: '-',
            title: 'Area',
            visible: true,
            width: 128
        },
        {
            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'},
                            {id: 'geoname', title: 'Find: Geoname'}
                        ],
                        overlap: 'right',
                        type: 'image'
                    })
                    .bindEvent({
                        change: function(data) {
                            var key = data.value,
                                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,
            //items: Ox.clone(self.options.places),
            items: self.options.places,
            pageLength: self.options.pageLength,
            scrollbarVisible: true,
            sort: self.options.sort
        })
        .bindEvent({
            'delete': removeItem,
            init: initList,
            // fixme: do we need 0/shift-0? return already zooms to place
            key_0: function() {
                self.$map.panToPlace();
            },
            key_equal: function() {
                self.$map.zoom(1);
            },
            key_minus: function() {
                self.$map.zoom(-1);
            },
            key_shift_0: function() {
                self.$map.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'})
        .appendTo(self.$listStatusbar);

    self.$map = Ox.Map({
        clickable: true,
        editable: true,
        findPlaceholder: 'Find on Map',
        height: self.options.height,
        places: self.options.places,
        //statusbar: true,
        showControls: self.options.showControls,
        showLabels: self.options.showLabels,
        showTypes: self.options.showTypes,
        toolbar: true,
        width: self.options.width - 514,//self.mapResize[1],
        zoombar: true
    })
    .bindEvent({
        /*
        addplace: function(data) {
            that.triggerEvent('addplace', data);
        },
        */
        changeplace: function(data) {
            self.$placeForm.values(data).show();
            self.$areaKmInput.value(Ox.formatArea(data.area));
        },
        changeplaceend: function(data) {
            //Ox.Log('Map', 'ssP', self.selectedPlace);
            var isResult = self.selectedPlace[0] == '_';
            !isResult && editPlace([
                'lat', 'lng', 'south', 'west', 'north', 'east', 'area'
            ]);
        },
        geocode: function(data) {
            that.triggerEvent('geocode', data);
        },
        /*
        resize: function() {
            self.$map.resizeMap(); // fixme: don't need event
        },
        */
        selectplace: selectPlace
    });

    self.$placeTitlebar = Ox.Bar({
        size: 24
    });
    self.$placeTitle = $('<div>')
        .hide()
        .appendTo(self.$placeTitlebar)
    self.$placeFlag = $('<img>')
        .addClass('OxFlag')
        .attr({
            src: Ox.getFlagByGeoname('', 16)
        })
        .css({float: 'left', margin: '4px'})
        .appendTo(self.$placeTitle);
    self.$placeName = Ox.Label({
            title: '',
            width: 208
        })
        .css({float: 'left', margin: '4px 0 4px 0'})
        .appendTo(self.$placeTitle)
        .bindEvent({
            singleclick: function() {
                self.$map.panToPlace();
            },
            doubleclick: function() {
                self.$map.zoomToPlace();
            }
        });
    self.$deselectPlaceButton = Ox.Button({
            title: 'close',
            tooltip: 'Done',
            type: 'image'
        })
        .css({float: 'left', margin: '4px'})
        .bindEvent({
            click: function() {
                self.$map.options({selected: null});
            }
        })
        .appendTo(self.$placeTitle);

    self.$nameInput = Ox.Input({
        id: 'name',
        label: 'Name',
        labelWidth: 64,
        width: 240
    }).bindEvent({
        change: function(data) {
            var isResult = self.selectedPlace[0] == '_';
            !isResult && self.$list.value(self.selectedPlace, 'name', data.value);
            if (!self.isAsync) {
                Ox.getObjectById(
                    self.options.places, self.selectedPlace
                ).name = data.value;
            } else {
                !isResult && editPlace(['name']);
            }
            self.$map.value(self.selectedPlace, 'name', data.value);
        }
    });

    self.$alternativeNamesInput = Ox.ArrayInput({
        id: 'alternativeNames',
        label: 'Alternative Names',
        max: 10,
        //sort: true,
        values: [],
        width: 240
    }).bindEvent({
        change: function(data) {
            var isResult = self.selectedPlace[0] == '_';
            if (!self.isAsync) {
                
            } else {
                !isResult && editPlace(['alternativeNames'])
            }
            self.$map.value(self.selectedPlace, 'alternativeNames', data.value);
        }
    });

    self.$geonameInput = Ox.Input({
        id: 'geoname',
        label: 'Geoname',
        labelWidth: 64,
        width: 240
    }).bindEvent({
        change: function(data) {
            var geoname = data.value,
                country = Ox.getCountryByGeoname(geoname),
                countryCode = country ? country.code : '',
                isResult = self.selectedPlace[0] == '_';
            self.$placeFlag.attr({
                src: Ox.getFlagByGeoname(geoname, 16)
            });
            self.$placeName.options({title: geoname});
            self.$placeForm.values({countryCode: countryCode});
            if (!self.isAsync) {
                if (!isResult) {
                    self.$list.value(self.selectedPlace, 'geoname', geoname);
                    self.$list.value(self.selectedPlace, 'countryCode', countryCode);
                }
            } else {
                !isResult && editPlace(['countryCode', 'geoname']);
            }
            self.$map.value(self.selectedPlace, 'countryCode', countryCode);
            self.$map.value(self.selectedPlace, 'geoname', geoname);
        }
    });

    // fixme: form should have a change event
    // fixme: it has one now, but inputs fire on blur
    self.$placeFormItems = Ox.merge([
        self.$nameInput,
        self.$alternativeNamesInput,
        self.$geonameInput,
        Ox.Input({
            id: 'countryCode'
        }).hide(),
        Ox.Select({
            id: 'type',
            items: [
                {id: 'country', title: 'Country'},
                {id: 'region', title: 'Region'}, // administative (Kansas) or colloquial (Midwest)
                {id: 'city', title: 'City'},
                {id: 'borough', title: 'Borough'},
                {id: 'street', title: 'Street'}, // streets, squares, bridges, tunnels, ...
                {id: 'building', title: 'Building'}, // airports, stations, stadiums, military installations, ...
                {id: 'feature', title: 'Feature'} // continents, islands, rivers, lakes, seas, oceans, mountains...
            ],
            label: 'Type',
            labelWidth: 64,
            width: 240
        }).bindEvent({
            change: function(data) {
                var isResult = self.selectedPlace[0] == '_';
                if (!self.isAsync) {
                    
                } else {
                    !isResult && editPlace(['type'])
                }
                self.$map.value(self.selectedPlace, 'type', data.value);
            }
        })
    ], ['Latitude', 'Longitude', 'South', 'West', 'North', 'East'].map(function(v) {
        var id = (
                v == 'Latitude' ? 'lat' : v == 'Longitude' ? 'lng' : v
            ).toLowerCase(),
            max = ['Latitude', 'South', 'North'].indexOf(v) > -1 ? Ox.MAX_LATITUDE : 180;
        return Ox.Input({
            decimals: 8,
            disabled: ['lat', 'lng'].indexOf(id) > -1,
            id: id,
            label: v,
            labelWidth: 80,
            min: -max,
            max: max,
            type: 'float',
            width: 240
        })
        .bindEvent({
            blur: function(data) {
                ///*
                // fixme: if type is set, no change event fires
                var isResult = self.selectedPlace[0] == '_';
                if (!self.isAsync) {
                    
                } else {
                    !isResult && editPlace([v])
                }
                self.$map.value(self.selectedPlace, id, parseFloat(data.value));
                //*/
            }
        });
    }), [
        self.$areaInput = Ox.Input({
            id: 'area',
            type: 'float'
        }).hide()
    ]);

    self.$placeForm = Ox.Form({
            items: self.$placeFormItems,
            width: 240
        })
        .css({margin: '8px'})
        .hide();

    self.$areaKmInput = Ox.Input({
            disabled: true,
            id: 'areaKm',
            label: 'Area',
            labelWidth: 80,
            textAlign: 'right',
            width: 240
        })
        .css({margin: '4px 0 4px 0'})
        .appendTo(self.$placeForm);

    if (self.options.getMatches) {
        self.$matchesInput = Ox.Input({
                disabled: true,
                id: 'matches',
                label: 'Matches',
                labelWidth: 80,
                type: 'int',
                width: 240
            })
            .css({margin: '8px 0 4px 0'})
            .appendTo(self.$placeForm);
    }

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

    self.$newPlaceButton = Ox.Button({
            title: 'New Place',
            width: 96
        })
        .css({float: 'left', margin: '4px 2px 4px 4px'})
        .bindEvent({
            click: function() {
                self.$map.newPlace();
            }
        })
        .appendTo(self.$placeStatusbar);

    self.$placeButton = Ox.Button({
            title: 'Add Place',
            width: 96
        })
        .css({float: 'right', margin: '4px 4px 4px 2px'})
        .bindEvent({
            click: function() {
                if (self.$placeButton.options('title') == 'Add Place') {
                    addPlace();
                } else {
                    removePlace();
                }
            }
        })
        .hide()
        .appendTo(self.$placeStatusbar);

    /*
    self.$revertButton = Ox.Button({
            title: 'Revert',
            width: 96
        })
        .css({float: 'right', margin: '4px 4px 4px 2px'})
        .hide()
        .appendTo(self.$placeStatusbar);
    */

    /*
    self.mapResize = [
        Math.round(self.options.width * 0.25),
        Math.round(self.options.width * 0.5),
        Math.round(self.options.width * 0.75)
    ];
    */

    /*
    if (!self.isAsync) {
        self.placesLength = self.options.places.length;
        setStatus();
    } else {
        self.options.places({}, function(results) {
            self.placesLength = results.data.items;
            setStatus();
        });
    }
    */

    that.$element.replaceWith(
        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.$map,
                },
                {
                    collapsible: true,
                    element: Ox.SplitPanel({
                        elements: [
                            {
                                element: self.$placeTitlebar,
                                size: 24
                            },
                            {
                                element: self.$placeForm
                            },
                            {
                                element: self.$placeStatusbar,
                                size: 24
                            }
                        ],
                        orientation: 'vertical'
                    })
                    .bindEvent({
                        resize: function(data) {
                            self.$placeName.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'
        }).$element
    );

    function addPlace() {
        var place = self.$placeForm.values(),
            country = Ox.getCountryByGeoname(place.geoname);
        place.countryCode = country ? country.code : '';
        if (!self.isAsync) {
            place.id = self.selectedPlace.substr(1); // fixme: safe?
            self.selectedPlace = place.id;
            self.options.places.push(place);
            self.$list.options({
                items: Ox.clone(self.options.places),
                selected: [place.id]
            });
            self.$map.addPlace(place);
            self.$placeButton.options({title: 'Remove Place'});
            //setStatus();
        }
        //that.triggerEvent('addplace', {place: place});
        if (self.isAsync) {
            self.$placeButton.options({disabled: true, title: 'Adding Place'});
            self.options.addPlace(place, function(result) {
                if (result.status.code == 200) {
                    place.id = result.data.id;
                    self.selectedPlace = place.id;
                    self.$list.reloadList().options({selected: [place.id]});
                    self.$map.addPlace(place);
                    self.$placeButton.options({disabled: false, title: 'Remove Place'});
                } else {
                    if (result.data.names) {
                        if (result.data.names.indexOf(self.$nameInput.value()) > -1) {
                            self.$nameInput.addClass('OxError');
                        }
                        self.$alternativeNamesInput.setErrors(result.data.names);
                    }
                    if (result.data.geoname) {
                        self.$geonameInput.addClass('OxError');
                    }
                    self.$placeButton.options({disabled: false, title: 'Add Place'});
                }
            });
        }
    }

    function editPlace(keys) {
        var values = Ox.filter(self.$placeForm.values(), function(values, key) {
            return keys.indexOf(key) > -1;
        });
        values.id = self.selectedPlace;
        self.options.editPlace(values, function() {
            if (keys.indexOf(self.$list.options('sort')[0].key) > -1) {
                self.$list.reloadList();
            } else {
                Ox.forEach(values, function(value, key) {
                    if (key != 'id') {
                        self.$list.value(values.id, key, value);
                        self.$map.value(values.id, key, value);
                    }
                });
            }
        });
        if (keys.indexOf('name') > -1 || keys.indexOf('alternativeNames') > -1) {
            updateMatches();
        }
    }

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

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

    function removeItem(data) {
        var id = data.ids[0];
        // fixme: events or callback functions??
        that.triggerEvent('removeplace', {id: id});
        self.$map.removePlace(id);
    }

    function removePlace() {
        var index;
        Ox.Log('Map', 'REMOVE PLACE', self.selectedPlace, index)
        if (!self.isAsync) {
            // fixme: doesn't call self.options.removePlace!
            index = Ox.getPositionById(self.options.places, self.selectedPlace);
            self.options.places.splice(index, 1);
            self.$list.options({items: Ox.clone(self.options.places)});
            //setStatus();
        }
        // fixme: what is this? both options.removePlace and event removeplace??
        if (self.isAsync) {
            self.options.removePlace({id: self.selectedPlace}, function() {
                self.$list.options({selected: []}).reloadList(true);
            });
        }
        self.$map.removePlace();
        self.$placeButton.options({title: 'Add Place'});
        that.triggerEvent('removeplace', {id: self.selectedPlace});
    }

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

    function selectPlace(place) {
        var isResult = place.id && place.id[0] == '_';
        self.$list.options({
            selected: place.id && !isResult ? [place.id] : []
        });
        if (place.id) {
            //isResult && self.options.places.push(place);
            self.selectedPlace = place.id;
            self.$placeFlag.attr({
                src: Ox.getFlagByGeoname(place.geoname, 16)
            });
            self.$placeName.options({title: place.geoname || ''});
            self.$placeTitle.show();
            self.$areaKmInput.value(Ox.formatArea(place.area));
            self.$placeForm.values(place).show();
            self.$placeButton.options({title: isResult ? 'Add Place' : 'Remove Place'}).show();
            updateMatches();
        } else {
            self.selectedPlace = null;
            self.$placeTitle.hide();
            self.$placeForm.hide();
            self.$placeButton.hide();
        }
    }

    function toFixed(val) {
        return Ox.isNumber(val) ? val.toFixed(8) : val; // fixme: why can a string be passed ??
    }

    function updateList(key, value) {
        var query = {
                conditions: Ox.merge(
                    ['all', 'name'].indexOf(key) > -1
                        ? [{key: 'name', value: value, operator: '='}] : [],
                    ['all', 'alternativeNames'].indexOf(key) > -1
                        ? [{key: 'alternativeNames', value: value, operator: '='}] : [],
                    ['all', 'geoname'].indexOf(key) > -1
                        ? [{key: 'geoname', value: value, operator: '='}] : []
                ),
                operator: key == 'all' ? '|' : '&'
            };
        self.$list.options({
            items: function(data, callback) {
                return pandora.api.findPlaces(Ox.extend(data, {
                    query: query
                }), callback);
            }
        });
    }

    function updateMatches() {
        var names, place;
        if (self.options.getMatches) {
            place = self.$placeForm.values();
            names = Ox.filter(Ox.merge([place.name], place.alternativeNames), function(name) {
                return name !== '';
            });
            if (names.length) {
                self.$matchesInput.value('');
                self.options.getMatches(names, function(matches) {
                    self.$matchesInput.value(Ox.formatNumber(matches));
                });
            } else {
                self.$matchesInput.value(0);
            }
        }
    }

    /*@
    setOption <f> setOption
    @*/
    self.setOption = function(key, value) {
        if (key == 'height') {
            self.$list.size();
            self.$map.resizeMap();
        } else if (key == 'selected') {
            self.$list.options({selected: value});
        } else if (key == 'width') {
            self.$map.resizeMap();
        }
    }

    /*@
    focusList <f> focusList
    @*/
    that.focusList = function() {
        self.$list.gainFocus();
        return that;
    }

    /*@
    reloadList <f> reloadList
    @*/
    that.reloadList = function() {
        self.$list.reloadList();
        return that;
    }

    /*@
    resizeMap <f> resizeMap
    @*/
    that.resizeMap = function() {
        Ox.Log('Map', 'Ox.ListMap.resizeMap()')
        self.$map.resizeMap();
        return that;
    };

    return that;

};
