/*
    MarkerReducer.js - another one of GMarker manager
    $Revision$
    (c) 2007 WATANABE Hiroaki < hwat at mac dot com >
    This is distributed under the MIT license.
*/
/***************************************
    Class MarkerReducer
*/
var MarkerReducer = function(/*GMap2*/map,/*Object*/opts){
    opts = opts || {};
    this.debug;
    this.map = map;
    this.packages = []; // stack for MarkerReducer.Package
    this.borderPadding = opts.borderPadding ? parseInt(opts.borderPadding) : 8; // span to wrapping markers
    this.maxZoom = opts.maxZoom ? parseInt(opts.maxZoom) : 19;  // to do
    this.trackMarkers = opts.trackMarkers ? true : false;   // to do
    this.wrapperIcon = opts.wrapperIcon || null;    // GIcon. null means default icon G_DEFAULT_ICON.
    this.packageFieldOptions = opts.packageFieldOptions || null;  // Object. it need have properties for parameters of GPolygon.
    this.wrapperFieldOptions = opts.wrapperFieldOptions || null;  // If set this to not Object, then it won't draw polygon.
    this.mapPadding = opts.mapPadding || 10;

    if( opts.debug ){
        this.enableDebug(true);
    }else{
        this.disableDebug();
    }
    GEvent.bind( this.map, 'zoomend', this, function (o,n){
        if( o < n ){
            // on zoom in
            this.onZoomIn(o,n);
        }else{
            // on zoom out
            this.onZoomOut(o,n);
        }
    } );
    GEvent.addListener( this.map, 'addoverlay', function (overlay){
        overlay._overlaied = true;
    } );
    GEvent.addListener( this.map, 'removeoverlay', function (overlay){
        overlay._overlaied = false;
    } );
}
MarkerReducer.Logger = {};
MarkerReducer.Logger.write = function (){};

MarkerReducer.FIELD_PROPERTIES_PACKAGE = {
    "strokeColor":"#999999",
    "strokeWeight":1,
    "strokeOpacity":0.5,
    "fillColor":"#cccccc",
    "fillOpacity":0.25
};
MarkerReducer.FIELD_PROPERTIES_WRAPPER = {
    "strokeColor":"#990000",
    "strokeWeight":1,
    "strokeOpacity":0.5,
    "fillColor":"#cc0000",
    "fillOpacity":0.25
};
MarkerReducer.GPolygonFromGLatLngBounds = function(/*GLatLngBounds*/b,strokeColor,strokeWeight,strokeOpacity,fillColor,fillOpacity){
    var pts = [];
    pts.push( new GLatLng( b.getSouthWest().lat(), b.getSouthWest().lng() ) );
    pts.push( new GLatLng( b.getSouthWest().lat(), b.getNorthEast().lng() ) );
    pts.push( new GLatLng( b.getNorthEast().lat(), b.getNorthEast().lng() ) );
    pts.push( new GLatLng( b.getNorthEast().lat(), b.getSouthWest().lng() ) );
    pts.push( pts[0] );
    return new GPolygon( pts, strokeColor, strokeWeight, strokeOpacity, fillColor, fillOpacity );
}
MarkerReducer.prototype.enableDebug = function (force){
    if( ! this.debug || force ){
        this.debug = {};
    }
}
MarkerReducer.prototype.disableDebug = function (){
    this.debug = null;
}
MarkerReducer.prototype.getGBoundsOfMarkerField = function (/*MarkerReducer.Package*/pkg){
    var pt = this.map.fromLatLngToDivPixel( pkg.gmarker.getPoint() );
    var pt_sw   = new GPoint(pt.x - this.borderPadding, pt.y + this.borderPadding);
    var pt_ne   = new GPoint(pt.x + this.borderPadding, pt.y - this.borderPadding);
    pkg._gbounds = new GBounds( [pt_sw, pt_ne] );
    return pkg._gbounds;
}
MarkerReducer.prototype.getGLatLngBoundsOfMarkerField = function (/*MarkerReducer.Package*/pkg){
    var pt = this.map.fromLatLngToDivPixel( pkg.gmarker.getPoint() );
    var pt_sw   = new GPoint(pt.x - this.borderPadding, pt.y + this.borderPadding);
    var pt_ne   = new GPoint(pt.x + this.borderPadding, pt.y - this.borderPadding);
    var latlng_sw   = this.map.fromDivPixelToLatLng( pt_sw );
    var latlng_ne   = this.map.fromDivPixelToLatLng( pt_ne );
    pkg._glatlngbounds = new GLatLngBounds( latlng_sw, latlng_ne );
    return pkg._glatlngbounds;
}
MarkerReducer.prototype.getIntersection = function (/*MarkerReducer.Package*/pkg1,pkg2){
    var b1 = pkg1._gbounds ? pkg1._gbounds : this.getGBoundsOfMarkerField( pkg1 );
    var b2 = pkg2._gbounds ? pkg2._gbounds : this.getGBoundsOfMarkerField( pkg2 );
    var intersection = GBounds.intersection(b1,b2);
    return intersection.min().x < intersection.max().x ? intersection : null;
}
MarkerReducer.prototype.getIntersectionCenterGLatLng = function (/*MarkerReducer.Package*/pkg1,pkg2){
    var is = this.getIntersection(pkg1,pkg2);
    if( is ){
        return this.map.fromDivPixelToLatLng( new GPoint(
            is.minX + ((is.maxX - is.minX) / 2),
            is.minY + ((is.maxY - is.minY) / 2)) );
    }else{
        return null;
    }
}
MarkerReducer.prototype._extendGLatLngBoundsContainsAllIntersects = function (b,/*MarkerReducer.Package*/pkg){
    for( var i = 0; i < pkg.intersects.length; ++i ){
        b.extend( pkg.intersects[i].gmarker.getPoint() );
        b = this._extendGLatLngBoundsContainsAllIntersects(b, pkg.intersects[i]);
    }
    return b;
}
MarkerReducer.prototype.fitMapContainsAllIntersects = function (/*MarkerReducer.Package*/pkg1,pkg2){
    var b = new GLatLngBounds(pkg1.gmarker.getPoint());
    b.extend(pkg2.gmarker.getPoint());
    b = this._extendGLatLngBoundsContainsAllIntersects(b, pkg1);
    b = this._extendGLatLngBoundsContainsAllIntersects(b, pkg2);
    if( this.debug ){
        if( this.debug.intersects_bounds instanceof GPolygon ){
            this.map.removeOverlay(this.debug.intersects_bounds);
        }
        this.debug.intersects_bounds = MarkerReducer.GPolygonFromGLatLngBounds(b,"#000099",1,0.5,"#ccccff",0.25);
        this.map.addOverlay(this.debug.intersects_bounds);
    }
    var width  = this.map.getSize().width  - this.mapPadding * 2;
    var height = this.map.getSize().height - this.mapPadding * 2;
    var z = this.map.getCurrentMapType().getBoundsZoomLevel(b, new GSize(width,height) );
    MarkerReducer.Logger.write('thinned:'+ z);
    this.map.setCenter(b.getCenter(),z);
}
/*--------------------------------------
    Public Methods
*/
MarkerReducer.prototype.addMarker = function (/*GMarker*/gmarker, minZoom, maxZoom){
    minZoom = minZoom ||  0; // to do
    maxZoom = maxZoom || 19; // to do
    var pkg = new MarkerReducer.Package( gmarker );
    this.packages.push( pkg );
    return pkg;
}
MarkerReducer.prototype.addMarkers = function (/*Array of GMarker*/gmarkers, minZoom, maxZoom){
    for( var i = 0; i < gmarkers.length; ++i ){
        this.addMarker( gmarkers[i], minZoom, maxZoom );
    }
    return this;
}
MarkerReducer.prototype.getMarkerCount = function (/*(ignore)*/zoom){ // count shown markers.
    var count = 0;
    for( var i = 0; i < this.packages.length; ++i ){
        if( ! this.packages[i].gmarker.isHidden() ){
            ++count;
        }
    }
    return count;
}
MarkerReducer.prototype.refresh = function (){
    MarkerReducer.Logger.write("----- "+new Date());

    var t1 = (new Date()).getTime();
    this.clearGPolygons();
    var t2 = (new Date()).getTime();
    this.resets();
    var t3 = (new Date()).getTime();
    this.drawGPolygons();
    var t4 = (new Date()).getTime();

    MarkerReducer.Logger.write('refresh(): zoom level    : '+ this.map.getZoom() );
    MarkerReducer.Logger.write('refresh(): clear GPolygon: '+ (t2 - t1) +' ms');
    MarkerReducer.Logger.write('refresh(): reset()       : '+ (t3 - t2) +' ms');
    MarkerReducer.Logger.write('refresh(): draw GPolygon : '+ (t4 - t3) +' ms');
}
/*--------------------------------------
    Private Methods
*/
//MarkerReducer.prototype.assimilate = function (/*MarkerReducer.Package*/pkg){}
MarkerReducer.prototype.resets = function (){
    var t1 = (new Date()).getTime();
    for( var i = this.packages.length - 1; 0 <= i; --i ){
        if( this.packages[i].isWrapper() ){
            if( this.packages[i].zoominListener ){
                GEvent.removeListener( this.packages[i].zoominListener );
            }
            if( this.packages[i].gpolygon ){
                this.map.removeOverlay( this.packages[i].gpolygon );
            }
            if( this.packages[i].gmarker ){
                this.map.removeOverlay( this.packages[i].gmarker  );
            }
            this.packages.splice(i,1);
        }else{
            this.packages[i]._as_show = true;
            this.packages[i].wrapper = null;
            // clear caches
            this.packages[i]._gbounds = null;
            this.packages[i]._glatlngbounds = null;
        }
    }
    //--
    var t2 = (new Date()).getTime();
    var cnt = 0;
    var alone = [];
    for( var i = 0; i < this.packages.length; ++i ){ // collapses packages
        if( alone[i] || ! this.packages[i]._as_show ){
            continue;
        }
        alone[i] = true;
        for( var j = 0; j < this.packages.length; ++j ){
            if( alone[j] || i == j || ! this.packages[j]._as_show ){
                continue;
            }
            ++cnt;
            var intersection_center_glatlng = this.getIntersectionCenterGLatLng(this.packages[i],this.packages[j]);
            if( intersection_center_glatlng ){
                if( ! this.packages[i].wrapper ){
                    var wrapper = this.addMarker( new GMarker(intersection_center_glatlng, {"icon":this.wrapperIcon,"draggable":false} ) );
                    var closure = GEvent.callbackArgs( this, function ( pkg ){
                        MarkerReducer.Logger.write('- click zoom, pkg has intersects: '+ pkg.intersects.length);
                        this.fitMapContainsAllIntersects(pkg.intersects[0],pkg.intersects[1]);
                    }, wrapper );
                    wrapper.zoominListener = GEvent.bind( wrapper.gmarker, 'click', this.map, closure );
                    wrapper._as_show = true;
                    wrapper.intersects = [this.packages[i],this.packages[j]];
                    this.packages[i].wrapper = wrapper;
                    this.packages[j].wrapper = wrapper;
                    this.packages[i]._as_show = false;
                    this.packages[j]._as_show = false;
                }else{
                    MarkerReducer.Logger.write('<span style="color:red">NOTICE 1</span>');
                    this.packages[i].wrapper.intersects.push(this.packages[j]);
                    this.packages[j]._as_show = false;
                }
                alone[i] = false;
                break;
            }
        }
    }
    //--
    var t3 = (new Date()).getTime();
    for( var i = this.packages.length - 1; 0 <= i ; --i ){
        if( this.packages[i]._as_show ){
            if( ! this.packages[i].gmarker._overlaied ){
                this.map.addOverlay(this.packages[i].gmarker);
            }
            this.packages[i].gmarker.show();
        }else{
            this.packages[i].gmarker.hide();
        }
        this.packages[i]._as_show = null;
    }
    //
    var t4 = (new Date()).getTime();

    MarkerReducer.Logger.write('- reset cnt: '+ cnt);
    MarkerReducer.Logger.write('- reset t2-t1: '+ (t2 - t1) +' ms');
    MarkerReducer.Logger.write('- reset t3-t2: '+ (t3 - t2) +' ms');
    MarkerReducer.Logger.write('- reset t4-t3: '+ (t4 - t3) +' ms');

}
MarkerReducer.prototype.clearGPolygons = function (){
    for( var i = 0; i < this.packages.length; ++i ){
        if( this.packages[i].gpolygon instanceof GPolygon ){
            this.map.removeOverlay( this.packages[i].gpolygon );
        }
    }
}
MarkerReducer.prototype.drawGPolygons = function (){
    for( var i = 0; i < this.packages.length; ++i ){
        if( this.packages[i].gmarker instanceof GMarker && ! this.packages[i].gmarker.isHidden() ){
            // create GPolygon
            if( this.packages[i].isWrapper() ){
                if( this.wrapperFieldOptions instanceof Object ){
                    var glatlngbounds = this.packages[i]._glatlngbounds
                                      ? this.packages[i]._glatlngbounds
                                      : this.getGLatLngBoundsOfMarkerField(this.packages[i]);
                    for( var c = 0; c < this.packages[i].intersects.length; ++c ){
                        var extbounds = this.packages[i].intersects[c]._glatlngbounds
                                      ? this.packages[i].intersects[c]._glatlngbounds
                                      : this.getGLatLngBoundsOfMarkerField(this.packages[i].intersects[c]);
                        glatlngbounds.extend( extbounds.getSouthWest() );
                        glatlngbounds.extend( extbounds.getNorthEast() );
                    }
                    this.packages[i].gpolygon = MarkerReducer.GPolygonFromGLatLngBounds(
                        glatlngbounds,
                        this.wrapperFieldOptions.strokeColor,
                        this.wrapperFieldOptions.strokeWeight,
                        this.wrapperFieldOptions.strokeOpacity,
                        this.wrapperFieldOptions.fillColor,
                        this.wrapperFieldOptions.fillOpacity
                    );
                    this.map.addOverlay( this.packages[i].gpolygon );
                }
            }else{
                if( this.packageFieldOptions instanceof Object ){
                    this.packages[i].gpolygon = MarkerReducer.GPolygonFromGLatLngBounds(
                        ( this.packages[i]._glatlngbounds
                        ? this.packages[i]._glatlngbounds
                        : this.getGLatLngBoundsOfMarkerField(this.packages[i])
                        ),
                        this.packageFieldOptions.strokeColor,
                        this.packageFieldOptions.strokeWeight,
                        this.packageFieldOptions.strokeOpacity,
                        this.packageFieldOptions.fillColor,
                        this.packageFieldOptions.fillOpacity
                    );
                    this.map.addOverlay( this.packages[i].gpolygon );
                }
            }
        }
    }
}
MarkerReducer.prototype.onZoomIn = function (oldLevel,newLevel){
    this.refresh();
}
MarkerReducer.prototype.onZoomOut = function (oldLevel,newLevel){
    this.refresh();
}
/***************************************
    Class MarkerReducer.Package
*/
MarkerReducer.Package = function (/*GMarker or GLatLng*/gmarker,/*Object*/opts){
    opts = opts || {};
    this.gmarker = gmarker; // GMarker ... original GMarker
    this.intersects = 
        opts.intersects instanceof Array ? opts.intersects : [];
                            // Array of MarkerReducer.Package ... two references for child package
    this.wrapper;           // MarkerReducer.Package ... reference for parent package
    this.zoominListener;    // GEventListener ... chach for remove event listener
    this.gpolygon;          // GPolygon ... chach for remove overlay
    this.minZoom = opts.minZoom // to do
    this.maxZoom = opts.maxZoom // to do
}
MarkerReducer.Package.prototype.isWrapper = function (){
    return 0 < this.intersects.length ? true : false;
}
MarkerReducer.Package.prototype.hasWrapper = function (){
    return 0 < this.wrapper instanceof MarkerReducer.Package ? true : false;
}

