

/*
* Home Widjet
*/

// Hack to get syntax highlight to work on vim
// <script>

var first_load = false;

function t() {
  console.log( kMAPS.map.getZoom() );
  console.log( kMAPS.map.getBounds().toString() );
}

if( !window.console ) {
  window.console = {
    log: function() { },
    error: function() { },
    warn: function() { },
    debug: function() { },
    clear: function() { }
  };
}

// FIXME: remove later, just for debugging
function dist() {
  var b = kMAPS.map.getBounds();
  console.log( b.getSouthWest().distanceTo( b.getNorthEast() ) );
}

function adler32(  str ) {
  var mod_adler = 65521;

  var a = 1, b = 0;
  for( var i = 0; i<str.length; i++ ) {
    a = ( a + str.charCodeAt( i ) ) % mod_adler;
    b = ( b + a ) % mod_adler;
  }

  return Math.abs( ( b << 16 ) | a );

}

// A quick implementation of perl grep
if( !Array.prototype.grep ) {

  Array.prototype.grep = function( fun ) {
    if( typeof fun != 'function' )
      return this;

    var n = [ ];
    for( var i = 0; i < this.length; i++ ) { 
      if( fun( this[ i ] ) ) {
        n.push( this[ i ] );
      }
    }

    return n;
  };
}


// Array extensions
if (! Array.prototype.map) {

  Array.prototype.map = function(fun /*, thisp*/) {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}

// Hack extend google maps LatLng to contain distance calculation
google.maps.LatLng.prototype.distanceTo = function( point ) {
  if (typeof precision == 'undefined') precision = 4;  
  
  var R = 6371; 
  var lat1 = this.lat().toRad(), lon1 = this.lng().toRad();
  var lat2 = point.lat().toRad(), lon2 = point.lng().toRad();
  var dLat = lat2 - lat1;
  var dLon = lon2 - lon1;

  var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
          Math.cos(lat1) * Math.cos(lat2) * 
          Math.sin(dLon/2) * Math.sin(dLon/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = R * c;
  return d.toPrecisionFixed(precision);
};


/** Converts numeric degrees to radians */
if (typeof(Number.prototype.toRad) === "undefined") {
  Number.prototype.toRad = function() {
    return this * Math.PI / 180;
  }
}

/** Converts radians to numeric (signed) degrees */
if (typeof(Number.prototype.toDeg) === "undefined") {
  Number.prototype.toDeg = function() {
    return this * 180 / Math.PI;
  }
}


if (typeof(Number.prototype.toPrecisionFixed) === "undefined") {
  Number.prototype.toPrecisionFixed = function(precision) {
    if (isNaN(this)) return 'NaN';
    var numb = this < 0 ? -this : this;  // can't take log of -ve number...
    var sign = this < 0 ? '-' : '';
    
    if (numb == 0) { n = '0.'; while (precision--) n += '0'; return n };  // can't take log of zero
  
    var scale = Math.ceil(Math.log(numb)*Math.LOG10E);  // no of digits before decimal
    var n = String(Math.round(numb * Math.pow(10, precision-scale)));
    if (scale > 0) {  // add trailing zeros & insert decimal as required
      l = scale - n.length;
      while (l-- > 0) n = n + '0';
      if (scale < n.length) n = n.slice(0,scale) + '.' + n.slice(scale);
    } else {          // prefix decimal and leading zeros if required
      while (scale++ < 0) n = '0' + n;
      n = '0.' + n;
    }
    return sign + n;
  }
}


if( ! kMAPS ) {

  var kMAPS =  { 
    markers: { },
    map: null,
    infowindow: null,
    current_position_list: { },
    queue_num: 0,
    dev_mode: false,
    loading: false
  };

  kMAPS._l = 17;
  kMAPS._p = 0.020;
}


kMAPS.current_location = {
  latitude: 59.9167,
  longitude: 10.75 
};


kMAPS.config = {

  /* Only for the home page */
  map_width: null,
  map_height: null,
  map_id: null,
  search_action: null,
  image_path: null,
  search_url: 'search.php',
  min_post_code: 1299,
  apply_bounds_correction: true,

  start_lat: null,
  start_lon: null,
  start_zoom: null,
  start_course_leader_id: null,
  course_leader_loaded: null,

  query: null,
  already_searched: null,   
  
  search_form: null,
  keyword_form: null,

  initialized: null,

  use_absolute_path: false,
  static_urls: [""],
  app_path: '',
  inner_rect: false, 

  container: '',
  max_markers: 10,
  service_url: 'service/closest_courses.php',
  postnummer_to_location_service_url: 'service/postnummer_to_location.php',
  search_container: '.lsec',
  search_service_url: 'service/search.php',
  locate_center_service_url: 'service/find_center.php',
  browser_update_check: 'service/update_check.php',
  course_leader_service: 'service/get_course_leader.php',
  zoomlevel_service: 'service/zoom_level_adjust.php',
  map_options: {
    zoom: 12,
    minZoom: 6,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  },
  bounds_distance: 14,
  marker_base_url: 'dump/kimages/cimg/sel/',
  marker_selected_base: 'dump/kimages/cimg/sel',
  marker_deselected_base: 'dump/kimages/cimg/desel',
  kl_base_url: 'dump/kimages/kl',
  marker_size: {
    0: {
      size: new google.maps.Size( 28, 50 ),
      anchor: new google.maps.Point( 13, 48 ) 
    },
    1: {
      size: new google.maps.Size( 28, 80 ),
      anchor: new google.maps.Point( 13, 78 ) 
    },
    2: {
      size: new google.maps.Size( 51, 55 ),
      anchor: new google.maps.Point( 1, 52 )
    },
    3: {
      size: new google.maps.Size( 49, 56 ),
      anchor: new google.maps.Point( 48, 54 )
    },
    4: {
      size: new google.maps.Size( 76, 34 ),
      anchor: new google.maps.Point( 77, 19 )
    },
    5: {
      size: new google.maps.Size( 76, 34 ),
      anchor: new google.maps.Point( 2, 19 )
    },
    6: {
      size: new google.maps.Size( 48, 54 ),
      anchor: new google.maps.Point( 46, 1 )
    },
    7: {
      size: new google.maps.Size( 26, 78 ),
      anchor: new google.maps.Point( 12, 1 )
    },
    8: {
      size: new google.maps.Size( 48, 54 ),
      anchor: new google.maps.Point( 0, 2 )
    }

  },


  // possible values: DEFAULT, FLAT, SERVERSIDE
  sc_statergy: 'SERVERSIDE',

  sc_zoom_level: 11 

};


if( ! Util ) {
  var Util = {

    to_lat_lon_key: function( lat, lon ) {
      return String( lat ).substr( 0, 6 ) + String( lon ).substr( 0, 6 );
    },

    marker_exists: function( loc_id, course_leader_id ) {
      if( ! kMAPS.current_position_list[ loc_id ] || kMAPS.current_position_list[ loc_id ].markers.length == 0 )
        return false;

       for( var j in kMAPS.current_position_list[ loc_id ].markers ) {
         var m = kMAPS.current_position_list[ loc_id ].markers[ j ];
         if( m.__data && m.__data.course_leader_id == course_leader_id ) {
           return m;
         }
       } 

       return false;
    },

    get_position_count: function( location_id ) {
      for( var i in kMAPS.current_position_list ) {
        if( i == location_id ) {
          return kMAPS.current_position_list[i].markers.length
        }
      }
      return 0;
    },

    create_marker_image_url: function( course_leader_id, count ) {
      return kMAPS.uri_for( kMAPS.config.marker_base_url + '/' + count + '/KL' + course_leader_id + '.png' );
    },

    create_marker: function( opts ) {
   
      if( opts.count > 8 ) return null;

      var iurl = Util.create_marker_image_url( opts.data.course_leader_id, opts.count ); 
      var mi = new google.maps.MarkerImage(  iurl, kMAPS.config.marker_size[ opts.count ].size, null, kMAPS.config.marker_size[ opts.count ].anchor   );
      var _d = opts.data;
      var marker = new google.maps.Marker( {
          position: opts.position,
          map: kMAPS.map,
          icon: mi,
          title: ( _d.first_name + ' ' + _d.middle_name + ' ' + _d.last_name ), 
          zIndex: 10000
      } );
   
      marker.__data = opts.data;
      marker.count  = opts.count;
      if( opts.count === 0 ) {
        marker.__smaller = true;  
      }

      for( var i in opts.events ) {
        google.maps.event.addListener( marker, i, opts.events[i] );
      }
      
      return marker;
    },

    remove_unneeded_markers: function() {
      var b = kMAPS.map.getBounds();
      var _cp = kMAPS.current_position_list;
      var _tr = [ ];
      for( var i in _cp ) {
        if( _cp[i].position && ! b.contains( _cp[i].position ) ) {
          // Need to remove this entire thing
          for( var j in _cp[i].markers ) {
            _cp[i].markers[j].setMap( null );
          }
          _tr.push( i );
        }    
      }
 
      for( var k in _tr ) {
        delete kMAPS.current_position_list[ _tr[k] ]; 
      }
    },

    set_active_marker: function( marker ) {
     
      if( ! marker.inactive ) { 
        return false;
      }

      var iurl = kMAPS.uri_for( 
        kMAPS.config.marker_selected_base + '/' + marker.count + '/KL' + marker.__data.course_leader_id + '.png'
      );

      var icon = new google.maps.MarkerImage(  iurl, kMAPS.config.marker_size[ marker.count ].size, null, kMAPS.config.marker_size[ marker.count ].anchor   );
      marker.inactive = false;
      marker.setIcon( icon );
      marker.setZIndex( 10000 );
    },

    set_inactive_marker: function( marker ) {
      if( !marker.__data || marker.inactive ) {
        return false;
      } else {
        var iurl = kMAPS.uri_for( kMAPS.config.marker_deselected_base + '/' + marker.count + '/KL' + marker.__data.course_leader_id + '.png' );
        var icon = new google.maps.MarkerImage(  iurl, kMAPS.config.marker_size[ marker.count ].size, null, kMAPS.config.marker_size[ marker.count ].anchor   );
        marker.inactive = true;
        marker.setIcon( icon );
        marker.setZIndex( 5000 );
      }
    },

    resolve_type_of_markers: function( _adata ) {

      // FIXME: Needs refactoring
      try {
      // Prepare the look up structure
      var st = { };
      for( var i in _adata ) {

        // FIXME: quick test drive for Unfuddle #137
        _adata[i].course_location_id = Util.to_lat_lon_key( _adata[i].lat, _adata[i].lon );
        var cl_id = _adata[i].course_location_id;

        if( ! st[ cl_id ] ) { 
          st[ cl_id ] = [ ]; 
        }
        st[ cl_id ].push( _adata[i].course_leader_id );
      }

      // Now run through all markers, changing them when required
      for( var j in kMAPS.current_position_list ) {
        var mx = kMAPS.current_position_list[j].markers;

        if( ! st[ j ] ) {
          for( var k in mx ) { 
            Util.set_inactive_marker( mx[k] );
          }
        } else {
          for( var p in mx ) { 
            var found = false;
            for( var q in st[j] ) {
              if( mx[p].__data && mx[p].__data.course_leader_id == st[j][q] ) { 
                Util.set_active_marker( mx[p] );
                var found = true;
                break;
              } 
            }

            if( ! found ) {
              Util.set_inactive_marker( mx[p] );
            }

          } 
        }
      }

      } catch( e ) {  console.log( e );  }
    },

    disable_all_markers: function( ) {
      for( var k in kMAPS.current_position_list ) {
        var mx = kMAPS.current_position_list[k].markers;
        for( var p in mx ) {
          Util.set_inactive_marker( mx[ p ] );
        }
      }
    },
   
    reset_content_area: function() {
    }

  };
}


kMAPS.uri_for = function( path )  {

  return path;


  if( ! kMAPS.config.use_absolute_path ) {
    return path;
  } else {
    var host = kMAPS.config.static_urls[ Math.round( adler32( path ) % kMAPS.config.static_urls.length ) ];
    return 'http://' + [ host, kMAPS.config.app_path, path ].grep( function( $_ ) { return $_ !== ''; } ).join( '/' );
  }
};

// Placement Logic
var default_placement = function( iresult ) {
  if( iresult.geometry.viewport ) {
    var suggested_viewport = iresult.geometry.viewport;
    var bounds_coverage = suggested_viewport.getSouthWest().distanceTo( suggested_viewport.getNorthEast() );
        
     // Refactor this distance need to go in a config file
     //console.log( bounds_coverage );
     if( bounds_coverage < kMAPS.config.bounds_distance ) {
        var dist = kMAPS.config.bounds_distance, 
            diff = dist - bounds_coverage, 
            sw = suggested_viewport.getSouthWest(),
            ne = suggested_viewport.getNorthEast(),
            d  = ( ( diff / 2 ) / 111 ),
            y1 = Math.sqrt( Math.pow( d, 2 ) / 5 ); 

        var msw = new google.maps.LatLng( sw.lat() - y1 , sw.lng() - ( 2 * y1 ) ); 
        var mne = new google.maps.LatLng( ne.lat() + y1, ne.lng() + ( 2 * y1 ) ); 
        var nb = new google.maps.LatLngBounds( msw, mne );

        // FIXME: Screw all calculations, fitBounds is really wierd, or i don't know how to use it correctly
        kMAPS.map.fitBounds( nb ); 
        kMAPS.map.setCenter( iresult.geometry.location );
    } else flat_placement( iresult );
  }
};

var flat_placement    = function( result ) {
  //console.log( "flat placement eventually" );
  kMAPS.map.setZoom( kMAPS.config.sc_zoom_level ); 
  kMAPS.map.setCenter( result.geometry.location);
};


var serverside_placement = function( result ) {
  var b = result.geometry.viewport;
  var z = kMAPS.methods.get_zoom_level_from_bounds( b );
  $.post( kMAPS.config.zoomlevel_service, { bounds: b.toString(), current_zoom: z }, function( data ) {
    var d = eval( '(' + data + ')' );

    kMAPS.map.setCenter( result.geometry.location ); 
    // I am not sure why, but i think to cover for inaccurate bounding box calculations
    if( d.zoom != null ) {
       kMAPS.map.setZoom( parseInt( d.zoom ) );
    }
  } );
};

var get_correct_result = function( q, results ) {
  var iresult = null;
  var re = new RegExp( q, 'i' );
  for( var i in results ) {
    if( typeof results[i] != 'function' && results[i].formatted_address.match( re ) ) { 
      iresult = results[i];
    }
  }

  iresult = iresult || results[0];

  return iresult;

};

var verify_zoom_level = function( result ) {
  console.log( "Yo, i have the result" );
  console.log( result );

  if( kMAPS.config.apply_bounds_correction ) {

    console.log( "applying bounds correction" );
    setTimeout( function() {

      var _change_bounds = false;
      var _current_bounds = kMAPS.map.getBounds();

      // New Bounds
      var _b = new google.maps.LatLngBounds();

      for( var i = 0; i < result.points.length; i++ ) {
        console.log( result.points[ i ] );
        _b.extend( result.points[ i ] );
        console.log( _b.toString() );

        if( ! _current_bounds.contains( result.points[ i ] ) ) {
          _change_bounds = true;
        }

      }

      if( _change_bounds ) {

        console.log( "using bounds" );
        console.log( _b.toString() );
        kMAPS.map.fitBounds( _b );
        kMAPS.map.setCenter( result.center );

        // Use one zoom level lower than required
        setTimeout( function() {
          kMAPS.map.setZoom( kMAPS.map.getZoom() - 1 );
        }, 300 );
      } else {
        console.log( "not resetting bounds" );
      }

    }, 500 );

  }

};


kMAPS.methods = {


  not_found: function() {
    $( '.nf_message' ).fadeIn( 'slow' );
    window.setTimeout( function() {
      $( '.nf_message' ).fadeOut( 'slow' );
    }, 5000 );
  },

  __init: function() {
    $.c = kMAPS.config;

    var init_map = function( lat, lon, zoom ) {
      var c = new google.maps.LatLng( lat, lon ); 
  
      zoom = zoom || kMAPS.config.map_options.zoom;

      var other_options = {
        center: c,  
        zoom: zoom 
      };

      var options = $.extend( { }, $.c.map_options, other_options )

      kMAPS.map = new google.maps.Map( document.getElementById( $.c.container ), options ); 
      kMAPS.infowindow = new google.maps.InfoWindow( { } );
 
      // Locate me selector
      var lms = '.' + ( kMAPS.config.locate_me_class || 'locate_me' );

      // Use browser geo-location services where available
      if( $( lms ).length ) {
        if( navigator.geolocation ) {
          $( lms ).click( function( e ) {
            if( ! kMAPS.dev_mode && navigator.geolocation ) {
              try {
                // FIXME: #153  
                // FIXME: Remove the comments
                // kMAPS.methods.show_load_screen();
                navigator.geolocation.getCurrentPosition( function( p ) {
                  kMAPS.methods.hide_load_screen();
                  kMAPS.methods.update_location( p ); 
                });
              } catch( e ) { }
            }
          } );
        } else {
          $( lms ).css( 'visibility', 'hidden' );
        }
      }

    };

    // FIXME: This entire part needs refactoring
    if( kMAPS.map === null ) {

      if( kMAPS.config.start_lat !== null && kMAPS.config.start_lon !== null ) {
        init_map( kMAPS.config.start_lat, kMAPS.config.start_lon );
      } else if( !kMAPS.config.query && kMAPS.current_location.latitude && kMAPS.current_location.longitude ) {
        init_map( kMAPS.current_location.latitude, kMAPS.current_location.longitude );
      } else {

        var _q = '';
        if( kMAPS.config.query && kMAPS.config.already_searched == null ) {
          _q = kMAPS.config.query;
          kMAPS.config.already_searched = true;
        } else {
          _q = 'Oslo';
        }

        // $result
        // -> type: (local|geocoder)
        // -> center: LatLng
        // -> result: GeocoderResult|null
        kMAPS.methods.locate_and_run( _q, function( result ) {

          console.log( result );

          // Initialize the map
          init_map( result.center.lat(), result.center.lng() );

          //var iresult = get_correct_result( _q, results );

          // If the result is from geocoder
          if( result.type == 'geocoder' ) {

            // Publishing a search result 
            if( kMAPS.config.query ) {
              kMAPS.q = kMAPS.config.query;

              switch( kMAPS.config.sc_statergy ) {
                case 'FLAT':
                  flat_placement( result.result );
                break;
                case 'DEFAULT':
                  default_placement( result.result );
                break;
                case 'SERVERSIDE':
                  serverside_placement( result.result );
                break;
              }

            }

          } else {
            // Local placement
            kMAPS.map.setZoom( 12 );
            verify_zoom_level( result );

          }

          if( kMAPS.context == 'search' ) {
            kMAPS.methods.init_search();
          } else {
            kMAPS.methods.init();
          }

        });
      }
    }
  },


  get_zoom_level_from_bounds: function( bounds ) {
    var sw = bounds.getSouthWest(),  
        ne = bounds.getNorthEast(),
        minx = sw.lng(),
        maxx = ne.lng(),
        miny = ne.lat(),
        maxy = sw.lat();
    
     var dlonpp = Math.abs( maxx - minx ) / 570; 
     var dlatpp = Math.abs( maxy - miny ) / 300;
     var resolution = Math.max( dlonpp, dlatpp );

     // Constants were determined emperically
     return ( 12 + Math.round( Math.log( 0.00034 / resolution ) / Math.log( 2 ) ) );

  },


  test: function() {
    console.log( kMAPS.map.getZoom() );
    console.log( kMAPS.methods.get_zoom_level_from_bounds( kMAPS.map.getBounds() ) )
  },

  init_search: function() {
    kMAPS.context = 'search';
    kMAPS.methods.__init();

    if( kMAPS.map ) {
      setTimeout( function() { 
        kMAPS.methods.load_data_for_search( kMAPS.map.getBounds().toString() ); 
        google.maps.event.addListener( kMAPS.map, 'bounds_changed', kMAPS.methods.searchBoundsChanged );
        // FIXME: Another hack, must me set up from configuration file
        google.maps.event.addListener( kMAPS.map, 'dragend', function( ) { $( '#scity' ).val( '' ); } );
      }, 5000 );
    }

    if( $( kMAPS.config.search_container ).length ) {
      $( kMAPS.config.search_container ).click( function( e ) { 
        var target = e.target;
        if( target.tagName == 'INPUT' ) {
          $('img#search_city_btn_advanced').attr('src', 'img/finn_kurs.png');
        }
      } );
    }


    // FIXME: A quick hack to get search working
    // FIXME: Refactor: move this somewhere else
    var search_city_lambda = function( e ) {
      var value = $( '#scity' ).attr( 'value' );

      // Reset all filters
      $( '#c input[value="both"]' ).attr( 'checked', true );
      $( '.lsec input[type="checkbox"]' ).attr( 'checked', true );
      $( '.lsec_new input[type="checkbox"]' ).attr( 'checked', true );



      first_load = true;

      // $result
      // -> type: (local|geocoder)
      // -> center: LatLng
      // -> result: GeocoderResult|null
      kMAPS.methods.locate_and_run( value, function( result ) {
        //console.log( results );

        if( result.type == 'geocoder' ) {

          console.log( "Searched for: " + kMAPS.q );

          // setup center
          kMAPS.map.setCenter( result.center );

          switch( kMAPS.config.sc_statergy ) {
            case 'FLAT':
              flat_placement( result.result );
            break;
     
            case 'SERVERSIDE':
              console.log( 'serverside placment' );
              serverside_placement( result.result );
            break;

            case 'DEFAULT':
              default_placement( result.result );
            break;
          }
        } else {
          // Local placement
          kMAPS.map.setCenter( result.center );
          kMAPS.map.setZoom( 12 );
          verify_zoom_level( result );

        }
      } );

    };


    $( '.group_selectors' ).click( function( e ) {
      e.preventDefault();
      var t = $( e.target );

      if( t.hasClass( 'select_all' ) ) {
        var stat = true;
      } else if( t.hasClass( 'clear' ) ) {
        var stat = false;
      }

      if( stat != undefined ) {
        t.parents( 'div.group_selectors' ).next().find( 'li input[type="checkbox"]' ).attr( 'checked', stat );
      } else {
        t.parents( 'div.group_selectors' ).next().find( 'li input[type="checkbox"]' ).attr( 'checked', false );

        var _attr = 'input[name="' + ( ( t.hasClass( 'med_t' ) ) ? 'med_training[]' : 'privatundervisning[]' ) + '"]';
        var a = $( 'input[name="course_type[]"]' );
        var b = $( _attr ); 

        a.each( function( i, _ctx ) {
          b.each( function( j, _tx ) { 
            if( $( _ctx ).val() == $( _tx ).val() )
              $( _ctx ).attr( 'checked', true );
            
          } )
        } );

      }

      $('img#search_city_btn_advanced').attr('src', 'img/finn_kurs.png');
    } );

    $( 'img#search_city_btn_advanced' ).click( function( e ) {
      $('img#search_city_btn_advanced').attr('src', 'img/finn_kurs_grey.png');
      kMAPS.methods.load_data_carefully( kMAPS.methods.load_data_for_search );
    } );
    
    $( '#search_city_btn' ).click( search_city_lambda ); 
    $( '#' + kMAPS.config.keyword_form ).submit( function( e ) {
      e.preventDefault();
      e.stopPropagation();
      search_city_lambda( e );
    });
  },

  get_map_markup: function( ) {
    var map_style = 'style="width:' + kMAPS.config.map_width + 'px; height: ' + kMAPS.config.map_height + 'px; float:none; clear: both; margin: 0 0 10px 5px;"';
    var h_style = 'style="font-family: Verdana,Arial,sans-serif; color: #900; font-size: 15px; line-height: 140%; font-weight: normal; margin: 5px 5px;  "'
    return '<div style="height: 100%;">' +
      '<div id="' + kMAPS.config.map_id + '" ' + map_style + '>'  +
      '</div>' + 
      '<div style="margin-left: 13px;" id="adv_search_btn">' +
          '<form action="' + kMAPS.config.search_url + '" id="_gr_search_form" >' +
            '<div class="scont" style="float: left; height: 30px; padding-top: 4px;">' +
              '<input type="text" onfocus="this.value=\'\'" value="Søk med postnr eller stedsnavn" size="30" id="scity" name="q"> &nbsp;&nbsp;&nbsp;' +
            '</div>' +
            '<div class="sbutton" style="clear: right">' + 
	      '<input type="button" onclick="javascript: document.getElementById( \'_gr_search_form\' ).submit()" style="width: 83px; height: 30px; border: 0; outline: 0; cursor: pointer;background: url(' +  kMAPS.config.image_path + '/finn_kurs.png ) no-repeat;" id="search_city_btn">' +
            '</div>' + 
          '</form>'  +
        '</div>'     +
    '</div>';
  },


  init: function( ) {

    if( ! kMAPS.config.initialized ) {
      var mp = kMAPS.methods.get_map_markup( );
      $( '#' + kMAPS.config.container ).html( mp );    
      kMAPS.config.container = kMAPS.config.map_id;
      kMAPS.config.initialized = true;
    }

    kMAPS.methods.__init();  

    if( kMAPS.map ) {
      google.maps.event.addListener( kMAPS.map, 'bounds_changed', kMAPS.methods.boundsChanged );
    }  
  
  },

  locate_and_run: function( _loc, run_this ) {


    if( !kMAPS.running ) { 
      kMAPS.running = true;
    } else {
      return; 
    }

    var _all_points_from_result = function( result ) {
      var _points = [ ];
      if( result.data.length > 0 ) { 
        for( var i = 0; i < result.data.length; i++ ) {
          _points.push( new google.maps.LatLng( result.data[ i ].lat, result.data[ i ].lon ) );
        }
      }
      return _points;
    };

    var to_geocode_result = function( res, type, status ) {
      if( type == 'geocoder' ) {

        if( status == google.maps.GeocoderStatus.OK ) { 
          var _r = get_correct_result( kMAPS.q, res );
          return {
            center: _r.geometry.location,
            type: type,
            result: _r
          };
        }

      } else if( type == 'local' ) {
        return {
          center: new google.maps.LatLng( res.lat, res.lon ),
          type: type,
          result: null,
          points: _all_points_from_result( res )
        };
      }
    };

    var _google_geocoder = function() {
      var gc = new google.maps.Geocoder();
      var greq = {
        region: 'NO',
        address: kMAPS.q + ' Norway, Europe'
      };

      console.log( greq );

      kMAPS.methods.show_load_screen();
      gc.geocode( greq, function( results, status ) {
        console.log( results );
        console.log( status );
        if( status == google.maps.GeocoderStatus.OK ) { 
          if( typeof run_this == 'function' ) {
            kMAPS.running = false;
            run_this( to_geocode_result( results, 'geocoder', status ) );
            kMAPS.methods.hide_load_screen();
          }
        } else {
          kMAPS.methods.hide_load_screen();
          kMAPS.q = null;
        }
      } );
    };

    var _local_lookup_or_geocode = function( loc ) {

      console.log( "local lookup or geocode: " + loc );

      kMAPS.q = loc;
      $.post( kMAPS.config.locate_center_service_url, { q: loc }, function( data ) {
        try {
          kMAPS.running = false;
          var d = eval( '(' + data + ')' );
          if( d.lat && d.lon ) {

            console.log( "using local lookup" );
            if( typeof run_this == 'function' ) {
              run_this( to_geocode_result( d, 'local', null ) );
            }
          } else {
            console.log( "using geocoder" );
            _google_geocoder();
          }

        } catch( e ) {
          console.log( "error" );
          _google_geocoder();
        }
      } );
    };


    // Convert postcodes to location before doing anything crazy
    if( _loc.match( /^\d{4}$/ ) ) {
      if( parseInt( _loc ) > kMAPS.config.min_post_code ) { 
        $.post( kMAPS.config.postnummer_to_location_service_url, { q: _loc }, function( data ) {
          var d = eval( '(' + data + ')' );
          _local_lookup_or_geocode( d.location || _loc );
        } );
      } else {
        _loc = _loc + ' Oslo';
        _local_lookup_or_geocode( _loc );
      }
    } else {
      _local_lookup_or_geocode( _loc );
    }

  },

  show_load_screen: function() {
    $( '.loader' ).css( 'visibility', 'visible' );
  },

  hide_load_screen: function() {
    $( '.loader' ).css( 'visibility', 'hidden' );
  },

  update_location: function( pos ) {
    // Don't update location if we don't have data for the bounds for this region
    // Use revers geocoding to fix on norway
    var is_norway = function( components ) {
      var _norway = /(Norway|NO|norway)/;  
      //var _norway = /(India|IN|india)/;
      for( var j in components ) {
        if( components[j].long_name.match( _norway ) || components[j].short_name.match( _norway ) ) {
          return true;
        }
      }

      return false;
    };

    var latlon = new google.maps.LatLng( pos.coords.latitude, pos.coords.longitude );
    var gc = new google.maps.Geocoder();
    kMAPS.methods.show_load_screen();
    gc.geocode( {
      latLng: latlon
    }, function( data, status ) {
      kMAPS.methods.hide_load_screen();
      if( status == google.maps.GeocoderStatus.OK ) {
        if( is_norway( data[0].address_components ) ) { 
          if( kMAPS.map ) {
            kMAPS.map.setCenter( new google.maps.LatLng( pos.coords.latitude, pos.coords.longitude ) );
            kMAPS.map.fitBounds( data[0].geometry.viewport );
            /*
              setTimeout(  function() {
                kMAPS.methods.load_data_for_search( kMAPS.map.getBounds().toString() );
              }, 4000 );
            */
          }
        }
      }
    } );
  },

  adjust_filters: function( _data ) {
    var _cl   = { };

    _data.map( function( o ) { 
      if( ! _cl[ o.course_leader_id ] ) {
        _cl[ o.course_leader_id ] = parseInt( o.course_count );
      } else {
        _cl[ o.course_leader_id ] += parseInt( o.course_count );
      }
    });

    var _co   = {
      course_days: { }, 
      course_leaders: _cl, 
      course_types: { }
    };

     try {
     for( var x in _co ) { 
       if( x != 'course_leaders' ) {
        if( typeof _co[x] == 'function' ) continue;
         _data.map( function( o ) { 
           var _frag = o[x].split( ',' );
           for( var j in _frag ) {
             if( typeof _frag[j] == 'function' ) continue;
             if( ! _co[x][ _frag[j] ] ) {
                _co[x][ _frag[j] ] = 1;
             } else {
                _co[x][ _frag[j] ]++;
             }
           }
         } );
       }
     }

     var return_count = function( key, val ) {
       if( _co[key] && _co[key][val] )
         return _co[key][val];
       else
         return 0;
     };

     for( var j in _co ) {
       $( '#' + j ).find( 'input' ).each( function( i, o ) {
         var cnt = return_count( j, o.value ); 
         var elem = $( o ).parents( 'li' );
         elem.css( 'display', ( ( cnt == 0 ) ? 'none' : 'block' ) );
         elem.find( '.cnt' ).html( '(' + cnt + ')' );
       } );
     }
    } catch( e ) { console.log( e ); }
  },

  // _b -> bounds, _a -> actual
  load_markers: function( _data, _adata ) {

    for( var i in _data ) {

    try {

      //console.log( _data[i] );
      // FIXME: A quick hack to test drive #137
      _data[i].course_location_id = Util.to_lat_lon_key( _data[i].lat, _data[i].lon ); 

      var mk = Util.marker_exists( _data[i].course_location_id, _data[i].course_leader_id );

      if( ! mk ) {
      
        //console.log( 'now i come to set the marker up' );
        var lat   = _data[i].lat, 
            lon   = _data[i].lon,
            xpos  = 15,
            ypos  = 30,
            found = false,
            count = 0,
            first = false,
            cx    = Util.get_position_count( _data[i].course_location_id );


        if( cx == 0 ) { 
          first = true; 
          kMAPS.current_position_list[ _data[i].course_location_id ] = { 
            markers: [ ],
            position: null
          };
        } else if( cx == 1 ) {
          // TODO: Replace the first marker with a new one
          var _z = kMAPS.current_position_list[ _data[i].course_location_id ].markers;
          for( var j in _z ) {
            if( _z[j].__smaller === true ) {
              var d = _z[j].__data;
              var iurl = Util.create_marker_image_url( d.course_leader_id, 1 );
              var icon = new google.maps.MarkerImage(  iurl, kMAPS.config.marker_size[ 1 ].size, null, kMAPS.config.marker_size[ 1 ].anchor   );
              _z[j].count = 1;
              _z[j].setIcon( icon );
            }
          } 
        }

        // Rewrite cx now for small image
        cx = ( first ) ? 0 : ( cx + 1 );

        if( lat && lon ) {
          var px = new google.maps.LatLng( lat, lon );
          var c  = kMAPS.map.getCenter();
          var m  = Util.create_marker( {
            position: px, 
            data: _data[i],
            count: cx,
            events: {
              click: kMAPS.methods.markerClick
            }
          } );
  
          if( m === null ) continue;

          with( kMAPS.current_position_list[ _data[i].course_location_id ] ) { 
            markers.push( m );
            position = px;
          }
        }

      }
    } catch( e ) { console.log( e );  }
    }
   
    // Unfuddle: #93 
    // Util.remove_unneeded_markers();
    try {
      _adata && Util.resolve_type_of_markers( _adata );
    } catch( e ) { console.log( e ); }
  },

  publish_results: function( rdata, page ) {
    var res = $( '#search_results' );
    if( page ) {
      res.html( res.html() + rdata ); 
    } else {
      res.html( rdata ); 
    }
  
  },

  load_data_for_bounds: function( bounds ) {

    $.post( kMAPS.config.service_url, { bounds: bounds }, function( data ) {
      var _data = eval( '(' + data + ')' );
      try { 
        kMAPS.methods.load_markers( _data );
      } catch( e ) { console.log( e ); }
    });

  },

  load_data_for_search: function( bounds, page ) {
    // Now collect all elements 
    // console.log( bounds );
    if( kMAPS.loading ) {
      return; 
    }

    console.log( kMAPS.config.inner_rect );
  
    var form_data = { };
    $( '#' + kMAPS.config.search_form + ' input' ).each( function( i, o ) {
      var jo = $( o );
      if( ! form_data[ jo.attr( 'name' ) ] ) {
        if( jo.attr( 'name' ).indexOf( '[' ) !== -1 )  {
          form_data[ jo.attr( 'name' ) ] = [ ];
        } 
      } 

      if( jo.attr('name').indexOf( '[' ) !== -1  ) {
        if( jo.attr( 'checked' ) ) {
          form_data[ jo.attr( 'name' ) ].push( jo.attr( 'value' ) );
        }
      } else {
        form_data[ jo.attr( 'name' ) ] = jo.attr( 'value' );
      }
    } );

    // FIXME: Hack should be in config file
    $( '#c input' ).each( function() {
      var elem = $( this );
      if( elem.attr( 'checked' ) ) {
        form_data[ elem.attr( 'name' ) ] = elem.attr( 'value' ); 
      }
    } );


    if( first_load !== false ) {
      first_load = true;
    }
    
    var c = kMAPS.map.getCenter();
    form_data.bounds = kMAPS.map.getBounds().toString();
    form_data.lat = c.lat();
    form_data.lon = c.lng();

    form_data.q = kMAPS.q ? kMAPS.q : '';

    console.log( "Search String: " + kMAPS.q );

    if( page ) {
      form_data.page = page; 
    }

    kMAPS.methods.show_load_screen();
    kMAPS.loading = true;
    $.post( kMAPS.config.search_service_url, form_data, function( d ) {
      kMAPS.loading = false;
      var _data;

      try {
        _data = eval( '(' + d + ')' );
      } 

      catch( e ) {
        kMAPS.methods.hide_load_screen();
        Util.disable_all_markers();
        kMAPS.methods.publish_results( '', null ); 
        return;
      }

      if( _data.performance ) console.log( _data.performance );
      if( _data.inner_bounds ) console.log( _data.inner_bounds );

      var c = kMAPS.map.getCenter();
      // FIXME: Needs to be in a config
      // Remove old one
      if( _data.error && first_load ) {
        first_load = false;
        kMAPS.methods.publish_results( '', null );

        var p = new google.maps.LatLng( parseFloat( _data.closest_point.lat ), parseFloat( _data.closest_point.lon ) );
        var nb = kMAPS.map.getBounds().extend( p );

        var sw = nb.getSouthWest(),
            ne = nb.getNorthEast(),
            cw = ne.lng() - sw.lng(),
            ch = ne.lat() - sw.lat(),
            cr = Math.abs( cw / ch );

         // Reduce the bounds
         var factor = 0.1;
         var nbx = new google.maps.LatLngBounds(
           new google.maps.LatLng( sw.lat() + factor, sw.lng() + factor ),
           new google.maps.LatLng( ne.lat() - factor, ne.lng() - factor )
         );

         
         kMAPS.map.fitBounds( nb ); 
         // kMAPS.map.setCenter( c );

      } else {
        $( '#more' ).remove();
        if( _data.error ) {
          kMAPS.methods.publish_results( '', null );
        }

        try {


          if( ! page ) { 
            kMAPS.methods.load_markers( _data.mdata, _data.fdata );
          }

          kMAPS.methods.publish_results( _data.rdata, page );

          $( '#more' ).click( kMAPS.methods.next_page_for_search )
          kMAPS.methods.hide_load_screen();

          if( ! page ) {  kMAPS.methods.adjust_filters( _data.mdata ); }

          if( ! kMAPS.config.course_leader_loaded && kMAPS.config.start_course_leader_id != null ) {
            kMAPS.methods.moveToKL( kMAPS.config.start_course_leader_id );
            kMAPS.config.course_leader_loaded = true;
          }


          if( kMAPS.config.inner_rect ) {
            kMAPS.methods.publish_inner_rect( _data.inner_bounds );
          }



        } catch( e ) {
          console.log( e );
        }
      }

    } );

  },

  publish_inner_rect: function( bounds ) {
    var matches = bounds.match( /(?:LineString\(\s*(\-?[0-9\.]+)\s*(\-?[0-9\.]+)\s*,\s*(\-?[0-9\.]+)\s*(\-?[0-9\.]+)\s*\))/ );
    var sw = new google.maps.LatLng( Number( matches[2] ), Number( matches[1] ) ); 
    var ne = new google.maps.LatLng( Number( matches[4] ), Number( matches[3] ) )
    var b  = new google.maps.LatLngBounds( sw, ne );

    new google.maps.Rectangle( { bounds: b, map: kMAPS.map } );
  },


  next_page_for_search: function( e ) {
    e.preventDefault();
    e.stopPropagation();
    
    var target = $( e.target );
    kMAPS.methods.load_data_for_search( null, parseInt( target.attr( 'current_page' ) ) + 1 );
  },


  load_data_carefully: function( f ) {
    var i = ++kMAPS.queue_num;
    setTimeout( function() {
    ( function( i ) {
       if( i != kMAPS.queue_num) return;
       f();
    })( i ); 
    }, 400 );
  },

  boundsChanged: function( ) {

    var bounds = kMAPS.map.getBounds();
    kMAPS.methods.load_data_carefully( function() {
  
       if( first_load != true ) first_load = false;
       kMAPS.methods.load_data_for_bounds( bounds.toString(), null ); 
    } );
  },

  searchBoundsChanged: function( ) {

    var bounds = kMAPS.map.getBounds();
    kMAPS.methods.load_data_carefully( function() {
      if( first_load != true ) first_load = false;
      kMAPS.methods.load_data_for_search( bounds, null );
    } );
  },


  moveToKL: function( course_leader_id ) {
    if( $( 'a[name="' + 'kl-' + course_leader_id + '"]' ).length ) {
      return true;
    } else {
      kMAPS.methods.show_load_screen();
      $.post( kMAPS.config.course_leader_service, { course_leader_id: course_leader_id }, function( d ) {
        kMAPS.methods.hide_load_screen();
        // FIXME: Doesn't use config now, just a quick hack to get it out quickly
        if( d != 'error' ) {
          var ct = $( '#search_results' );
          ct.html( ct.html() + d );
          document.location.href = document.location.href;
        }
      } ); 
    }
  },


  goToKL: function( elem ) {
    var t = $( elem );
    var course_leader_id = t.attr( 'href' ).replace( '#kl-', '' );
    return kMAPS.methods.moveToKL( course_leader_id );
  },
  
  markerClick: function( e ) {
    var marker = this;
    var data = this.__data;

    if( kMAPS.context == 'search' ) {

      var fn = data.first_name || '';
      var ln = data.last_name || '';
      var mn = data.middle_name || '';
      var cn = data.course_names || '';
      var ad = data.address|| '';
      
      var content = '<table style="display:block;"><tr><td align="center" valign="middle"><img style="display:block;" width="60" height="68" src="' + kMAPS.config.kl_base_url + '/' + data.picture_filename + '">' +
      '</td><td align="center" valign="middle"><table style="margin-left: 5px; font-family: Verdana,Arial,Helvetica,sans-serif;"><tr>' +
      '<td style="color: #656565; width:300px;"> <span style="font-size: 15px;">'+ fn + ' ' + mn + ' ' + ln + '</span></td></tr><tr><td>'+ ad + '</td></tr><tr> ' +
      '<td> Antall kurs: '+ data.course_count_in_bounds + ' ( <a style="text-decoration:none; color:#006699;" href="#kl-'+ data.course_leader_id +'" onclick="javascript: kMAPS.methods.goToKL( this )"> Kursoversikt </a> )</td></tr></table></td></tr></table>';

      kMAPS.infowindow.setContent( content ); 
      kMAPS.infowindow.open( kMAPS.map, this );
            
    } else {
      var q = 'c_lat=' + kMAPS.map.getCenter().lat() + 
            '&c_lon=' + kMAPS.map.getCenter().lng() + 
            '&zoom=' + kMAPS.map.getZoom() + 
            '&course_leader_id=' + data.course_leader_id  + 
            '#kl-' + data.course_leader_id;

      document.location.href = kMAPS.config.search_url + '?' + q;
    } 
  } 

};

// FIXME: any more methods this needs refatoring
kMAPS.init = kMAPS.methods.init;
kMAPS.init_search = kMAPS.methods.init_search;


