/*
 * Copyright 2009, Google 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:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * @fileoverview This file contains functions used for creating a beach scene.
 *
 */
o3djs.require('o3djs.util');
o3djs.require('o3djs.math');
o3djs.require('o3djs.rendergraph');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.effect');
o3djs.require('o3djs.dump');
o3djs.require('o3djs.primitives');
o3djs.require('o3djs.loader');

// Events

/**
 * Run the init() function once the page has finished loading.
 */
window.onload = init;

/**
 * Run the unload() function when the page is unloaded.
 */
window.onunload = unload;

// O3D variables
var g_root;
var g_o3dElement;
var g_o3d;
var g_math;
var g_client;
var g_pack = null;
var g_viewInfo;
var g_clientWidth;
var g_clientHeight;
var g_loader;
var g_finished = false;  // for selenium
var g_globalParams;

// Model variables
var g_modelCounter = 0;
var g_modelPaths = [];
var g_modelPacks = [];
var g_modelRoots = [];
var g_modelIndices = [];

// Pack variables
var g_rockPack;
var g_waterPack;
var g_bridgePack;
var g_woodPack;
var g_domePack;
var g_treePack;
var g_sunPack;

// Time variables
var g_frame;
var g_clock = 0.0;
var g_clockParam;
var g_timeMult = 1.0;

// General shader variables
var g_lightDirectionParam;

// Water variables
var g_samplerParam;
var g_swizzle1Param;
var g_deepWaterTextures = [];

// Cameras
var g_cameraOriginal = {
 eye: { x: -2.751, y: 3.529, z: -8.563 },
 target: { x: 0, y: 4, z: -10 },
 up: { x: 0, y: 1, z: 0 }
};
var g_cameraOverhead = {
 eye: { x: 0, y: 120, z: -20 },
 target: { x: 0, y: 2, z: -20 },
 up: { x: 0, y: 0, z: -1 }
};
var g_camera = {
 eye: { x: 0, y: 0, z: 0 },
 target: { x: 0, y: 0, z: 0 },
 up: { x: 0, y: 0, z: 0 }
};

var g_clientZoom = 1.0;

// Initialize the models we'll be using
g_modelPaths[0] = 'assets/rocks.9.o3dtgz';
g_modelPaths[1] = 'assets/lazy_bridge.o3dtgz';

g_modelIndices[0] = 0;
g_modelIndices[1] = 2;

/**
 * Performs the initial creation of the packs and their roots.
 */
function initializePacks() {
  var packNames = [
      'Rock pack',    // 0
      'Tree pack',    // 1
      'Brdige pack',  // 2
      'Wood pack',    // 3
      'Dome pack',    // 4
      'Water pack',   // 5
      'Sun pack'];    // 6

  for (var mp = 0; mp < packNames.length; mp++) {
    g_modelPacks[mp] = g_client.createPack();
    g_modelRoots[mp] = g_modelPacks[mp].createObject('Transform');
    g_modelRoots[mp].parent = g_root;
  }

  // Set some useful variables
  g_rockPack = g_modelPacks[0];
  g_treePack = g_modelPacks[1];
  g_bridgePack = g_modelPacks[2];
  g_woodPack = g_modelPacks[3];
  g_domePack = g_modelPacks[4];
  g_waterPack = g_modelPacks[5];
  g_sunPack = g_modelPacks[6];

}

/**
 * Swaps the properties of two cameras.
 * @param {!Object} oldCamera The old camera.
 * @param {!Object} newCamera The new camera to switch to.
 */
function switchCamera(oldCamera, newCamera) {
  oldCamera.eye.x = newCamera.eye.x;
  oldCamera.eye.y = newCamera.eye.y;
  oldCamera.eye.z = newCamera.eye.z;

  oldCamera.target.x = newCamera.target.x;
  oldCamera.target.y = newCamera.target.y;
  oldCamera.target.z = newCamera.target.z;

  oldCamera.up.x = newCamera.up.x;
  oldCamera.up.y = newCamera.up.y;
  oldCamera.up.z = newCamera.up.z;
}


/**
 * Generates the projection matrix based on the size of the o3d plugin and the
 * camera zoom.
 */
function resize() {
  var newWidth = g_client.width;
  var newHeight = g_client.height;

  if (g_clientZoom != g_cameraZoom ||
      newWidth != g_clientWidth ||
      newHeight != g_clientHeight) {
    g_clientZoom = g_cameraZoom;
    g_clientWidth = newWidth;
    g_clientHeight = newHeight;

    // Set the projection matrix, with a vertical field of view of 30 degrees
    // a near clipping plane of 0.1 and far clipping plane of 10000.
    g_viewInfo.drawContext.projection = g_math.matrix4.perspective(
        g_math.degToRad(g_clientZoom * 30),
        g_clientWidth / g_clientHeight,
        0.1,
        10000);
  }
}
/**
 * Performs functions (usually related to animation) every time the frame is
 * rendered. Also updates properities such as time.
 * @param {!o3d.RenderEvent} renderEvent Render event.
 */
function onrender(renderEvent) {

  var elapsedTime = renderEvent.elapsedTime;
  g_clock += g_timeMult * elapsedTime;

  g_clockParam.value = g_clock;

  g_frame = Math.floor(g_clock * 18);
  g_frame = g_frame % 90;

  var frame = g_frame - 22.5;
  var twoPiOver90 = 2.0 * Math.PI / 90.0;
  var s1 = Math.sin(frame * twoPiOver90) + 1;
  var s2 = Math.sin((frame - 30) * twoPiOver90) + 1;
  var s3 = Math.sin((frame - 60) * twoPiOver90) + 1;

  var denominator = s1 + s2 + s3;
  s1 /= denominator;
  s2 /= denominator;
  s3 /= denominator;

  if (g_frame >= 0 && g_frame < 30) {
    g_swizzle1Param.value = new Array(s1, s3, s2);
  } else if (g_frame >= 30 && g_frame < 60) {
    g_swizzle1Param.value = new Array(s2, s1, s3);
  } else {
    g_swizzle1Param.value = new Array(s3, s2, s1);
  }

  var i = g_frame % 30;
  var m = Math.floor(g_frame / 3);
  if (g_deepWaterTextures[i]){
    g_samplerParam.value.texture = g_deepWaterTextures[i];
  }
  resize();
}

/**
 * Creates the client area.
 */
function init() {
  o3djs.util.makeClients(initStep2);
}

/**
 * Initializes the scene.
 * @param {Array} clientElements Array of o3d object elements.
 */
function initStep2(clientElements) {
  g_o3dElement = clientElements[0];
  g_o3d = g_o3dElement.o3d;
  g_math = o3djs.math;
  g_client = g_o3dElement.client;

  // Currently hardcoding the size.
  g_clientWidth = 700;
  g_clientHeight = 500;

  g_loader = o3djs.loader.createLoader(function() {
      g_finished = true;  // for selenuim
  });

  g_pack = g_client.createPack();
  g_root = g_pack.createObject('Transform');
  g_root.parent = g_client.root;

  // Initialize the camera
  switchCamera(g_camera, g_cameraOriginal);

  // Initialize all the other packs
  initializePacks();

  // Create the render graph for a view.
  g_viewInfo = o3djs.rendergraph.createBasicView(
      g_pack,
      g_client.root,
      g_client.renderGraphRoot);

  // Initialize camera
  resize();
  setViewFromRotation();

  // Create global params.
  g_globalParams = g_pack.createObject('ParamObject');
  g_lightDirectionParam = g_globalParams.createParam('lightWorldPos',
                                                     'ParamFloat3');
  g_clockParam = g_globalParams.createParam('timeParam',
                                            'ParamFloat');
  g_swizzle1Param = g_globalParams.createParam('swizzle1Param',
                                               'ParamFloat3');

  g_lightDirectionParam.value = [0, .15, -1];

  // Add our models
  water(g_waterPack, g_modelRoots[5]);
  dome(g_domePack, g_modelRoots[4]);
  sun(g_sunPack, g_modelRoots[6]);
  rocks(g_rockPack, g_modelRoots[0]);
  bridge(g_bridgePack, g_modelRoots[2]);

  // Add mouse control
  o3djs.event.addEventListener(g_o3dElement, 'mousedown', startDragging);
  o3djs.event.addEventListener(g_o3dElement, 'mousemove', drag);
  o3djs.event.addEventListener(g_o3dElement, 'mouseup', stopDragging);
  o3djs.event.addEventListener(g_o3dElement, 'wheel', scrollMe);

  g_client.setRenderCallback(onrender);

  g_loader.finish();
}

/**
 * Returns the path of where the file is located
 * with the trailing slash
 * @return {string} The current path.
 */
function getCurrentPath() {
  var path = window.location.href;
  var index = path.lastIndexOf('/');
  return path.substring(0, index + 1);
}

/**
 * Loads a texture.
 *
 * @param {!o3djs.loader.Loader} loader Loader to use to load texture.
 * @param {!o3d.Pack} pack Pack to load texture in.
 * @param {!o3d.Material} material Material to attach sampler to.
 * @param {string} samplerName Name of sampler.
 * @param {string} textureName filename of texture.
 * @return {!o3d.Sampler} Sampler attached to material.
 */
function loadTexture(loader, pack, material, samplerName, textureName) {
  var samplerParam = material.getParam(samplerName);
  var sampler = pack.createObject('Sampler');
  samplerParam.value = sampler;

  var url = getCurrentPath() + 'assets/' + textureName;
  loader.loadTexture(pack, url, function(texture, exception) {
        if (exception) {
          alert(exception);
        } else {
          sampler.texture = texture;
        }
      });

  return sampler;
}

/**
 * Creates the rocks and its shaders.
 * @param {!o3d.Pack} pack Pack to load the rocks into.
 * @param {o3d.Transform} root Parent transform for the rocks shape.
 */
function rocks(pack, root) {
  var loader = g_loader.createLoader(function() {
      o3djs.pack.preparePack(pack, g_viewInfo);});
  loader.loadScene(g_client, pack, root, getCurrentPath() + g_modelPaths[0],
                   rockCallback);

  function rockCallback(pack, root, exception) {
    var rockTextures = ['rock_tile_rgb.jpg', 'main_rock_normal.dds'];
    var bumpSamplers = ['DiffuseSampler', 'BumpSampler'];

    var materials = pack.getObjects('lambert2', 'o3d.Material');
    for (var m = 0; m < materials.length; m++) {
      var material = materials[m];
      var fxString = document.getElementById('rocks').value;
      var effect = pack.createObject('Effect');
      effect.loadFromFXString(fxString);

      effect.createUniformParameters(material);
      material.effect = effect;

      var lightDiffuseParam = material.getParam('diffuse_Color');
      if (lightDiffuseParam) {
        lightDiffuseParam.value = [.5, .5, .5, 1];
      }
      for (var t = 0; t < rockTextures.length; t++) {
        loadTexture(loader, pack, material, bumpSamplers[t], rockTextures[t]);
      }
    }
    connectParams(pack);
  }
  loader.finish();
}

/**
 * Creates the bridge and its shaders.
 * @param {!o3d.Pack} pack Pack to load the bridge into.
 * @param {!o3d.Transform} root Parent transform for the bridge shape.
 */
function bridge(pack, root) {
  var loader = g_loader.createLoader(function() {
      o3djs.pack.preparePack(pack, g_viewInfo);});
  loader.loadScene(
      g_client, pack, root, getCurrentPath() + g_modelPaths[1], callback);

  function callback(pack, root, exception) {
    var materials = pack.getObjects('bridgeMaterial', 'o3d.Material');
    for (var m = 0; m < materials.length; m++) {
      var material = materials[m];

      var fxString = document.getElementById('bridge').value;
      var effect = pack.createObject('Effect');
      effect.loadFromFXString(fxString);

      effect.createUniformParameters(material);
      material.effect = effect;

      // White ambient light
      var lightAmbientParam = material.getParam('lightAmbient');
      if (lightAmbientParam) {
        lightAmbientParam.value = [0.04, 0.04, 0.04, 1];
      }
      // Reddish diffuse light
      var lightDiffuseParam = material.getParam('lightDiffuse');
      if (lightDiffuseParam) {
        lightDiffuseParam.value = [.45, .27, .07, 1];
      }
    }
    connectParams(pack);
  }

  loader.finish();
}

/**
 * Creates the water shapes, applies correct shaders and connects any special
 * water params.
 * @param {!o3d.Pack} pack Pack to load the water into.
 * @param {!o3d.Transform} root Parent transform for the water shape.
 */
function water(pack, root) {
  // Create the material
  o3djs.dump.dump('-- start water\n');

  var material = pack.createObject('Material');
  o3djs.dump.dump('-- water: created material\n');
  material.drawList = g_viewInfo.performanceDrawList;
  var effect = pack.createObject('Effect');
  var effectString = document.getElementById('water').value;
  effect.loadFromFXString(effectString);
  effect.createUniformParameters(material);
  material.effect = effect;

  o3djs.dump.dump('-- water: created material & effect\n');
  var swizzle1Param = material.getParam('swizzle1');
  swizzle1Param.bind(g_swizzle1Param);
  o3djs.dump.dump('-- water: bound swizzle param\n');

  var water = o3djs.primitives.createDisc(pack, material,
                                          155, 300, 500, 300, 10)
  var loader = g_loader.createLoader(
      function() {
        root.addShape(water);
      });

  // The texture that this sampler references changes every 3 frames
  g_samplerParam = material.getParam('HeightSampler');
  if (g_samplerParam) {
    o3djs.dump.dump('bind sampler param\n');
    g_samplerParam.value = pack.createObject('Sampler');
  }

  // This is so "index" is unique.
  // @param {number} index Index of texture to register.
  // @return {!function(!o3d.Texture): void} A function that takes a
  //     texture.
  function registerDeepWaterTexture(index) {
    return function(texture) {
      g_deepWaterTextures[index] = texture;
    }
  }

  o3djs.dump.dump('-- water: start loading deep water textures\n');
  for (var i = 0; i < 30; i++) {
    var textureName = 'deepwater.' + i + '.png';
    var url = getCurrentPath() + 'assets/deepwater/' + textureName;
    loader.loadTexture(pack, url, registerDeepWaterTexture(i));
  }

  var sampler = loadTexture(loader, pack, material, 'Reflectivity',
                            'reflectivity_map.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;
  sampler.magFilter = g_o3d.Sampler.LINEAR;
  sampler.minFilter = g_o3d.Sampler.LINEAR;

  sampler = loadTexture(loader, pack, material, 'ReflectionSampler',
                        'rock_reflection_new.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;

  sampler = loadTexture(loader, pack, material, 'HorizonRamp',
                        'horizon_ramp_1.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;

  sampler = loadTexture(loader, pack, material, 'SunRamp', 'sun_ramp_1.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;

  sampler = loadTexture(loader, pack, material, 'RockTexture',
                        'rock_tile_rgb.jpg');

  connectParams(pack);
  o3djs.dump.dump('Done applying Shader\n');

  root.localMatrix = g_math.matrix4.translation([-2.751, 0, -8.563]);
  o3djs.dump.dump('-- Finished water\n');
  loader.finish();
}


/**
 * Creates the sun shape, applies correct shaders and connects any special
 * sun params.
 * @param {!o3d.Pack} pack Pack to load the sun into.
 * @param {!o3d.Transform} root Parent transform for the sun shape.
 */
function sun(pack, root) {
  // Create the material
  o3djs.dump.dump('-- start sun\n');

  var material = pack.createObject('Material');
  var effect = pack.createObject('Effect');
  var fxString = document.getElementById('sun').value;
  effect.loadFromFXString(fxString);
  material.drawList = g_viewInfo.zOrderedDrawList;
  material.effect = effect;
  effect.createUniformParameters(material);
  o3djs.dump.dump('-- created sun material\n');

  // Probably not a necessary state, but available for the future
  var state = pack.createObject('State');
  state.getStateParam('CullMode').value = g_o3d.State.CULL_NONE;
  material.state = state;

  var sun = o3djs.primitives.createPlane(pack, material, 20, 20, 2, 2);
  var loader = g_loader.createLoader(
      function() {
        root.addShape(sun);
      });

  var sampler = loadTexture(loader, pack, material, 'DiffuseSampler',
                            'sun_compat.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;

  connectParams(pack);
  root.localMatrix = g_math.matrix4.mul(
      g_math.matrix4.rotationX(90 * 0.0174),
      g_math.matrix4.translation([0, 15, 140]));
  loader.finish();
  o3djs.dump.dump('-- Finished sun\n');
}

/**
 * Creates the dume sphere shape, applies correct shaders and connects any
 * special dome params.
 * @param {!o3d.Pack} pack Pack to load the dome into.
 * @param {!o3d.Transform} root Parent transform for the dome sphere shape.
 */
function dome(pack, root) {
  // Create the material
  o3djs.dump.dump('-- start dome\n');

  var material = pack.createObject('Material');
  var effect = pack.createObject('Effect');
  var fxString = document.getElementById('dome').value;
  effect.loadFromFXString(fxString);
  material.effect = effect;
  material.drawList = g_viewInfo.performanceDrawList;
  effect.createUniformParameters(material);
  o3djs.dump.dump('-- created dome material\n');

  // Turn off culling since we are inside the sphere
  var state = pack.createObject('State');
  state.getStateParam('CullMode').value = g_o3d.State.CULL_NONE;
  material.state = state;

  var dome = o3djs.primitives.createSphere(pack, material, 150, 25, 25);

  var loader = g_loader.createLoader(
      function() {
        root.addShape(dome);
      });

  var sampler = loadTexture(loader, pack, material, 'DiffuseSampler',
                            'sky_compat.png');

  sampler = loadTexture(loader, pack, material, 'HorizonRamp',
                        'horizon_ramp.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;

  sampler = loadTexture(loader, pack, material, 'SunRamp', 'sun_ramp.png');
  sampler.addressModeU = g_o3d.Sampler.CLAMP;
  sampler.addressModeV = g_o3d.Sampler.CLAMP;

  connectParams(pack);
  loader.finish();
  o3djs.dump.dump('-- Finished dome\n');
}

/**
 * Binds params to the global params so they update automatically.
 * @param {!o3d.Pack} pack The pack containing the materials with params.
 */
function connectParams(pack) {
  // Manually connect all the materials' lightWorldPos params to the global
  // params.
  var materials = pack.getObjectsByClassName('o3d.Material');
  for (var m = 0; m < materials.length; ++m) {
    var material = materials[m];
    var param = material.getParam('lightDirection');
    if (param) {
      param.bind(g_lightDirectionParam);
    }
    param = material.getParam('inputTime');
    if (param) {
      param.bind(g_clockParam);
    }
    param = material.getParam('cameraEye');
    if (param) {
     param.value = [-2.751, 3.529, -8.563];
    }
  }
}


/**
 * Remove any callbacks so they don't get called after the page has unloaded.
 */
function unload() {
  if (g_client) {
    g_client.cleanup();
  }
}