
/**
 *  @namespace EarthGL namespace 
 */

var EarthGL = {};

/**
 * @namespace utility functions for EarthGL 
 */

EarthGL.Util = {};

// EarthGL.Util.extend is from OpenLayers
EarthGL.Util.extend = function(destination, source) {
    destination = destination || {};
    if(source) {
        for(var property in source) {
            var value = source[property];
            if(value !== undefined) {
                destination[property] = value;
            }
        }

        /**
         * IE doesn't include the toString property when iterating over an object's
         * properties with the for(property in object) syntax.  Explicitly check if
         * the source has its own toString property.
         */

        /*
         * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
         * prototype object" when calling hawOwnProperty if the source object
         * is an instance of window.Event.
         */

        var sourceIsEvt = typeof window.Event == "function"
                          && source instanceof window.Event;

        if(!sourceIsEvt
           && source.hasOwnProperty && source.hasOwnProperty('toString')) {
            destination.toString = source.toString;
        }
    }
    return destination;
};

EarthGL.Util.merge = function(source,options) {
    // overwrite default options
    if (options) {
	for (var p in options) {
	    source[p] = options[p];
	}
    }

    return source;
};



// return GET parameter string from the hash table param

EarthGL.Util.encodeparam = function(param) {                
    var parameters = "";

    for (var name in param) {
        parameters += (parameters.length !==0 ? "&" : "") + name + "=" + encodeURIComponent(param[name]);        
    }

    return parameters;
};


EarthGL.Util.append_param = function(url,vars) {

    if (typeof vars == 'object') {
	vars = EarthGL.Util.encodeparam(vars);
    }

    if (url.indexOf('?') == -1) {
	return url + '?' + vars;
    }
    else {
	return url + '&' + vars;
    }

};


EarthGL.Util.encodesec = function(slon,slat) {
    var section = "";
    for (var i=0; i < slon.length; i++) {
	section += slon[i] + "," + slat[i] + "|";
    }

    section = section.substring(0,section.length-1);
    return section;
}

EarthGL.Util.deencodesec = function(url_sec) {
    var coord = url_sec.split('%7C');

    var slon = [];
    var slat = [];

    for (i = 0; i < coord.length; i++) {
	var v = coord[i].split('%2C');
	slon.push(parseFloat(v[0]));
	slat.push(parseFloat(v[1]));
    }
    return {lon: slon, lat: slat};
}

EarthGL.Util.cross = function(y, z) {
    // X vector = Y cross Z
    var x = [];
    x[0] =  y[1] * z[2] - y[2] * z[1];
    x[1] = -y[0] * z[2] + y[2] * z[0];
    x[2] =  y[0] * z[1] - y[1] * z[0];
    return x;
};

EarthGL.Util.norm = function(x) {
    var mag = Math.sqrt(x[0]*x[0] + x[1]*x[1] + x[2]*x[2]);
    if (mag) {
        x[0] /= mag;
        x[1] /= mag;
        x[2] /= mag;
    }

    return x;
};

// check if x is inside any interval [x0,x1], [x0,x1] + p, [x0,x1] + 2*p, ...
EarthGL.Util.within =  function(x0,x1,p,x) {
    // bring Y to the interval [x0,x0+p]
    var Y  = (x - x0) % p + x0;

    return x0 <= Y && Y <= x1;
};


// check if [y0,y1] is inside any interval [x0,x1], [x0,x1] + p, [x0,x1] + 2*p, ...
EarthGL.Util.withininter =  function(x0,x1,p,y0,y1) {
    // bring Y0 to the interval [x0,x0+p]
    var Y0 = (y0 - x0) % p + x0;
    // apply the same shit to Y1
    var Y1 = y1 - y0 + Y0;

    // both end points Y0 and Y1 have to be inside [x0,x1]
    return (x0 <= Y0 && Y0 <= x1) && (x0 <= Y1 && Y1 <= x1);
};

EarthGL.Util.coord = function(lon,lat,r) {
    var theta = (90-lat) * Math.PI/180;
    var phi = (-lon) * Math.PI/180;
    var sinTheta = Math.sin(theta);
    var sinPhi = Math.sin(phi);
    var cosTheta = Math.cos(theta);
    var cosPhi = Math.cos(phi);
            
    var x = r*sinPhi * sinTheta;
    var y = r*cosPhi * sinTheta;
    var z = r*cosTheta;
    return [x,y,z];
};

EarthGL.Util.distance = function(lon0,lat0,lon1,lat1) {
    var lat = (lat0+lat1) * Math.PI/180/2;

    var dx = (lon1 - lon0) * Math.cos(lat);
    var dy = (lat1 - lat0);

    var ds = Math.sqrt(dx*dx + dy*dy);
    return ds;
};

EarthGL.Util.refine_lin = function(x,y,maxdx) {

    var xf = [];
    var yf = [];

    var j = 0;

    for (var i=0; i < x.length-1; i++) {
	var ds = EarthGL.Util.distance(x[i],y[i],x[i+1],y[i+1]);

	var n = Math.max(1,Math.ceil(ds/maxdx));
	var dx = (x[i+1]-x[i])/(n);
	var dy = (y[i+1]-y[i])/(n);

	for (j = 0; j<n; j++) {
	    xf.push(x[i] + dx*j);
	    yf.push(y[i] + dy*j);
	}

	//console.log('n ' + n);
    }


    xf.push(x[i]);
    yf.push(y[i]);

    return {x:xf, y:yf};
    
};


EarthGL.Util.secdistance = function(x,y) {

    var s = [0];

    for (var i=0; i < x.length-1; i++) {
	var ds = EarthGL.Util.distance(x[i],y[i],x[i+1],y[i+1]);
	s[i+1] = s[i] + ds;
    }


    return s;
    
};


EarthGL.Util.loadShader = function(gl, glscript,type)
{
       
    var shaderType = type;

    // Create the shader object
    var shader = gl.createShader(shaderType);
    if (shader == null) {
        gl.console.log("*** Error: unable to create shader ");       
        return null;
    }

    // Load the shader source
    gl.shaderSource(shader, glscript);

    // Compile the shader
    gl.compileShader(shader);

    // Check the compile status
    var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
        // Something went wrong during compilation; get the error
        var error = gl.getShaderInfoLog(shader);
        gl.console.log("*** Error compiling shader " +error);
        gl.deleteShader(shader);
        return null;
    }

    return shader;
};

// from utils3d.js

EarthGL.Util.createProgram = function(gl,vshaders,fshaders,attribs) {

    // create our shaders
    var vertexShader;
    var fragmentShader;

    vertexShader = EarthGL.Util.loadShader(gl, vshaders,gl.VERTEX_SHADER);
    fragmentShader = EarthGL.Util.loadShader(gl, fshaders,gl.FRAGMENT_SHADER);

    if (!vertexShader || !fragmentShader) {
        return null;
    }

    // Create the program object
    var program = gl.createProgram();

    if (!program) {
        return null;
    }

    // Attach our two shaders to the program
    gl.attachShader (program, vertexShader);
    gl.attachShader (program, fragmentShader);

    // Bind attributes
    for (var i in attribs){
        gl.bindAttribLocation (program, i, attribs[i]);
    }

    // Link the program
    gl.linkProgram(program);

    // Check the link status
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
        // something went wrong with the link
        var error = gl.getProgramInfoLog (program);
        gl.console.log("Error in program linking:"+error);

        gl.deleteProgram(program);
        gl.deleteProgram(fragmentShader);
        gl.deleteProgram(vertexShader);

        return null;
    }

    return program;
};


EarthGL.Util.mergeTextures = function(textureWidth,textureHeight,urls,onready) {
    var textureCanvas = document.createElement("canvas");
    document.body.appendChild(textureCanvas);
    textureCanvas.width = textureWidth;
    textureCanvas.height = textureHeight;
    var textureContext = textureCanvas.getContext("2d");

    var img = [];

    var fun = function(img) {
	return function() {
	    var i;
	    for (i = 0; i < img.length; i++) {
		if (!img[i].complete) {
		    return;
		}
	    }

	    for (i = 0; i < img.length; i++) {
		textureContext.drawImage(img[i],0,0);
	    }
    
  
	    onready(textureCanvas);
	};			
    }(img);

    for (var i = 0; i < urls.length; i++) {
	img[i] = new Image();
	img[i].onload = fun;
	img[i].src = urls[i];
    }
};


// 
// makeSphereEarth
//
EarthGL.Util.makeSphereEarth = function(gl, radius, lats, longs, topo, lon, lat) {
    function hdata(i,j) {
	
	if (topo) {
	    var cyclic = 1;

	    if (cyclic && i == longs) {
		h = topo.data(0,j);
		console.log('i ',i);
	    }
	    else {
		h = topo.data(i,j);
	    }
	}
	else {
	    h = radius;
	}

	return h;
    }

    var i,j;
    var texCoordData = [ ];
    var elevationData = [ ];
    var gradhData = [ ];
    var indexData = [ ];

    var h;
    var dphi2 = 4*Math.PI/longs;
    var dtheta2 = 2*Math.PI/lats;

    var east = 180;
    var west = -180;

    var north = 90;
    var south = -90;

    if (lon) {
	west = lon[0];
	east = lon[1];
    }

    if (lat) {
	south = lat[0];
	north = lat[1];
    }


    for (j = 0; j <= lats; ++j) {
        for (i = 0; i <= longs; ++i) {

	    // lon: from -180 to +180
	    // lon = 360*i/longs - 180;
	    var lon = (east - west)*i/longs + west;
	    // lon: from -90 to +90
	    var lat = north - (north-south)*j/lats ;

	    var xx = EarthGL.Util.coord(lon,lat,1);
            var x = xx[0];
            var y = xx[1];
            var z = xx[2];

            var u = i/longs;
            var v = j/lats;

	    // h is height relative to the earth surface

	    h = hdata(i,j);
	    

	    //console.log('h '+ i + ' ' + j + ' ' + h);

	    gradhData.push((hdata(i+1,j)-hdata(i-1,j))/dphi2  );
	    gradhData.push((hdata(i,j+1)-hdata(i,j+1))/dtheta2);


	    elevationData.push(lon);
	    elevationData.push(lat);
	    elevationData.push(h);

            texCoordData.push(u);
            texCoordData.push(v);
        }
    }

    //console.log('i, j ' + i + ' ' + j);
    
    for (j = 0; j < lats; ++j) {
        for (i = 0; i < longs; ++i) {
            var first = (j * (longs+1)) + i;
            var second = first + longs + 1;
            indexData.push(first);
            indexData.push(second);
            indexData.push(first+1);

            indexData.push(second);
            indexData.push(second+1);
            indexData.push(first+1);
        }
    }
    
    var retval = { };
    
    retval.texCoordObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, retval.texCoordObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoordData), gl.STATIC_DRAW);

    retval.elevationObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, retval.elevationObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(elevationData), gl.STATIC_DRAW);

    retval.gradhObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, retval.gradhObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gradhData), gl.STATIC_DRAW);

    retval.numIndices = indexData.length;
    retval.indexObject = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, retval.indexObject);
    
    retval.indexType = gl.UNSIGNED_SHORT;
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), gl.STATIC_DRAW);

    //console.log('largest ',indexData[indexData.length-1]);
    
    /*
    retval.indexType = gl.UNSIGNED_LONG;
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indexData), gl.STATIC_DRAW);
    */
    return retval;
};


EarthGL.Util.makeEarthTex = function(gl, url, topo, onready, lon,lat) {
    var earth = {lon: lon, lat: lat};
    earth.loaded = false;

    var textureUrl = url;
    //console.log('turl  ' + url);
    console.log('makeEarthTex: lon ',lon,' lat ',lat);

    var fun = function(earth,onready,gl,topo) {
	return function() {
	    var earth2 = EarthGL.Util.makeSphereEarth(gl, 1, topo.height-1, topo.width-1, topo, lon,lat);
	    for (var i in earth2) {
		earth[i] = earth2[i];
	    }       	    

	    //console.log('gl ',gl);
	    earth.texture = EarthGL.Util.loadImageTexture(gl, textureUrl,
					      function() { 
						  earth.loaded = true;

						  //console.log('earth loaded' + onready);
						  if (onready) {
						      //console.log('earth loaded');
						      onready();
						  }
					      } );
	

	};
    }(earth,onready,gl,topo);

    var newParams;
    
    if (lon) { 
      newParams = {bbox: lon[0] + ',' + lat[0] + ',' + lon[1] + ',' + lat[1]};
    }

    topo.load(newParams,fun,lon,lat);
    return earth;
};

EarthGL.Util.makeSection = function(gl,lon,lat,h) {
    var v0,v1,v2,v3;
    // vertex coords array
    var vertices = [];
    // normal array
    var normals = [];
    // texCoord array
    var texCoords = [];
    // index array
    var indices = [];

    /*    
    lon = [0,0];
    lat = [0,90];
    h = [-0.00078125,0];
    */

    
    var maxd = 5;
    //var maxd = 80;

    //var sd = EarthGL.Util.secdistance(lon,lat);
    //console.log('maxsd 1 ' + sd[sd.length-1]);

    var fine = EarthGL.Util.refine_lin(lon,lat,maxd);
    lon = fine.x;
    lat = fine.y;
    
    

    var sd = EarthGL.Util.secdistance(lon,lat);
    var maxsd = sd[sd.length-1];

    //console.log('maxsd ' + maxsd);
    //console.log(' lon ' + lon.toString());
    //console.log(' lat ' + lat.toString());
    //console.log('h',h);

    var s0;
    var s1;
    var i = 0;

    for (i = 0; i < lon.length-1; i++) {

	v0 = EarthGL.Util.coord(lon[i],lat[i],1+h[0]);
	v1 = EarthGL.Util.coord(lon[i],lat[i],1+h[1]);

	v2 = EarthGL.Util.coord(lon[i+1],lat[i+1],1+h[0]);
	v3 = EarthGL.Util.coord(lon[i+1],lat[i+1],1+h[1]);
 
	/*
	console.log(' v0 ' + v0.toString());
	console.log(' v1 ' + v1.toString());
	console.log(' v2 ' + v2.toString());
	console.log(' v3 ' + v3.toString());
	*/

	var dx = [];
	var dy = [];

	for (var j=0; j<3; j++) {
	    dx[j] = v2[j] - v0[j];
	    dy[j] = v1[j] - v0[j];
	}

	//alert('dx '+ dx[1]);
	//alert('dy '+ dy[1]);
	//console.log(' dx ' + dx.toString());
	//console.log(' dy ' + dy.toString());

	var n = EarthGL.Util.norm(EarthGL.Util.cross(dx,dy));
	//console.log(' n ' + n.toString());
    
	vertices = vertices.concat(v0,  v1,  v2, v3);
	normals = normals.concat(n, n, n, n);

	s0 = sd[i]/maxsd;
	s1 = sd[i+1]/maxsd;
	texCoords = texCoords.concat([s0,1,   s0,0,   s1,1,   s1,0]);
	indices = indices.concat([4*i, 4*i+1, 4*i+2,   4*i+1,4*i+2,4*i+3]);
    }

    //console.log('sd ' + sd.toString());

    //console.log('texCoords ' + texCoords.toString());
    //console.log('ind ' + indices.toString());
    //console.log('ver ' + vertices.toString());

    var retval = { };
    
    retval.normalObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, retval.normalObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
    
    retval.texCoordObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, retval.texCoordObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

    retval.vertexObject = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, retval.vertexObject);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    
    //gl.bindBuffer(gl.ARRAY_BUFFER, null);

    retval.indexObject = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, retval.indexObject);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
    //gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    
    retval.numIndices = indices.length;

    return retval;
};

/* from utils3d.js
 * loadImageTexture 
 * doLoadImageTexture
 */

/*
 * Copyright (C) 2009 Apple Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 */

EarthGL.Util.loadImageTexture = function(gl, url,cb)
{
    function doLoadImageTexture(gl, image, texture)
    {
	gl.bindTexture(gl.TEXTURE_2D, texture);
	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
	//gl.generateMipmap(gl.TEXTURE_2D);
	gl.bindTexture(gl.TEXTURE_2D, null);

	if (cb) {
	    cb();
	}
    }


    var texture = gl.createTexture();
    texture.image = new Image();
    texture.image.onload = function() { 
	doLoadImageTexture(gl, texture.image, texture); };
    texture.image.src = url;
    return texture;
};



