// 
// Google Big Maps v2
// Copyright (C) Oliver Azeau
// http://barrejadis.azeau.com/
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ==UserScript==
// @name           Google Big Maps v2
// @namespace      azeau.com
// @description    Generates a big map by selecting top-left and bottom-right coordinates
// @include        http://maps.google.*/*
// ==/UserScript==

//=== image searching : base class of all searches ===

function ImageSearcher(urlPattern)
{
  this.urlPattern = urlPattern;
  this.host = 0;
  this.n = 0;
  this.v = 0;

  this.GoThroughImages = function()
  {
    var params;
    var r = new RegExp();
    r.compile(this.urlPattern);
    var allImages = document.getElementsByTagName('img');
    for (var i = 0; i < allImages.length; i++) {
      var src = allImages[i].src;
      params = r.exec(src);
      if( params == null )
        continue;
      if( !this.Update(params) )
        continue;
      this.host = params[1];
      this.n = params[2];
      this.v = params[3];
    }
    this.GMSetValues();
  };
}

//=== map searching ===

function MapSearcher()
{
  this.zoom = 999;
  
  this.Update = function(params)
  {
    if( this.zoom !=999 && this.zoom != params[6] ) // skip miniature images
      return false;
    this.zoom = params[6];
    this.UpdateXY(params[4],params[5]);
    return true;
  };
  
  this.GMSetValues = function()
  {
    GM_setValue('maphost', this.host);
    GM_setValue('mapn', this.n);
    GM_setValue('mapv', this.v);
    GM_setValue('mapzoom', this.zoom);
  };
}
MapSearcher.prototype = new ImageSearcher('(http://mt.*\.google\..*/)mt.n=(.*)&v=(.*)&x=(.*)&y=(.*)&zoom=(.*)');

function MapTopLeftSearcher()
{
  this.x = 10000000;
  this.y = 10000000;
  
  this.UpdateXY = function(x,y)
  {
    if( x < this.x )
      this.x = x;
    if( y < this.y )
      this.y = y;
  };
  
  this.ExecuteSearch = function()
  {
    this.GoThroughImages();
    GM_setValue('minx', this.x);
    GM_setValue('miny', this.y);
  }
}
MapTopLeftSearcher.prototype = new MapSearcher();

function MapBottomRightSearcher()
{
  this.x = -10000000;
  this.y = -10000000;
  
  this.UpdateXY = function(x,y)
  {
    if( x > this.x )
      this.x = x;
    if( y > this.y )
      this.y = y;
  };
  
  this.ExecuteSearch = function()
  {
    this.GoThroughImages();
    GM_setValue('maxx', this.x);
    GM_setValue('maxy', this.y);
  }
}
MapBottomRightSearcher.prototype = new MapSearcher();

//=== satellite coordinates ===

function SatelliteCoords() {
  var moveTopLeft = new Array();
  moveTopLeft['qq'] = 'q';
  moveTopLeft['qr'] = 'q';
  moveTopLeft['qs'] = 'q';
  moveTopLeft['qt'] = 'q';
  moveTopLeft['rq'] = 'q';
  moveTopLeft['rr'] = 'r';
  moveTopLeft['rs'] = 'r';
  moveTopLeft['rt'] = 'q';
  moveTopLeft['sq'] = 'q';
  moveTopLeft['sr'] = 'r';
  moveTopLeft['ss'] = 's';
  moveTopLeft['st'] = 't';
  moveTopLeft['tq'] = 'q';
  moveTopLeft['tr'] = 'q';
  moveTopLeft['ts'] = 't';
  moveTopLeft['tt'] = 't';

  var moveBottomRight = new Array();
  moveBottomRight['qq'] = 'q';
  moveBottomRight['qr'] = 'r';
  moveBottomRight['qs'] = 's';
  moveBottomRight['qt'] = 't';
  moveBottomRight['rq'] = 'r';
  moveBottomRight['rr'] = 'r';
  moveBottomRight['rs'] = 's';
  moveBottomRight['rt'] = 's';
  moveBottomRight['sq'] = 's';
  moveBottomRight['sr'] = 's';
  moveBottomRight['ss'] = 's';
  moveBottomRight['st'] = 's';
  moveBottomRight['tq'] = 't';
  moveBottomRight['tr'] = 's';
  moveBottomRight['ts'] = 's';
  moveBottomRight['tt'] = 't';

  function Extreme(a,b,t1,t2) {
    var extreme = '';
    for( var i=0; i<t1.length; i++ ) {
      var c = a[t1.charAt(i)+t2.charAt(i)];
      extreme = extreme + c;
      if( t1.charAt(i) == c && t2.charAt(i) != c )
        return extreme + t1.substr(i+1);
      if( t2.charAt(i) == c && t1.charAt(i) != c )
        return extreme + t2.substr(i+1);
      if( c != t1.charAt(i) && c != t2.charAt(i) )
        a = b;
    }
    return extreme;
  }

  this.FindTopLeft = function(t1,t2) {
    return Extreme(moveTopLeft,moveBottomRight,t1,t2);
  }

  this.FindBottomRight = function(t1,t2) {
    return Extreme(moveBottomRight,moveTopLeft,t1,t2);
  }

  var crosser = new Array();
  crosser['qq'] = 'q';
  crosser['qr'] = 'r';
  crosser['qs'] = 'r';
  crosser['qt'] = 'q';
  crosser['rq'] = 'q';
  crosser['rr'] = 'r';
  crosser['rs'] = 'r';
  crosser['rt'] = 'q';
  crosser['sq'] = 't';
  crosser['sr'] = 's';
  crosser['ss'] = 's';
  crosser['st'] = 't';
  crosser['tq'] = 't';
  crosser['tr'] = 's';
  crosser['ts'] = 's';
  crosser['tt'] = 't';

  this.FindTopRight = function(topLeft,bottomRight) {
    var topRight = '';
    for( var i=0; i<topLeft.length; i++ )
      topRight = topRight + crosser[topLeft.charAt(i)+bottomRight.charAt(i)];
    return topRight;
  }

  this.FindBottomLeft = function(topLeft,bottomRight) {
    var bottomLeft = '';
    for( var i=0; i<topLeft.length; i++ )
      bottomLeft = bottomLeft + crosser[bottomRight.charAt(i)+topLeft.charAt(i)];
    return bottomLeft;
  }

  function StepUpImpl(t) {
    if(t.length < 2) return t;
    var lastChar = t.charAt(t.length-1);
    if( lastChar == 't' ) return t.substr(0,t.length-1)+'q';
    if( lastChar == 's' ) return t.substr(0,t.length-1)+'r';
    if( lastChar == 'r' ) return StepUpImpl(t.substr(0,t.length-1))+'s';
    if( lastChar == 'q' ) return StepUpImpl(t.substr(0,t.length-1))+'t';
  }

  this.StepUp = function(t) {
    return StepUpImpl(t);
  }

  function StepDownImpl(t) {
    if(t.length < 2) return t;
    var lastChar = t.charAt(t.length-1);
    if( lastChar == 'q' ) return t.substr(0,t.length-1)+'t';
    if( lastChar == 'r' ) return t.substr(0,t.length-1)+'s';
    if( lastChar == 's' ) return StepDownImpl(t.substr(0,t.length-1))+'r';
    if( lastChar == 't' ) return StepDownImpl(t.substr(0,t.length-1))+'q';
  }

  this.StepDown = function(t) {
    return StepDownImpl(t);
  }

  function StepLeftImpl(t) {
    if(t.length < 2) return t;
    var lastChar = t.charAt(t.length-1);
    if( lastChar == 'r' ) return t.substr(0,t.length-1)+'q';
    if( lastChar == 'q' ) return StepLeftImpl(t.substr(0,t.length-1))+'r';
    if( lastChar == 't' ) return StepLeftImpl(t.substr(0,t.length-1))+'s';
    if( lastChar == 's' ) return t.substr(0,t.length-1)+'t';
  }

  this.StepLeft = function(t) {
    return StepLeftImpl(t);
  }

  function StepRightImpl(t) {
    if(t.length < 2) return t;
    var lastChar = t.charAt(t.length-1);
    if( lastChar == 'q' ) return t.substr(0,t.length-1)+'r';
    if( lastChar == 'r' ) return StepRightImpl(t.substr(0,t.length-1))+'q';
    if( lastChar == 's' ) return StepRightImpl(t.substr(0,t.length-1))+'t';
    if( lastChar == 't' ) return t.substr(0,t.length-1)+'s';
  }

  this.StepRight = function(t) {
    return StepRightImpl(t);
  }

  this.Distance = function(direction,t1,t2) {
    var d = 0;
    while( t1 != t2 ) {
      t1 = direction(t1);
      d++;
    }
    return d;
  }
}

//=== satellite searching ===

function SatelliteSearcher()
{
  this.t = '';
  
  this.Update = function(params)
  {
    var t = params[4]
    if( this.t.length == 0 ) {
      this.t = t;
      return true;
    }
    if( t.length != this.t.length ) // skip miniature images
      return false;
    this.t = this.UpdateT(this.t,t);
    return true;
  };
  
  this.GMSetValues = function()
  {
    GM_setValue('satellitehost', this.host);
    GM_setValue('satelliten', this.n);
    GM_setValue('satellitev', this.v);
  };
}
SatelliteSearcher.prototype = new ImageSearcher('(http://kh.*\.google\..*/)kh.n=(.*)&v=(.*)&t=(.*)');

function SatelliteTopLeftSearcher()
{
  var coords = new SatelliteCoords();
  
  this.UpdateT = function(t1,t2)
  {
    return coords.FindTopLeft(t1,t2);
  }
  
  this.ExecuteSearch = function()
  {
    this.GoThroughImages();
    GM_setValue('mint', this.t);
  }
}
SatelliteTopLeftSearcher.prototype = new SatelliteSearcher();

function SatelliteBottomRightSearcher()
{
  var coords = new SatelliteCoords();
  
  this.UpdateT = function(t1,t2)
  {
    return coords.FindBottomRight(t1,t2);
  }
  
  this.ExecuteSearch = function()
  {
    this.GoThroughImages();
    GM_setValue('maxt', this.t);
  }
}
SatelliteBottomRightSearcher.prototype = new SatelliteSearcher();

//=== result document generation ===
function ComputeHTML(host,n,v,zoom,shost,sn,sv)
{
  this.yBase = 70;
  this.host = host;
  this.n = n;
  this.v = v;
  this.zoom = zoom;
  this.shost = shost;
  this.sn = sn;
  this.sv = sv;
  this.gWidth = 0;
  this.gHeight = 0;
  
  this.Map = function(minx,miny,maxx,maxy,displayFactor)
  {
    var html = '';
    if( this.host == 0 )
      return html;
    var side = 256/displayFactor;
    for(var x=minx; x<=maxx; x++) {
      for(var y=miny; y<=maxy; y++) {
        html = html + '<img src="'+this.host+'mt?n='+this.n+'&v='+this.v+'&x='+x+'&y='+y+'&zoom='+this.zoom+'" style="position: absolute; left: '+(x-minx)*side+'px; top: '+((y-miny)*side+this.yBase)+'px; width: '+side+'px; height: '+side+'px;"/>';
      }
    }
    this.gWidth = (maxx-minx+1)*side;
    this.gHeight = (maxy-miny+1)*side;
    return html;
  }
  
  this.Satellite = function(topLeft,bottomRight,displayFactor)
  {
    var html = '';
    if( this.shost == 0 )
      return html;
    var side = 256/displayFactor;
    var coords = new SatelliteCoords();
    var topRight = coords.FindTopRight(topLeft,bottomRight);
    var distanceRight = coords.Distance(coords.StepRight,topLeft,topRight);
    var distanceDown = coords.Distance(coords.StepDown,topRight,bottomRight);
    var tBase = topLeft;
    for(var x=0; x <= distanceRight; x++) {
      var tCurrent = tBase;
      for(var y=0; y <= distanceDown; y++) {
        html = html + '<img src="'+this.shost+'kh?n='+this.sn+'&v='+this.sv+'&t='+tCurrent+'" style="position: absolute; left: '+x*side+'px; top: '+(y*side+this.yBase)+'px; width: '+side+'px; height: '+side+'px;"/>';
        tCurrent = coords.StepDown(tCurrent);
      }
      tBase = coords.StepRight(tBase);
    }
    this.gWidth = (distanceRight+1)*side;
    this.gHeight = (distanceDown+1)*side;
    return html;
  }
  
  this.CopyImageToCanvas = function(theCanvas,displayFactor) {
    var side = 256/displayFactor;
    var context = theCanvas.getContext('2d');
    netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
    context.drawWindow(window, 0, this.yBase, (maxx-minx+1)*side, (maxy-miny+1)*side, "rgb(0,0,0)");
  }

}

//=== buttons callbacks ===
function StoreTopLeft(event)
{
  var mapSearchMin = new MapTopLeftSearcher();
  mapSearchMin.ExecuteSearch();
  var satelliteSearchMin = new SatelliteTopLeftSearcher();
  satelliteSearchMin.ExecuteSearch();
}

function StoreBottomRight(event)
{
  var mapSearchMax = new MapBottomRightSearcher();
  mapSearchMax.ExecuteSearch();
  var satelliteSearchMax = new SatelliteBottomRightSearcher();
  satelliteSearchMax.ExecuteSearch();
}

function Refresh()
{
  var displayFactorSelector = document.getElementById("displayFactor");
  var displayFactor = displayFactorSelector.options[displayFactorSelector.selectedIndex].label;
  var picture = document.getElementById("picture");
  picture.innerHTML = computeHTML.Satellite(topLeft,bottomRight,displayFactor)+computeHTML.Map(minx,miny,maxx,maxy,displayFactor);
  picture.style.width = computeHTML.gWidth;
  picture.style.height = computeHTML.gHeight;
}
function ResizeTopPlus() { miny--; topLeft=coords.StepUp(topLeft);Refresh(); }
function ResizeTopMinus() { miny++; topLeft=coords.StepDown(topLeft);Refresh(); }
function ResizeLeftPlus() { minx--; topLeft=coords.StepLeft(topLeft);Refresh(); }
function ResizeLeftMinus() { minx++; topLeft=coords.StepRight(topLeft);Refresh(); }
function ResizeBottomPlus() { maxy++; bottomRight=coords.StepDown(bottomRight);Refresh(); }
function ResizeBottomMinus() { maxy--; bottomRight=coords.StepUp(bottomRight);Refresh(); }
function ResizeRightPlus() { maxx++; bottomRight=coords.StepRight(bottomRight);Refresh(); }
function ResizeRightMinus() { maxx--; bottomRight=coords.StepLeft(bottomRight);Refresh(); }
function GenerateImage() {
  var canvas = document.getElementsByTagName('canvas')[0];
  computeHTML.CopyImageToCanvas(canvas,displayFactor);
  window.open(canvas.toDataURL(), "_blank");
}
function ClosePanel() {
  document.getElementById('controlPanel').style.display = 'none';
  computeHTML.yBase = 0;
  Refresh();
}

function GetDocument(event)
{
  var host = GM_getValue('maphost');
  var n = GM_getValue('mapn');
  var v = GM_getValue('mapv');
  var minx = GM_getValue('minx');
  var miny = GM_getValue('miny');
  var maxx = GM_getValue('maxx');
  var maxy = GM_getValue('maxy');
  var zoom = GM_getValue('mapzoom');
  var shost = GM_getValue('satellitehost');
  var sn = GM_getValue('satelliten');
  var sv = GM_getValue('satellitev');
  var topLeft = GM_getValue('mint');
  var bottomRight = GM_getValue('maxt');
  var newwindow = window.open("about:blank", "_blank");
  var ndoc = newwindow.document;
  var plusImg = '<img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%02%00%00%00%90%91h6%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00%07tIME%07%D7%09%10%11%1B%03%B3%3D%E0%BE%00%00%007IDATx%DAc%FC%FF%FF%3F%03)%80%89%81D%80%5D%03%23%23%23%23%23%23-m%18P%0D%8C%90%60%C5%E5Ed%00QI%AE%0D%98%C1%0A7r%F8%06%2B%0Dm%00%00%F1%C4%12%17%AAXq)%00%00%00%00IEND%AEB%60%82"/>';
  var minusImg = '<img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%02%00%00%00%90%91h6%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00%07tIME%07%D7%09%10%11%1D%2F%D7%BF%2B%DB%00%00%00(IDATx%DAc%FC%FF%FF%3F%03)%80%89%81D0%225%B0%A0%F1%19%19%19%D1D%D0%C2%9Dd%1B%18G%23%8E%16%1A%00%15F%09%1B%03%F3%069%00%00%00%00IEND%AEB%60%82"/>';
  ndoc.write('<html><head><title>Google Big Map</title><script>'+SatelliteCoords+ComputeHTML+'var computeHTML = new ComputeHTML("'+host+'","'+n+'","'+v+'","'+zoom+'","'+shost+'","'+sn+'","'+sv+'"); var minx='+minx+'; var miny='+miny+'; var maxx='+maxx+'; var maxy='+maxy+'; var topLeft="'+topLeft+'"; var bottomRight="'+bottomRight+'"; var coords = new SatelliteCoords();'+Refresh+ResizeTopPlus+ResizeTopMinus+ResizeLeftPlus+ResizeLeftMinus+ResizeBottomPlus+ResizeBottomMinus+ResizeRightPlus+ResizeRightMinus+GenerateImage+ClosePanel+'</script></head><body><div id="controlPanel"><select id="displayFactor" onChange="javascript:Refresh();"><option label="1">100%</option><option label="2">50%</option><option label="4" selected=selected">25%</option><option label="8">12.5%</option><option label="16">6.25%</option></select><div style="position: absolute; left: 200px; top: 0px;"><a onClick="javascript:ResizeTopPlus();">'+plusImg+'</a><a onClick="javascript:ResizeTopMinus();">'+minusImg+'</a></div><div style="position: absolute; left: 160px; top: 20px;"><a onClick="javascript:ResizeLeftPlus();">'+plusImg+'</a><a onClick="javascript:ResizeLeftMinus();">'+minusImg+'</a></div><div style="position: absolute; left: 200px; top: 40px;"><a onClick="javascript:ResizeBottomPlus();">'+plusImg+'</a><a onClick="javascript:ResizeBottomMinus();">'+minusImg+'</a></div><div style="position: absolute; left: 240px; top: 20px;"><a onClick="javascript:ResizeRightPlus();">'+plusImg+'</a><a onClick="javascript:ResizeRightMinus();">'+minusImg+'</a></div><a style="position: absolute; left: 100px; top: 10px;" onClick="javascript:ClosePanel();"><img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%02%00%00%00%90%91h6%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00%08tEXtComment%00%F6%CC%96%BF%00%00%00%2BIDAT(%CFc%D4s%D9%CC%40%0A%60b%20%11%D0%5E%03%0B2%E7%E2n%1F%AC%8A%F4%5D%B7%0Cf%3F%0C%07%0D%8C%C3%20-%01%002%CF%05k%84%82%07%05%00%00%00%00IEND%AEB%60%82"></a><div style="display: none;"><canvas>empty</canvas></div></div><div id="picture">');
  
  ch = new ComputeHTML(host,n,v,zoom,shost,sn,sv);
  ndoc.write(ch.Satellite(topLeft,bottomRight,4));
  ndoc.write(ch.Map(minx,miny,maxx,maxy,4));
  
  ndoc.write('</div></body>');
  ndoc.close();
}

// initialize button bar
var paneltabs = document.getElementById('paneltabs');
var theRow = paneltabs.childNodes[0].childNodes[0];
var theCell = theRow.childNodes[theRow.childNodes.length-1];
var buttonBar = document.createElement('span');
buttonBar.innerHTML = '<a><img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%02%00%00%00%90%91h6%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00%08tEXtComment%00%F6%CC%96%BF%00%00%00*IDAT(%CFc%D4s%D9%CC%40%08%5C%DC%ED%03g31%90%08h%AF%81%05%97%5B%07%CEI%23R%03%E3%FF%FF%FF%87%BA%1F%00%7D%CA%07%F3%CA%05)%94%00%00%00%00IEND%AEB%60%82"/></a><a><img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%02%00%00%00%90%91h6%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00%08tEXtComment%00%F6%CC%96%BF%00%00%00%2BIDAT(%CFc%D4s%D9%CC%40%0A%60b%20%11%D0%5E%03%0B2%E7%E2n%1F%AC%8A%F4%5D%B7%0Cf%3F%0C%07%0D%8C%C3%20-%01%002%CF%05k%84%82%07%05%00%00%00%00IEND%AEB%60%82"/></a><a><img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%02%00%00%00%90%91h6%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00%08tEXtComment%00%F6%CC%96%BF%00%00%00(IDAT(%CFc%FC%FF%FF%3F%03)%80%89%81D%40%7B%0D%2C%C4(%D2w%DD2%98%FD0%1C4%B0%E0%0A%EF!%E4%07%00%A9%10%05q%EB%3D%C8%13%00%00%00%00IEND%AEB%60%82"/></a>';
theCell.appendChild(buttonBar);
buttonBar.style.marginLeft = '10px';

// attach callbacks
var topLeftLink = buttonBar.childNodes[0];
topLeftLink.addEventListener('click', StoreTopLeft, true );
var docLink = buttonBar.childNodes[1];
docLink.addEventListener('click', GetDocument, true );
var bottomRightLink = buttonBar.childNodes[2];
bottomRightLink.addEventListener('click', StoreBottomRight, true );
