"use strict";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Responsible for loading and retrieving of all assets (shaders, images, but also application objects which
 * use them like meshes, models) in the application.
 *
 * @author smrceant (Antonin Smrcek)
 *
 * @constructor
 */
var AssetLoader = function()
{
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// (CONSTANTS FOR CODE CLARITY)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

AssetLoader.NO_OBJECT_INSTANCE = AssetLoader.NO_RUN_CALLBACK = null;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ASSET LIBRARY
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// assets in the following lists are not necessarily fully loaded!

/**
 * List of already created scene graphs. This will hold (probably) only one scene graph (for the whole scene).
 *
 * @type {Asset}
 */
AssetLoader.loadedScenes    = {};

/**
 * List of already created Assets which load and contain Texture objects.
 *
 * @type {Asset}
 */
AssetLoader.loadedImages  = {};

/**
 * List of all retrieved files.
 *
 * @type {Asset}
 */
AssetLoader.loadedFiles  = {};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PREPARING LOADING OF THE ASSETS AND RETRIEVING THEM
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Creates a job which does not create an application object, but it performs some action.
 * Dummy job is often used as a parent job that waits for completion of its children jobs (e.g. parent job = model assembly,
 * children jobs = loading of mesh, texture..) and then executes given callback (e.g. the actual model assembly from the
 * mesh, texture etc. and producing of the Model object).
 *
 * The dummy job is not stored in the Asset Library.
 *
 * @param   {string}    dummyJobName        name of the job (just for [DEV] purposes)
 * @param   {boolean}   mustStartItself     if TRUE, dummyJob.asset.start() must be called somewhere in the source code for this job to execute
 * @param   {function}  [dummyJobCallback]  function which will be performed by this job
 * @returns {Asset}
 */
AssetLoader.prototype.makeDummyJob = function (dummyJobName, mustStartItself, dummyJobCallback)
{
  var dummyJob = AssetJob.createJob (dummyJobName, mustStartItself ? Asset.AssetType.DUMMY_START_ITSELF : Asset.AssetType.DUMMY, AssetLoader.NO_OBJECT_INSTANCE, dummyJobCallback);

  return dummyJob.asset;
};

/**
 * Creates a job for loading an asset by external means. Used when the asset needs to be loaded e.g. by some jQuery
 * plugin (example: jQuery SVG plugin), but nevertheless, we want to have it in our "Asset ecosystem", maybe because
 * we want this job to be required for some other (normal) job. External job is implemented as dummy job which has
 * to start itself (DUMMY_START_ITSELF).
 *
 * The only thing left is to call asset.startJob() in some onLoadFinished() function
 * provided by the plugin, which will start the dummy job (and since it is dummy, it will immediately finish, which is what
 * we want).
 *
 * @param {string}            jobName              name of the job (just for [DEV] purposes)
 * @param {function}          [jobCallback]        function which will be performed after the external load is completed
 * @param {Object}            [externalArguments]  object which acts as a container for variables which we want to pass into the jobCallback, e.g. when externally loading SVG, we might want to pass reference to the SVG wrapper like {svgWrapper: svgWrapperElement}
 * @returns {Asset}
 */
AssetLoader.prototype.makeExternalLoadingJob = function (jobName, jobCallback, externalArguments)
{
  var _jobCallback = jobCallback;

  // add external arguments object as an argument to the callback, if the callback exists
  if (Util.isInitialized (jobCallback))
  {
    if (Util.isInitialized (externalArguments))
    {
      _jobCallback = function()
      {
        jobCallback (externalArguments);
      };
    }
  }

  // (note: we obtain Asset object, not AssetJob)
  return assetLoader.makeDummyJob (jobName, true, _jobCallback);
};

/**
 * Prepares loading the scene definition file and returns asset containing handler to the scene graph.
 * If the scene definition (= scene graph) is already present in the Asset Library, it is retrieved - but it is not
 * guaranteed that the scene graph is loaded and ready to use. To safely use it in the application, asset.finished
 * property must be TRUE.
 *
 * @param   {string}  sceneFilename   name of the scene definition file
 * @returns {Asset}                   asset with which will hold scene graph (when fully loaded)
 */
AssetLoader.prototype.loadScene = function (sceneFilename)
{
  // asset is already being loaded OR it already completed loading
  if (sceneFilename in AssetLoader.loadedScenes)
  {
    return AssetLoader.loadedScenes[sceneFilename];
  }

  var objectInstance = new SceneGraph (sceneFilename);

  var sceneJob = AssetJob.createJobObjectCreation (sceneFilename, Asset.AssetType.SCENE_DEF, objectInstance);

  return sceneJob.asset;
};

/**
 * Loads an image. Used by loadImageRawData(). Do NOT use this for loading textures and such.
 *
 * @param   {string} imageFilename      image file
 * @returns {Asset}                     asset with the downloaded Image object
 */
AssetLoader.prototype.loadImage = function (imageFilename)
{
  imageFilename = imageFilename.trim();

  if (imageFilename in AssetLoader.loadedImages)
  {
    return AssetLoader.loadedImages[imageFilename];
  }

  var assetJob = AssetJob.createJob (imageFilename, Asset.AssetType.IMAGE, AssetLoader.NO_OBJECT_INSTANCE, AssetLoader.NO_RUN_CALLBACK);

  return assetJob.asset;
};

/**
 * Loads an image and saves its pixel values (RGB form) into the flat array to the object.objectProperty
 * This method is used only for loading heightmap or PVS images, it is NOT used for loading textures and such.
 *
 * @param   {string}    imageFilename       image for which we want to know pixel values
 * @param   {Object}    outObject           into which object does the objectProperty belongs
 * @param   {string}    outObjectProperty   into which object propery we wil save the array
 * @returns {Asset}                         asset with downloaded image object, not very useful in this case
 */
AssetLoader.prototype.loadImageRawData = function (imageFilename, outObject, outObjectProperty)
{
  var assetImageLoad = assetLoader.loadImage (imageFilename);
  assetImageLoad.job.startItselfOnly();

  var getImageData = assetLoader.makeDummyJob("image data for " + imageFilename, false, function ()
  {
    renderer.getImageData (assetImageLoad.job.getDownloadedData(), outObject, outObjectProperty);
  });

  getImageData.waitFor (assetImageLoad); // grab pixels from the image after it is downloaded
  getImageData.startJob();

  return assetImageLoad;
};

/**
 * Retrieves given file and saves it as a text.
 *
 * @param   {string} filename
 * @returns {Asset}
 */
AssetLoader.prototype.loadFileAsText = function (filename)
{
  filename = filename.trim();

  if (filename in AssetLoader.loadedFiles)
  {
    return AssetLoader.loadedFiles[filename];
  }

  var assetJob = AssetJob.createJob (filename, Asset.AssetType.TEXT, AssetLoader.NO_OBJECT_INSTANCE, AssetLoader.NO_RUN_CALLBACK);

  return assetJob.asset;
};

/**
 * Loads vertex and fragment shader files.
 *
 * @param   {string}  vertexShaderFilename    source file for VS
 * @param   {string}  fragmentShaderFilename  source file for FS
 * @param   {*}       callback
 * @returns {Asset}
 */
AssetLoader.prototype.loadFilesForShaderProgram = function (vertexShaderFilename, fragmentShaderFilename, callback)
{
  var shadersRetrieval = assetLoader.makeDummyJob("shaders retrieval: " + vertexShaderFilename + ", " + fragmentShaderFilename, false, callback);

  var vertexShader   = assetLoader.loadFileAsText (vertexShaderFilename);
  var fragmentShader = assetLoader.loadFileAsText (fragmentShaderFilename);

  shadersRetrieval.waitFor (vertexShader);
  shadersRetrieval.waitFor (fragmentShader);

  // (do not return .asset like in other functions, we already return dummyJob of shadersRetrieval)
  return shadersRetrieval;
};

/**
 * Prepares loading of a text file and returns asset containing handler to the JSON object parse from the text file.
 *
 * If the text file is already present in the Asset Library, it is retrieved - but it is not
 * guaranteed that the JSON object has been already created and ready to use. To safely use it in the application, asset.finished
 * property must be TRUE.
 *
 * @param   {string}      filename        text file used for creating JSON
 * @param   {function?}   [runCallback]   function executed AFTER the JSON is created (parsed) from the text file
 * @returns {Asset}                       asset with handler to the JSON
 */
AssetLoader.prototype.loadFileAsJSON = function (filename, runCallback)
{
  filename = filename.trim();

  if (filename in AssetLoader.loadedFiles)
  {
    return AssetLoader.loadedFiles[filename];
  }

  var assetJob = AssetJob.createJob (filename, Asset.AssetType.JSON, AssetLoader.NO_OBJECT_INSTANCE, Util.isInitialized (runCallback) ? runCallback : AssetLoader.NO_RUN_CALLBACK);

  return assetJob.asset;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SAVING ASSETS INTO THE ASSET LIBRARY
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Saves given asset into the list of all created assets so far. There are multiple assets categories and thus lists,
 * depending on the type of the requested asset (shader program, mesh, model, etc.).
 *
 * @param {Asset}           asset       asset to be saved into the library
 * @param {Asset.assetType} assetType   type of the asset
 */
AssetLoader.prototype.saveAssetIntoLibrary = function (asset, assetType)
{
  switch (assetType)
  {
    case Asset.AssetType.IMAGE:
      AssetLoader.loadedImages[asset.name] = asset;
      break;

    case Asset.AssetType.SCENE_DEF:
      AssetLoader.loadedScenes[asset.name] = asset;
      break;

    case Asset.AssetType.TEXT:
    case Asset.AssetType.JSON:
      AssetLoader.loadedFiles[asset.name] = asset;
      break;

    default:
      throw new Error("ERROR: unrecognized asset type for the asset " + asset.name);
      break;
  }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
