

var MAALoc = {
    Version: '0.14',
    SIG: '(ﾟAﾟ)'
};


MAALoc.Config = {
    baseuri:     'http://m.syntacticsugar.net/?',
    mapPath:     'map',
    dataPath:    'data',
    commentPath: 'comments',
    postPath:    'cmt.pl',

    defaultServer: 'p',
    defaultZone:   'yug',
    
    twitterBase: 'http://twitter.com/share?',
    twitterTag:  'MasterOfEpic',

    commentListMode: 'server',
    dispCategoryInHousedata: false,
    
    maxTextLength:     80,
    warningTextLength: 10,

    scrollingInterval: 6,

    useAutoImageSetting: false,  // true にすると下のテーブルを使わんで自動取得するけど IE がうんｋになっても知らない
    imageSize: {
        'yug': {
            'small': { x:256 , y:203 },
            'zoom': { x:1392 , y:1105 }
        },
        'thoress': {
            'small': { x:256 , y:247 },
            'zoom': { x:1392 , y:1344 }
        },
        'geoz-ne': {
            'small': { x:256 , y:257 },
            'zoom': { x:1026 , y:1030 }
        },
        'geoz-w': {
            'small': { x:256 , y:257 },
            'zoom': { x:1026 , y:1030 }
        },
        'geoz-s': {
            'small': { x:256 , y:257 },
            'zoom': { x:1026 , y:1030 }
        }
    }
};



MAALoc.Util = {
    uri: function(loc) {
        return MAALoc.Config.baseuri + loc.toQueryString();
    },

    uriJsonCmtIndividual: function(loc) {
        if (loc.block && loc.number) {
            return this.randomize(MAALoc.Config.commentPath + '/' + loc.server + '/'
                + loc.zone + '/' + loc.id() + '.json');
        }
        else {
            return false;
        }
    },
    
    uriJsonCmtServer: function(loc) {
        if (loc.server) {
            return this.randomize(MAALoc.Config.commentPath  + '/' + loc.server + '/all.json');
        }
        else {
            return false;
        }
    },
    
    uriJsonCmtAll: function() {
        return this.randomize(MAALoc.Config.commentPath + '/all.json');
    },
    
    uriCmtDelete: function(id) {
        return MAALoc.Config.postPath + '?del=' + id;
    },

    uriTweetIt: function(loc) {
        return MAALoc.Config.twitterBase + $H({
            url: this.uri(loc),
            text: loc.fullAddress() + ' #' + MAALoc.Config.twitterTag
        }).toQueryString();
    },

    uriMapImage: function(loc, size) {
        return MAALoc.Config.mapPath + '/' + loc.zone + '-' + size + '.jpg';
    },

    uriJsonLocation: function(loc) {
        return MAALoc.Config.dataPath + '/' + 'loc-' + loc.zone + '.json';
    },

    firstTime: {},
    
    uriJsonTownMap: function(loc) {
        var file = MAALoc.Config.dataPath + '/' + loc.server + '-' + loc.zone + '.json'
        var path = this.firstTime[file];

        if (!path)
            path = this.firstTime[file] = this.randomize(file);

        return path;
    },

    postQueryHash: function(loc) {
        return $H({
            s: loc.server,
            z: loc.zone,
            b: loc.block,
            n: loc.number,
            t: Comments.BottomWindow.form().text.value
        });
    },

    randomize: function(str) {
        var date = new Date().getTime();
        var rand = Math.floor(Math.random() * 10^8);
        return str + '?' + date + rand;
    },

    ts2Date: function(ts) {
        var result = ts.match(/^(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2}):(\d{2})$/);
        //return new Date(result[0], result[1], result[2], result[3], result[4], result[5], 0);
        return result[2] + '/' + result[3] + ' ' + result[4] + ':' + result[5];
    }
};


var Noun = {
    categories: {
        1: '鍛冶屋',
        2: '美容室',
        3: '装飾品屋',
        4: '食べ物・飲み物屋',
        5: '薬屋',
        6: '雑貨屋',
        7: '木工屋',
        8: '裁縫屋',
        9: 'レストラン',
       10: '酒場',
       11: 'ペット屋',
       12: '希少品取扱店',
       13: '娯楽施設'
    },
    
    servers: {
        p: 'Pearl',
        d: 'Diamond',
        e: 'Emerald'
    },

    zones: {
        yug:       'Yug',
        thoress:   'Thoress',
        'geoz-ne': 'Geoz NE',
        'geoz-w':  'Geoz W',
        'geoz-s':  'Geoz S'
    },

    s2pTable: {
        category: 'categories',
        server:   'servers',
        zone:     'zones'
    },

    zoneCount: function() {
        return $H(this.zones).keys().length;
    },
    
    serverCount: function() {
        return $H(this.servers).keys().length;
    },
 
    s2p: function(key) {
        return key in this.s2pTable ? this.s2pTable[key] : key;
    },
    
    know: function(type, key) {
        return key in this[this.s2p(type)];
    },
    
    check: function(type, key, defaultVal) {
        return this.know(type, key) ? key : defaultVal;
    },
    
    expand: function(type, key, defaultVal) {
        if (this.know(type, key)) {
            return this[this.s2p(type)][key];
        }
        else if (defaultVal != undefined) {
            return defaultVal;
        }
        else {
            return '';
        }
    },

    snip: function(type, key) {
        return $H(this[this.s2p(type)]).keys().find(function(s) {return this.expand(type, s) == key; }.bind(this));
    }
};


var Location = Class.create({
    server: '',
    zone:   '',
    block:   0,
    number:  0,

    initialize: function(server, zone, block, number) {
        if (zone == 'geoz')
            zone = 'geoz-ne';
        
        this.server = Noun.check('server', server, MAALoc.Config.defaultServer);
        this.zone = Noun.check('zone', zone, MAALoc.Config.defaultZone);
        this.block = (block -= 0) ? block : 0;     // 0-0 番地は地図の中央を示す特別な地番として扱う
        this.number = (number -= 0) ? number : 0;
        
        if (this.block == 0)
            this.number = 0;
    },

    toStringg: function() {
        return Noun.expand('server', this.server, '--') + ' '
            + Noun.expand('zone', this.zone, '--') + ' ' + this.banti();
    },

    toQueryString: function() {
        return [this.server, this.zone, this.block, this.number].join(';');
    },

    id: function() {
        return 'n' + this.block + '_' + this.number;
    },

    info: function() {
        var home = TownMap.lookup(this);
        return home ? home.info : '';
    },
    
    category: function() {
        var home = TownMap.lookup(this);
        return home ? Noun.expand('category', home.category, '--') : '--';
    },

    banti: function() {
        return this.block + '-' + this.number;
    },

    fullAddress: function() {
        var addr = this.toStringg();

        if (TownMap.lookup(this))
            addr += ', ' + this.info();

        return addr;
    },
    
    validation: function() {
        if (!NumberList.hasBlock(this.block)) {
            this.block = 0;
            this.number = 0;
        }
        else if (!NumberList.lookup(this)) {
            this.number = NumberList.numbers(this.block).first();
        }
        
        return this;
    },

    x: function() {
        return NumberList.lookup(this).x;
    },

    y: function() {
        return NumberList.lookup(this).y;
    }
});


var NumberList = {
    data: {},
    tree: {},

    destroy: function() {
        delete this.data;
        delete this.tree;
    },
    
    lookup: function(loc) {
        var id;

        if (loc.hasOwnProperty('server')) {
            id = loc.id();
        }
        else if (arguments[1] != undefined) {
            id = new Location(null, null, arguments[0], arguments[1]).id();
        }
        else {
            id = loc;
        }

        return this.data.get(id);
    },

    update: function(loc, callback) {
        var uri = MAALoc.Util.uriJsonLocation(loc);
        delete this.data;
        
        new Ajax.Request(uri, {
            method: 'get',
            onSuccess: function(req) {
                this.data = $H(req.responseText.evalJSON());
                this.onload();
                callback();
            }.bind(this)
        });
    },

    onload: function() {
        this.tree = new Hash;

        this.data.each(function(pair) {
            var o = pair.value;
            if (!o) throw $continue;
            
            if (!this.tree.get(o.block))
                this.tree.set(o.block, new Hash);
            
            NumberList.tree.get(o.block).set(o.number, o);
        }.bind(this));
    },

    blocks: function() {
        return this.tree.keys().sort(function(a, b) { return (a - b) });
    },

    numbers: function(block) {
        if (this.tree.get(block)) {
            return this.tree.get(block).keys().sort(function(a, b) { return (a - b) });
        }
        else {
            return [];
        }
    },

    hasBlock: function(block) {
        return this.tree.get(block) ? true : false;
    }
};


var TownMap = {
    data: {},
    
    destroy: function() {
        delete this.data;
    },
    
    lookup: function(loc) {
        var id;

        if (loc.hasOwnProperty('server')) {
            id = loc.id();
        }
        else if (arguments[1] != undefined) {
            id = new Location(null, null, arguments[0], arguments[1]).id();
        }
        else {
            id = loc;
        }
        
        return this.data.get(id);
    },

    update: function(loc, callback) {
        var uri = MAALoc.Util.uriJsonTownMap(loc);
        this.data = {};

        new Ajax.Request(uri, {
            method: 'get',
            onSuccess: function(req) {
                this.data = $H(req.responseText.evalJSON());
                callback();
            }.bind(this)
        });
    },

    houses: function() {
        var regex = /^n(\d+)_(\d+)$/;
        
        return this.data.keys().sort(function(a, b) {
            var am = a.match(regex);
            var bm = b.match(regex);
            return ((am[1] - 0) * 1000 + (am[2] - 0)) - ((bm[1] - 0) * 1000 + (bm[2] - 0));
        }).map(function(e) { return this.data.get(e) }.bind(this));
    }
};


MAALoc.App = {
    loc: null,
    prev: null,
    
    init: function() {
        var locator = $('locator');
        Event.observe(locator.server, 'change', this.eventChangeServer.bindAsEventListener(this));
        Event.observe(locator.zone,   'change', this.eventChangeZone.bindAsEventListener(this));
        Event.observe(locator.block,  'change', this.eventChangeBlock.bindAsEventListener(this));
        Event.observe(locator.number, 'change', this.eventChangeNumber.bindAsEventListener(this));
        Event.observe($('tweet-it').firstChild, 'click', this.eventTweetIt.bindAsEventListener(this));
        
        MAALoc.Map.init();
        
        var params = location.search.substr(1).split(';');
        this.loc = new Location(params[0], params[1], params[2], params[3]);

        this.update();
    },

    destroy: function() {
        var locator = $('locator');
        [locator.server, locator.zone, locator.block, locator.number].invoke('stopObserving');
    },
    
    numberIsSelected: function() {
        return this.loc.block && this.loc.number;
    },

    // リロードの必要性
    //              server   zone   server + zone  地番しか変わってない
    // NumberList     x       o           o          x
    // MapImage       x       o           o          x
    // TownMap        o       o           o          x
    
    needLoadNumberList: function() {
        if (!this.prev)
            return true;
        else if (this.prev.zone == this.loc.zone)
            return false;
        return true;
    },
    
    needLoadImages: function() {
        return this.needLoadNumberList();
    },
    
    needLoadTownMap: function() {
        if (!this.prev)
            return true;
        if (this.prev.server == this.loc.server && this.prev.zone == this.loc.zone)
            return false;
        return true;
    },
    
    needOnlySelectNumber: function() {
        if (!this.prev)
            return false;
        return (this.prev.server == this.loc.server && this.prev.zone == this.loc.zone);
    },

    needNothing: function() {
        if (!this.prev)
            return false;
        return this.needOnlySelectNumber()
            && (this.loc.block == this.prev.block && this.loc.number == this.prev.number);
    },
    
    // 
    
    update: function() {
        if (this.needNothing())
            return false;
        
        if (this.needOnlySelectNumber()) {
            this.onloadTownMap();
        }
        else {
            MAALoc.Status.showConstructingIndicator();

            if (this.needLoadNumberList()) {
                NumberList.update(this.loc, this.onloadNumberlist.bind(this));
            }
            else {
                this.onloadMapImages();
            }
        }
    },
    
    onloadNumberlist: function() {
        if (this.needLoadImages()) {
            MAALoc.Map.loadMapImages(this.loc, this.onloadMapImages.bind(this));
        }
        else {
            this.onloadMapImages();
        }
    },

    onloadMapImages: function() {
        if (this.needLoadTownMap()) {
            TownMap.update(this.loc, this.onloadTownMap.bind(this));
        }
        else {
            this.onloadTownMap();
        }
    },
    
    onloadTownMap: function() {
        if (this.needLoadNumberList()) {
            MAALoc.Map.makeListElements(this.loc, this.prev);
            MAALoc.Map.makeBlockSelecter(this.loc);
        }
        
        if (this.needLoadTownMap()) {
            MAALoc.Map.applyTownMap();
            Comments.SideWindow.HouseData.make();
        }
        
        MAALoc.Map.eventDepopupTooltip();

        this.loc.validation();
        this.selectNumber();
        this.syncForm();
        
        this.prev = null;
        
        MAALoc.Status.hideConstructingIndicator();
    },
    
    selectNumber: function() {
        if (NumberList.lookup(this.loc)) {
            MAALoc.Map.makeNumberSelecter(this.loc);
            MAALoc.Map.selectNumber(this.loc);
            MAALoc.Map.dispHouseInfo(this.loc);
            Comments.App.loadComments('individual', 'bottom', this.loc);
        }
        else {
            MAALoc.Map.makeNumberSelecter(this.loc);
            this.deselectNumber();
        }
    },

    deselectNumber: function() {
        this.loc.block = 0;
        this.loc.number = 0;
        MAALoc.Map.deselectNumber();
        MAALoc.Map.setUri(this.loc);
        Comments.BottomWindow.stop();
        Comments.BottomWindow.clear();
        Comments.BottomWindow.closeList();
    },

    syncForm: function() {
        MAALoc.Map.setOption('server', this.loc);
        MAALoc.Map.setOption('zone', this.loc);
        MAALoc.Map.setOption('block', this.loc);
        MAALoc.Map.setOption('number', this.loc);
        MAALoc.Map.setUri(this.loc);
    },

    pushNewLocation: function(loc) {
        this.prev = this.loc;
        this.loc = loc ? loc : Object.clone(this.prev);
        return this.loc;
    },
    
    eventChangeServer: function(event) {
        this.pushNewLocation();
        this.loc.server = Event.element(event).value;
        this.update();
    },

    eventChangeZone: function(event) {
        this.pushNewLocation();
        this.loc.zone = Event.element(event).value;
        this.update();
    },
    
    eventChangeBlock: function(event) {
        this.pushNewLocation();
        this.loc.block = Event.element(event).value;
        this.loc.number = 0;
        this.update();
    },
    
    eventChangeNumber: function(event) {
        this.pushNewLocation();
        this.loc.number = Event.element(event).value;
        this.update();
    },
    
    eventChangeNumberById: function(event) {
        var data = NumberList.lookup(Event.element(event).id);
        this.pushNewLocation();
        this.loc.block = data.block;
        this.loc.number = data.number;
        this.update();
    },

    eventJump: function(event) {
        var elem = Event.element(event);
        var addr = elem.firstChild.data.toLowerCase();
        var params = addr.match(/([a-z]+) ([a-z-]+)(?: ([a-z-]+))? (\d+)-(\d+)/);
        
        if (params[3])
            params[2] = params[2] + '-' + params[3];
        
        this.pushNewLocation(new Location(params[1], params[2], params[4], params[5]));
        this.update();
    },

    eventTweetIt: function(event) {
        //var elem = Event.element(event);
        var elem = $('tweet-it').firstChild;
        
        var width = 550;
        var height = 450;
        
        var left = Math.round(screen.width / 2 - width / 2);
        var top = 0;
        if (screen.height > height) {
            top = Math.round(screen.height / 2 - height / 2)
        }
        
        var popup = window.open(
            elem.href, 'twitter_tweet', 
            'left=' + left + ",top=" + top + ",width=" + width + ",height=" + height
            + ",personalbar=0,toolbar=0,scrollbars=1,resizable=1");
        
        if (popup) {
            popup.focus();
        }
        else {
            window.location.href = elem.parentNode.href;
        }
    }
};


MAALoc.Status = {
    init: function() {
        Ajax.Responders.register({
            onCreate: function() {
                if (Ajax.activeRequestCount > 0) {
                    this.loading();
                }
            }.bind(this),
            
            onComplete: function() {
                if (Ajax.activeRequestCount === 0) {
                    this.done();
                }
            }.bind(this)
        });

        this.hideConstructingIndicator();
    },

    showStatusMessage: function(msg, isErr, overwrite) {
        var elem = this.indicatorMessage();

        if (elem.className == 'error-message') {
            if (!isErr && !overwrite) {
                return false;
            }
        }
        
        elem.update();
        elem.appendChild(document.createTextNode(msg));
        elem.className = isErr ? 'error-message' : 'normal-message';
    },
    
    loading: function() {
        this.indicatorImage().show();
        this.showStatusMessage('loading ... (' + Ajax.activeRequestCount + ')', 0, 1);
    },

    done: function() {
        this.indicatorImage().hide();
        this.showStatusMessage('done.', 0, 0);
    },

    indicatorImage: function() {
        return $('loading-indicator').down('img');
    },

    indicatorMessage: function() {
        return $('loading-indicator').down('span');
    },

    showConstructingIndicator: function() {
        $('constructing-indicator').show();
    },
        
    hideConstructingIndicator: function() {
        $('constructing-indicator').hide();
    }
};


MAALoc.Map = {
    init: function() {
        $('sight').hide();
        $('tooltip').hide();
        
        Position.prepare();

        var map = $('zoom-map-image');
        Event.observe(map, 'mousedown', this.eventMouseDown.bindAsEventListener(this));
        Event.observe(map, 'mousemove', this.eventMouseMove.bindAsEventListener(this));
        Event.observe(document, 'mouseup', this.eventMouseUp.bindAsEventListener(this));
        Event.observe($('small-map'), 'mousedown', this.eventScrollMap.bindAsEventListener(this));
        
        if (Prototype.Browser.IE6) {
            // IE が地番選択しまくりでうざい Event.observe じゃだめだった
            $('zoom-map-frame', 'small-map', 'small-map-image',
              'cursor', 'zoom-map-image').each(function(elem) {
                  elem.setAttribute('onselectstart', new Function('return false;'))
            });
        }
        else if (Prototype.Browser.IE) {
            $('zoom-map-frame', 'small-map', 'small-map-image',
              'cursor', 'zoom-map-image').each(function(elem) {
                  Event.observe(elem, 'selectstart', function(event) {
                      event.returnValue = false;
                      return false;
                  });
            });
        }
    },
    
    destroy: function() {
        $A($('zoom-map').getElementsByTagName('li')).invoke('stopObserving');
        $('zoom-map-image').stopObserving();
        $('small-map').stopObserving();
        $('zoom-map-frame', 'small-map', 'small-map-image', 'cursor', 'zoom-map-image').invoke('stopObserving');
        document.stopObserving();
    },
    
    oldloadMapImages: function(loc, callback) {
        var zimg = new Image();
        var zfilename = MAALoc.Util.uriMapImage(loc, 'zoom');
        zimg.src = Prototype.Browser.IE ? MAALoc.Util.randomize(zfilename) : zfilename;

        Event.observe(zimg, 'load', function(e) {
            this.setImages('zoom', zfilename, zimg.width, zimg.height);
            zimg = null;

            var simg = new Image();
            var sfilename = MAALoc.Util.uriMapImage(loc, 'small');

            // IE がキャッシュに画像があると load イベントを fire しない unk
            simg.src = Prototype.Browser.IE ? MAALoc.Util.randomize(sfilename) : sfilename;

            Event.observe(simg, 'load', function(e) {
                this.setImages('small', sfilename, simg.width, simg.height);
                simg = null;
                callback();
            }.bind(MAALoc.Map));
        }.bind(this));
    },
 
    loadMapImages: function(loc, callback) { // 動的にやりたかったけどメモリリークが酷いのでとりあえず間に合わせ 
        if (MAALoc.Config.useAutoImageSetting) {
            this.oldloadMapImages(loc, callback);
            return;
        }
        
        var t = MAALoc.Config.imageSize[loc.zone];
        
        this.setImages('zoom', MAALoc.Util.uriMapImage(loc, 'zoom'), t.zoom.x, t.zoom.y);
        this.setImages('small', MAALoc.Util.uriMapImage(loc, 'small'), t.small.x, t.small.y);
        callback();
    },
    
    setImages: function(size, filename, width, height) {
        var div = $(size + '-map-image');
        div.style.width = width + 'px';
        div.style.height = height + 'px';
        div.style.backgroundImage = 'url(' + filename + ')';
        
        div = $(size + '-map');
        div.style.width = width + 'px';
        div.style.height = height + 'px';
    },
    
    makeListElements: function(loc, prevloc) {
        var current = $('number-list');
        var cached = $('cache-' + loc.zone);

        if (current) {
            current.hide();
            current.setAttribute('id', 'cache-' + prevloc.zone);
        }

        if (cached) {
            cached.show();
            cached.setAttribute('id', 'number-list');
            return true;
        }

        var ev = MAALoc.App.eventChangeNumberById.bindAsEventListener(MAALoc.App);

        var list = document.createElement('ul');
        $('zoom-map').appendChild(list);
        list.id = 'number-list';

        NumberList.data.each(function(pair) {
            var o = pair.value;
            var li = document.createElement('li');
            list.appendChild(li);
            li.setAttribute('id', 'n' + o.block + '_' + o.number); //var id = loc.id.bind(o)();
            li.style.left = o.x - 8 + 'px';
            li.style.top = o.y - 8 + 'px';
            li.appendChild(document.createTextNode(o.number));
            
            Event.observe(li, 'mouseover', MAALoc.Map.eventPopupTooltip);
            Event.observe(li, 'mouseout',  MAALoc.Map.eventDepopupTooltip);
            Event.observe(li, 'mousedown', ev);
        });
    },

    applyTownMap: function() {
        var list = $('number-list');
        $A(list.getElementsByTagName('li')).each(function(li) {
            var t = TownMap.lookup(li.id);
            if (t)
                li.className = 'category' + t.category;
            else
                li.className = '';
        });
    },

    makeBlockSelecter: function(loc) {
        var locator = $('locator');
        Element.update(locator.block);

        var option = document.createElement('option');
        locator.block.appendChild(option);
        option.setAttribute('value', 0);
        Element.update(option, '----');

        NumberList.blocks().each(function(block) {
            option = document.createElement('option');
            locator.block.appendChild(option);
            option.setAttribute('value', block);
            Element.update(option, block);
        });
    },

    makeNumberSelecter: function(loc) {
        var locator = $('locator');
        var prev = locator.number.getAttribute('house:block');

        if (prev != undefined && prev == loc.block) {
            return true;
        }

        Element.update(locator.number);
        locator.number.setAttribute('house:block', loc.block);

        if (NumberList.numbers(loc.block).length) {
            NumberList.numbers(loc.block).each(function(number) {
                var option = document.createElement('option');
                locator.number.appendChild(option);
                option.setAttribute('value', number);
                Element.update(option, number);
            });
        }
        else {
            var option = document.createElement('option');
            locator.number.appendChild(option);
            option.setAttribute('value', 0);
            Element.update(option, '----');
        }
    },
    
    selectNumber: function(loc) {
        var x = loc.x();
        var y = loc.y();
        var sight = $('sight');
        sight.show();
        sight.style.left = x - 9 + 'px';
        sight.style.top = y - 10 + 'px';
        this.scrollMapXY($('zoom-map'), x, y);
        
        Comments.BottomWindow.enableForm();
    },
    
    deselectNumber: function() {
        $('house-number').update('--');
        $('house-category').update('--');
        this.syncSmallMap(0.5, 0.5);
        this.syncZoomMap(0.5, 0.5);
        $('sight').hide();
        
        Comments.BottomWindow.disableForm();
        Comments.BottomWindow.hideForm();
    },

    setUri: function(loc) {
        $('locator').uri.value = MAALoc.Util.uri(loc);
        $('tweet-it').firstChild.href = MAALoc.Util.uriTweetIt(loc);
    },

    dispHouseInfo: function(loc) {
        $('house-number').update().appendChild(document.createTextNode(loc.fullAddress()));
        $('house-category').update().appendChild(document.createTextNode(loc.category()));
    },

    eventPopupTooltip: function(event) {
        var elem = Event.element(event);
        var tip = $('tooltip');
        var dt = Element.extend(tip.down('dt'));
        var dd = Element.extend(tip.down('dd'));

        var result = elem.id.match(/^n(\d+)_(\d+)$/);
        dt.update().appendChild(document.createTextNode(result[1] + '-' + result[2]));
        
        var data = TownMap.lookup(elem.id);
        if (data) {
            dd.show().update().appendChild(document.createTextNode(data.info));
        }
        else {
            dd.hide().update();
        }
        
        tip.style.left = Event.pointerX(event) + 4 + 'px';
        tip.style.top = Event.pointerY(event) - tip.getDimensions().height + -10 + 'px';
        tip.show();
    },
    
    eventDepopupTooltip: function(event) {
        $('tooltip').hide();
    },

    eventMouseDown: function(event) {
        this.isMouseDown = true;
    },
    
    eventMouseUp: function(event) {
        this.isMouseDown = false;
        this.moveCount = 0;
    },
    
    prevX: 0,
    prevY: 0,
    isMouseDown: false,
    moveCount: false,
    mapFrame: null,
    mapImg: null,
    mapFrameW: null,
    mapFrameH: null,
    
    eventMouseMove: function(event) {
        if (!this.mapFrame) {
            this.mapFrame = $('zoom-map-frame');
            this.mapImg = $('zoom-map-image');
            var size = this.mapFrame.getDimensions();
            this.mapFrameW = size.width;
            this.mapFrameH = size.height;
        }
        
        if (this.isMouseDown) {
            if (this.moveCount > 3) {
                this.mapFrame.scrollLeft += (Event.pointerX(event) - this.prevX) * -1;
                this.mapFrame.scrollTop += (Event.pointerY(event) - this.prevY) * -1;

                
                Position.withinIncludingScrolloffsets(this.mapImg, this.mapFrameW / 2, this.mapFrameH / 2);
                this.syncSmallMap(Position.overlap('horizontal', this.mapImg),
                                  Position.overlap('vertical', this.mapImg));
            }

            this.prevX = Event.pointerX(event);
            this.prevY = Event.pointerY(event);
            this.moveCount++;
        }
        else {
            this.moveCount = 0;
        }
    },

    eventScrollMap: function(event) {
        var elem = $('small-map-image');
        Position.prepare();
        Position.withinIncludingScrolloffsets(elem, Event.pointerX(event), Event.pointerY(event));
        var x = Position.overlap('horizontal', elem);
        var y = Position.overlap('vertical', elem);
        this.syncSmallMap(x, y);
        this.syncZoomMap(x, y);
    },

    scrollMapXY: function(parent, x, y) {
        Position.prepare();
        Position.within(parent, x, y);
        x = Position.overlap('horizontal', parent);
        y = Position.overlap('vertical', parent);
        this.syncSmallMap(x, y);
        this.syncZoomMap(x, y);
    },

    syncZoomMap: function(x, y) {
        var parent = $('zoom-map-frame');
        var size = parent.getDimensions();
        var maxsize = $('zoom-map-image').getDimensions();
        parent.scrollTop = (maxsize.height * (1 - y) - size.height / 2).round();
        parent.scrollLeft = (maxsize.width * (1 - x) - size.width / 2).round();
    },
    
    syncSmallMap: function(x, y) {
        var parent  = $('small-map');
        var cursor = $('cursor');
        var size = parent.getDimensions();

        var ratio = size.width / $('zoom-map-image').getDimensions().width;
        var frameSize = $('zoom-map-frame').getDimensions();
        var w = (frameSize.width * ratio).round();
        var h = (frameSize.height * ratio).round();
    
        var top = (size.height * (1 - y) - w / 2).round();
        var left = (size.width * (1 - x) - h / 2).round();

        if (top < 0) {
            top = 0;
        }
        else if (top > size.height - h) {
            top = size.height - h;
        }
    
        if (left < 0) {
            left = 0;
        }
        else if (left > size.width - w) {
            left = size.width - w;
        }

        cursor.style.left = left + 'px';
        cursor.style.top = top + 'px';
        cursor.style.width = w + 'px';
        cursor.style.height = h + 'px';
    },

    setOption: function(name, loc) {
        var options = $('locator')[name].getElementsByTagName('option');
        
        $A(options).each(function(opt) {
            try {
                if ('selected' in opt) {
                    if (opt.value == loc[name]) {
                        opt.selected = true;
                    }
                    else {
                        opt.selected = false;
                    }
                }
            }
            catch(e) {
                alert('謎肉！');
            }
        });
    }
};


var Comments =  {};


Comments.App = {
    post: function() {
        new Ajax.Request(MAALoc.Config.postPath, {
            method: 'post',
            parameters: MAALoc.Util.postQueryHash(MAALoc.App.loc),

            onSuccess: function(req) {
                this.onloadPost($H(req.responseText.evalJSON()));
            }.bind(this)
        });
    },
    
    onloadPost: function(result) {
        var err = result.get('error');
        
        if (err) {
            MAALoc.Status.showStatusMessage(err, 1, 1);
        }
        else {
            Comments.BottomWindow.hideForm();
            this.loadComments('individual', 'bottom');
            this.loadComments(MAALoc.Config.commentListMode, 'side');
        }
    },

    loadComments: function(mode, target, loc) {
        var uri;
        var mode;
        var callback;
        loc = loc ? loc : MAALoc.App.loc;
        
        if (mode == 'individual') {
            uri = MAALoc.Util.uriJsonCmtIndividual(loc);
        }
        else if (mode == 'server') {
            uri = MAALoc.Util.uriJsonCmtServer(loc);
        }
        else if (mode == 'all') {
            uri = MAALoc.Util.uriJsonCmtAll();
        }
        else {
            return false;
        }

        if (target == 'bottom') {
            callback = Comments.BottomWindow.onload.bind(Comments.BottomWindow);
        }
        else if (target == 'side') {
            callback = Comments.SideWindow.onload.bind(Comments.SideWindow);
        }
        else {
            return false;
        }
        
        new Ajax.Request(uri, {
            method: 'get',
            onSuccess: function(req) {
                var result = $H(req.responseText.evalJSON());

                if (result && result.get('posts') && result.get('posts').length) {
                    callback(result.get('posts'));
                }
                else {
                    callback();
                }
            }.bind(this),
            
            on404: function() {
                callback();
            }
        });
    },

    deleteComment:  function(event) {
        var id = Event.element(event).id.match(/^cmt-(\d+)$/);
        
        new Ajax.Request(MAALoc.Util.uriCmtDelete(id[1]), {
            method: 'get',
            onSuccess: function(req) {
                var result = $H(req.responseText.evalJSON());
                var err = result.get('error');

                if (err) {
                    MAALoc.Status.showStatusMessage(err, 1, 1);
                }
                else {
                    this.loadComments('individual', 'bottom');
                }
            }.bind(this)
        });
    }
};


Comments.Base = Class.create(Enumerable, {
    initialize: function(parent) {
        this.parent = parent;
    },
    
    _each: function(iterator) {
        $A(this.parent.getElementsByTagName('li'))._each(iterator);
    },
    
    clear: function() {
        $A(this.parent.getElementsByTagName('a')).each(function(e) {
            e.stopObserving();
            e.parentNode.removeChild(e);
        });
        this.parent.update();
        return this;
    },

    lookup: function(index) {
        return this.first().next(index - 1);
    },

    first: function() {
        return this.parent.down('li');
    },
    
    _destroy: function() {
        this.clear();
        delete this.parent;
        return false;
    },

    destroy: function() {
        this._destroy();
    },
    
    update: function(entries, callback) {
        if (!callback)
            callback = this.defaultCallback;
        
        this.clear();

        var hndrJump = MAALoc.App.eventJump.bindAsEventListener(MAALoc.App);
        var hndrDelete = Comments.App.deleteComment.bindAsEventListener(Comments.App);
        
        entries.each(function(e) {
            var li = document.createElement('li');
            this.parent.appendChild(li);
            callback(li, e, hndrJump, hndrDelete);
        }.bind(this));
        
        return this;
    },

    defaultCallback: function(li, e, hndrJump, hndrDelete) {
        var c = document.createElement('a');
        li.appendChild(c);
        c.className = 'delete-link';
        c.id = 'cmt-' + e.id;
        c.setAttribute('href', '#');
        c.appendChild(document.createTextNode('x'));
        Event.observe(c, 'click', hndrDelete);

        c = document.createElement('SPAN');
        li.appendChild(c);
        c.className = 'date';
        c.appendChild(document.createTextNode(MAALoc.Util.ts2Date(e.ts)));
        
        c = document.createElement('a');
        li.appendChild(c);
        c.className = 'addr';
        c.setAttribute('href', '#');
        c.appendChild(document.createTextNode(e.server + ' ' + e.zone + ' ' + e.block + '-' + e.number));
        Event.observe(c, 'click', hndrJump);
        
        li.appendChild(document.createTextNode(' '));
        li.appendChild(document.createTextNode(e.text));
    }
});


Comments.SideWindow = new Comments.Base;
Object.extend(Comments.SideWindow, {
    init: function() {
        this.initialize(this.list());

        this.tabSwitches().each(function(e) {
            Event.observe(e, 'click', this.eventToggleView.bindAsEventListener(this));
        }.bind(this));
        
        this.serverSwitches().each(function(e) {
            Event.observe(e, 'click', this.eventSwitchServers.bindAsEventListener(this));
        }.bind(this));
    },

    destroy: function() {
        this.tabSwitches().invoke('stopObserving');
        this.serverSwitches().invoke('stopObserving');
        this._destroy();
    },

    eventSwitchServers: function(event) {
        var elem = Event.element(event);
        var server = elem.firstChild.data.slice(0, 1).toLowerCase();
        
        this.serverSwitches().each(function(e) {
            e.className = '';
        });
        elem.className = 'current';
        
        if (server == 'a') {
            Comments.App.loadComments('all', 'side');
        }
        else {
            if (MAALoc.App.loc.server != server) {
                MAALoc.App.pushNewLocation();
                MAALoc.App.loc.server = server;
                MAALoc.App.update();                       // 泥臭いのでなんとかしたい
            }
            
            Comments.App.loadComments('server', 'side');
        }
    },
    
    serverSwitches: function() {
        var switches = [];
        switches.push(this.serverSwitchAll());

        for (var i = 0; i < Noun.serverCount(); i++) {
            switches.push(this.serverSwitchAll().next('a', i));
        }

        return switches;
    },

    tabSwitches: function() {
        return [this.locationSwitch(), this.commentsSwitch(), this.houseDataSwitch()];
    },
    
    list: function() {
        return $('all-comments').down('ol');
    },
    
    commentsSwitch: function() {
        return $('all-comments').down('p', 1).down('a');
    },
    
    locationSwitch: function() {
        return $('information').down('p', 1).down('a');
    },

    houseDataSwitch: function() {
        return $('house-data').down('p', 1).down('a');
    },
    
    serverSwitchAll: function() {
        return $('all-comments').down('p', 0).down('a');
    },

    eventToggleView: function(event) {
        var clicked = Event.element(event).up(0).up(0);

        if (clicked.className == 'disable-view') {
            clicked.className = 'active-view';
            
            if (clicked.id == 'all-comments') {
                $('small-map').style.visibility = 'hidden';
                $('information').className = 'disable-view';
                $('house-data').className = 'disable-view';
                Comments.App.loadComments(MAALoc.Config.commentListMode, 'side');
                this.tabSync();
            }
            else if (clicked.id == 'information') {
                $('small-map').style.visibility = 'visible';
                $('all-comments').className = 'disable-view';
                $('house-data').className = 'disable-view';
            }
            else {
                $('small-map').style.visibility = 'hidden';
                $('information').className = 'disable-view';
                $('all-comments').className = 'disable-view';
                Comments.SideWindow.HouseData.tabSync();
            }
        }
    },

    tabSync: function() {
        this.serverSwitches().each(function(e) {
            if (e.firstChild.data.slice(0, 1).toLowerCase() == MAALoc.App.loc.server) {
                e.className = 'current';
            }
            else {
                e.className = '';
            }
        });
    },
    
    onload: function(entries) {
        if (entries) {
            this.update(entries);
        }
        else {
            this.clear();
        }
    }
});


Comments.SideWindow.HouseData = new Comments.Base;
Object.extend(Comments.SideWindow.HouseData, {
    init: function() {
        this.initialize(this.list());
        
        this.switches().each(function(e) {
            Event.observe(e, 'click', this.eventSwitchViews.bindAsEventListener(this));
        }.bind(this));
    },

    destroy: function() {
        this.switches().invoke('stopObserving');
        this._destroy();
    },
    
    list: function() {
        return $('house-data').down('ul');
    },
    
    make: function() {
        this.clear();
        
        var server = MAALoc.App.loc.server;
        var zone = MAALoc.App.loc.zone;

        this.update(TownMap.houses(), function(li, e, hndrJump) {
            var c = document.createElement('a');
            li.appendChild(c);
            c.className = 'addr';
            c.setAttribute('href', '#');
            c.appendChild(document.createTextNode(server + ' ' + Noun.expand('zones', zone) + ' ' + e.number));
            Event.observe(c, 'click', hndrJump);
            
            li.appendChild(document.createTextNode(' '));

            if (MAALoc.Config.dispCategoryInHousedata)
                li.appendChild(document.createTextNode(Noun.expand('category', e.category) + ', '));
            
            li.appendChild(document.createTextNode(e.info));
        });
    },

    switches: function() {
        return $$('#house-data .server-select a');
    },
    
    eventSwitchViews: function(event) {
        var elem = Event.element(event);
        var key = elem.getAttribute('title');
        var zone = Noun.check('zones', key, null);
        var server = Noun.check('servers', key, null);

        if (zone && MAALoc.App.loc.zone != zone) {
            MAALoc.App.pushNewLocation();
            MAALoc.App.loc.zone = zone;
            MAALoc.App.update();
        }
        else if (server && MAALoc.App.loc.server != server) {
            MAALoc.App.pushNewLocation();
            MAALoc.App.loc.server = server;
            MAALoc.App.update();
        }
        
        this.tabSync();
    },

    tabSync: function() {
        this.switches().each(function(e) {
            var key = e.getAttribute('title');
            
            if (key == MAALoc.App.loc.zone || key == MAALoc.App.loc.server) {
                e.className = 'current';
            }
            else {
                e.className = '';
            }
        });
    }
});


Comments.BottomWindow = new Comments.Base();
Object.extend(Comments.BottomWindow, {
    peQueue: $A(),
    currentOpacity: 1,
    current: null,
    
    init: function() {
        this.initialize(this.list());
        this.moreSwitch().hide();
        this.closeList();
        this.hideForm();
        Event.observe(this.form().submit, 'click', Comments.App.post.bindAsEventListener(Comments.App));
        Event.observe(this.form().text,   'keyup', this.eventTextLength.bindAsEventListener(this));
        Event.observe(this.moreSwitch(),  'click', this.eventToggleList.bindAsEventListener(this));
        Event.observe(this.formSwitch(),  'click', this.eventToggleForm.bindAsEventListener(this));
        Event.observe(this.formSwitch(),  'click', this.eventTextLength.bindAsEventListener(this));
    },

    destroy: function() {
        this.stop();
        delete this.current;
        [this.form().submit, this.form().text, this.moreSwitch(), this.formSwitch()].invoke('stopObserving');
        this._destroy();
    },
    
    form: function() {
        return $('comments').down('form');
    },

    list: function() {
        return $('comments').down('ol');
    },

    moreSwitch: function() {
        return $('comment-control').down('a');
    },
    
    formSwitch: function() {
        return $('comment-control').down('a', 1);
    },
    
    isOpen: function() {
        return this.list().className == 'opened' ? true : false;
    },
    
    start: function() {
        this.stop();
        this.scroll();
        this.peQueue.push(new PeriodicalExecuter(this.scroll.bind(this), MAALoc.Config.scrollingInterval));
        return this;
    },
    
    stop: function() {
        this.peQueue.invoke('stop');
        this.peQueue.clear();
        return this;
    },

    onload: function(entries) {
        var isOpenPrev = this.isOpen();
        
        this.closeList();
        this.stop();
        this.clear();

        if (entries) {
            this.update(entries);
            isOpenPrev ? this.openList() : this.closeList();
        }
        else {
            this.synchMoreSwitch();
        }
    },

    synchMoreSwitch: function() {
        this.size() ? this.moreSwitch().show() : this.moreSwitch().hide();
    },

    openList: function() {
        this.stop();

        if (!this.size())
            return false;

        this.synchMoreSwitch();
        this.parent.className = 'opened';
        this.moreSwitch().update('close');
        this.invoke('show').invoke('setOpacity', 1);
        
        return this;
    },
    
    closeList: function() {
        this.stop();
        this.synchMoreSwitch();
        this.parent.className = 'closed';
        this.moreSwitch().update('more');
        
        var first = this.first();
        if (!first)
            return false;
        this.current = first;

        this.invoke('hide');
        
        if (this.size() > 1) {
            this.start();
        }
        else {
            first.setOpacity(1);
            first.show();
        }
        
        return this;
    },
    
    enableForm: function() {
        this.formSwitch().className = 'enable';
    },

    disableForm: function() {
        this.formSwitch().className = 'disable';
        this.hideForm();
    },

    hideForm: function() {
        this.form().hide().reset();
        this.formSwitch().update('comment');
    },

    showForm: function() {
        this.isOpen() && this.closeList();
        this.form().show().text.clear().focus();
        this.formSwitch().update('hide form');
    },
    
    // Events
    
    eventToggleList: function(event) {
        this.isOpen() ? this.closeList() : this.openList();
    },
    
    eventToggleForm: function(event) {
        if (this.form().style.display == '') {
            this.hideForm();
        }
        else if (MAALoc.App.numberIsSelected()) {
            this.showForm();
        }
    },
    
    eventTextLength: function(event) {
        var form = this.form();
        var diff = MAALoc.Config.maxTextLength - form.text.value.length;
        
        if (diff <= 0 || diff == MAALoc.Config.maxTextLength) {
            form.submit.disable().hide();
        }
        else {
            form.submit.enable().show();
        }

        var counter = form.down('p');
        counter.update(diff);
        counter.className = (diff < MAALoc.Config.warningTextLength) ? 'count-over' : 'count-ok';
    },

    
    // Effects
    
    scroll: function(pe) {
        this.current.setOpacity(1);
        this.current.show();
        this.peQueue.push(new PeriodicalExecuter(this.startFade.bind(this), MAALoc.Config.scrollingInterval - 0.6));
    },

    startFade: function(pe) {
        pe.stop();
        this.currentOpacity = 1;
        this.peQueue.push(new PeriodicalExecuter(this.fade.bind(this), 0.03));
    },

    fade: function(pe) {
        var nextOpacity = this.currentOpacity - 0.1;
        var e = this.current;

        if (nextOpacity > 0) {
            this.currentOpacity = nextOpacity;
            
            if (!e) {
                pe.stop();
            }
            else {
                e.setOpacity(nextOpacity);
            }
        }
        else {
            pe.stop();
            e.hide();
            e.setOpacity(1);
            this.current = e.next() ? e.next() : this.list().down('li');
        }
    }
});


function destroy() {
    MAALoc.Map.destroy();
    Comments.SideWindow.HouseData.destroy();
    MAALoc.App.destroy();
    NumberList.destroy();
    TownMap.destroy();
    Comments.SideWindow.destroy();
    Comments.BottomWindow.destroy();
}


function initialize() {
    Event.observe(window, 'unload', destroy);
    Comments.SideWindow.init();
    Comments.BottomWindow.init();
    Comments.SideWindow.HouseData.init();
    MAALoc.Status.init();
    MAALoc.App.init();
}


