var config = {
    percent: 0,
    lat: 0,
    lng: 0,
    segX: 14,
    segY: 12,
    isHaloVisible: true,
    isPoleVisible: true,
    autoSpin: true,
    zoom: 0,

    skipPreloaderAnimation: false,

    goToHongKong: function() {
        goTo(22.28552,114.15769);
    }
};

var stats;
var imgs;
var preloader;
var preloadPercent;
var globeDoms;
var vertices;

var world;
var worldBg;
var globe;
var globeContainer;
var globePole;
var globeHalo;

var pixelExpandOffset = 1.5;
var rX = 0;
var rY = 0;
var rZ = 0;
var sinRX;
var sinRY;
var sinRZ;
var cosRX;
var cosRY;
var cosRZ;
var dragX;
var dragY;
var dragLat;
var dragLng;

var isMouseDown = false;
var isTweening = false;
var tick = 1;

var URLS = {
    bg: 'images/css_globe_bg.jpg',
    diffuse: 'images/css_globe_diffuse.jpg',
    halo: 'images/css_globe_halo.png',
};

var transformStyleName = PerspectiveTransform.transformStyleName;

function init(ref) {

    world = document.querySelector('.world');
    worldBg = document.querySelector('.world-bg');
    worldBg.style.backgroundImage = 'url(' + URLS.bg + ')';
    globe = document.querySelector('.world-globe');
    globeContainer = document.querySelector('.world-globe-doms-container');
    globePole = document.querySelector('.world-globe-pole');
    globeHalo = document.querySelector('.world-globe-halo');
    globeHalo.style.backgroundImage = 'url(' + URLS.halo + ')';


    regenerateGlobe();

    var gui = new dat.GUI();
    gui.add(config, 'lat', -90, 90).listen();
    gui.add(config, 'lng', -180, 180).listen();
    gui.add(config, 'isHaloVisible');
    gui.add(config, 'isPoleVisible');
    gui.add(config, 'autoSpin');
    gui.add(config, 'goToHongKong');
    gui.add(config, 'zoom', 0, 1).listen();

    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.left = 0;
    stats.domElement.style.top = 0;
    document.body.appendChild( stats.domElement );

    // events
    world.ondragstart = function () {return false;};
    world.addEventListener('mousedown', onMouseDown);
    world.addEventListener('mousemove', onMouseMove);
    world.addEventListener('mouseup', onMouseUp);
    world.addEventListener('touchstart', touchPass(onMouseDown));
    world.addEventListener('touchmove', touchPass(onMouseMove));
    world.addEventListener('touchend', touchPass(onMouseUp));

    loop();
}

function touchPass(func) {
    return function(evt) {
        evt.preventDefault();
        func.call(this, {pageX: evt.changedTouches[0].pageX, pageY: evt.changedTouches[0].pageY});
    };
}

function onMouseDown(evt) {
    isMouseDown = true;
    dragX = evt.pageX;
    dragY = evt.pageY;
    dragLat = config.lat;
    dragLng = config.lng;
}

function onMouseMove(evt) {
    if(isMouseDown) {
        var dX = evt.pageX - dragX;
        var dY = evt.pageY - dragY;
        config.lat = clamp(dragLat + dY * 0.5, -90, 90);
        config.lng = clampLng(dragLng - dX * 0.5, -180, 180);
    }
}

function onMouseUp(evt) {
    if(isMouseDown) {
        isMouseDown = false;
    }
}

function regenerateGlobe() {
    var dom, domStyle;
    var x, y;
    globeDoms = [];
    while (dom = globeContainer.firstChild) {
        globeContainer.removeChild(dom);
    }

    var segX = config.segX;
    var segY = config.segY;
    var diffuseImgBackgroundStyle = 'url(' + URLS.diffuse + ')';
    var segWidth = 1600 / segX | 0;
    var segHeight = 800 / segY | 0;

    vertices = [];

    var verticesRow;
    var radius = (536) / 2;

    var phiStart = 0;
    var phiLength = Math.PI * 2;

    var thetaStart = 0;
    var thetaLength = Math.PI;

    for ( y = 0; y <= segY; y ++ ) {

        verticesRow = [];

        for ( x = 0; x <= segX; x ++ ) {

            var u = x / segX;
            var v = 0.05 + y / segY * (1 - 0.1);

            var vertex = {
                x: - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ),
                y: -radius * Math.cos( thetaStart + v * thetaLength ),
                z: radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ),
                phi: phiStart + u * phiLength,
                theta: thetaStart + v * thetaLength
            };
            verticesRow.push( vertex );
        }
        vertices.push( verticesRow );
    }

    for ( y = 0; y < segY; ++y ) {
        for ( x = 0; x < segX; ++x ) {
            dom = document.createElement('div');
            domStyle = dom.style;
            domStyle.position = 'absolute';
            domStyle.width = segWidth + 'px';
            domStyle.height = segHeight + 'px';
            domStyle.overflow = 'hidden';
            domStyle[PerspectiveTransform.transformOriginStyleName] = '0 0';
            domStyle.backgroundImage = diffuseImgBackgroundStyle;
            dom.perspectiveTransform = new PerspectiveTransform(dom , segWidth, segHeight);
            dom.topLeft = vertices[ y ][ x ];
            dom.topRight = vertices[ y ][ x + 1];
            dom.bottomLeft = vertices[ y + 1 ][ x ];
            dom.bottomRight = vertices[ y + 1 ][ x + 1 ];
            domStyle.backgroundPosition = (-segWidth * x) + 'px ' + (-segHeight * y) + 'px';
            globeContainer.appendChild(dom);
            globeDoms.push(dom);
        }
    }

}

function loop() {
    requestAnimationFrame(loop);
    stats.begin();
    render();
    stats.end();
}

function render() {

    if(config.autoSpin && !isMouseDown && !isTweening) {
        config.lng = clampLng(config.lng - 0.2);
    }

    rX = config.lat / 180 * Math. PI;
    rY = (clampLng(config.lng)  - 270) / 180 * Math. PI;

    globePole.style.display = config.isPoleVisible ? 'block' : 'none';
    globeHalo.style.display = config.isHaloVisible ? 'block' : 'none';

    var ratio = Math.pow(config.zoom, 1.5);
    pixelExpandOffset = 1.5 + (ratio) * -1.25;
    ratio = 1 + ratio * 3;
    globe.style[transformStyleName] = 'scale3d(' + ratio + ',' + ratio + ',1)';
    ratio = 1 + Math.pow(config.zoom, 3) * 0.3;
    worldBg.style[transformStyleName] = 'scale3d(' + ratio + ',' + ratio + ',1)';

    transformGlobe();
}

function clamp(x, min, max) {
    return x < min ? min : x > max ? max : x;
}

function clampLng(lng) {
    return ((lng + 180) % 360) - 180;
}

function transformGlobe() {

    var dom, perspectiveTransform;
    var x, y, v1, v2, v3, v4, vertex, verticesRow, i, len;
    if(tick ^= 1) {
        sinRY = Math.sin(rY);
        sinRX = Math.sin(-rX);
        sinRZ = Math.sin(rZ);
        cosRY = Math.cos(rY);
        cosRX = Math.cos(-rX);
        cosRZ = Math.cos(rZ);

        var segX = config.segX;
        var segY = config.segY;

        for ( y = 0; y <= segY; y ++ ) {
            verticesRow = vertices[y];
            for ( x = 0; x <= segX; x ++ ) {
                rotate(vertex = verticesRow[x], vertex.x, vertex.y, vertex.z);
            }
        }

        for ( y = 0; y < segY; y ++ ) {
            for ( x = 0; x < segX; x ++ ) {
                dom = globeDoms[x + segX * y];

                v1 = dom.topLeft;
                v2 = dom.topRight;
                v3 = dom.bottomLeft;
                v4 = dom.bottomRight;

                expand(v1, v2);
                expand(v2, v3);
                expand(v3, v4);
                expand(v4, v1);

                perspectiveTransform = dom.perspectiveTransform;
                perspectiveTransform.topLeft.x = v1.tx;
                perspectiveTransform.topLeft.y = v1.ty;
                perspectiveTransform.topRight.x = v2.tx;
                perspectiveTransform.topRight.y = v2.ty;
                perspectiveTransform.bottomLeft.x = v3.tx;
                perspectiveTransform.bottomLeft.y = v3.ty;
                perspectiveTransform.bottomRight.x = v4.tx;
                perspectiveTransform.bottomRight.y = v4.ty;
                perspectiveTransform.hasError = perspectiveTransform.checkError();

                if(!(perspectiveTransform.hasError = perspectiveTransform.checkError())) {
                    perspectiveTransform.calc();
                }
            }
        }
    } else {
        for ( i = 0, len = globeDoms.length; i < len; i ++ ) {
            perspectiveTransform = globeDoms[i].perspectiveTransform;
            if(!perspectiveTransform.hasError) {
                perspectiveTransform.update();
            } else {
                perspectiveTransform.style[transformStyleName] = 'translate3d(-8192px, 0, 0)';
            }
        }
    }
}

function goTo(lat, lng) {
    var dX = lat - config.lat;
    var dY = lng - config.lng;
    var roughDistance = Math.sqrt(dX * dX + dY * dY);
    isTweening = true;
    TweenMax.to(config, roughDistance * 0.01, {lat: lat, lng: lng, ease:'easeInOutSine'});
    TweenMax.to(config, 1, {delay: roughDistance * 0.01, zoom: 1, ease:'easeInOutSine', onComplete: function(){
        isTweening = false;
    }});
}

function rotate(vertex, x, y, z) {
    x0 = x * cosRY - z * sinRY;
    z0 = z * cosRY + x * sinRY;
    y0 = y * cosRX - z0 * sinRX;
    z0 = z0 * cosRX + y * sinRX;

    var offset = 1 + (z0 / 4000);
    x1 = x0 * cosRZ - y0 * sinRZ;
    y0 = y0 * cosRZ + x0 * sinRZ;

    vertex.px = x1 * offset;
    vertex.py = y0 * offset;
}

// shameless stole and edited from threejs CanvasRenderer
function expand( v1, v2 ) {

    var x = v2.px - v1.px, y = v2.py - v1.py,
    det = x * x + y * y, idet;

    if ( det === 0 ) {
        v1.tx = v1.px;
        v1.ty = v1.py;
        v2.tx = v2.px;
        v2.ty = v2.py;
        return;
    }

    idet = pixelExpandOffset / Math.sqrt( det );

    x *= idet; y *= idet;

    v2.tx = v2.px + x;
    v2.ty = v2.py + y;
    v1.tx = v1.px - x;
    v1.ty = v1.py - y;

}

init();