'use strict';
/* global
  CONFIG_MAX_IMAGE_PIXEL_SIZE,
  CONFIG_REQUIRED_EXIF_PREVIEW_HEIGHT,
  CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH,
  Downsample,
  GestureDetector,
  getImageSize,
  getStorageIfAvailable,
  getUnusedFilename,
  MediaFrame,
  MimeMapper,
  NFC,
  parseJPEGMetadata
*/

(function init() {
  var activity;         // The activity object we're handling
  var activityData;     // The data sent by the initiating app
  var blob;             // The blob we'll be displaying and maybe saving
  var frame;            // The MediaFrame that displays the image
  var saved = false;    // Did we save the file?
  var storage;          // The DeviceStorage object used for saving
  var title;            // What we call the image in the titlebar and banner

  // Register a handler to receive the Activity object
  navigator.mozSetMessageHandler('activity', handleOpenActivity);

  function $(id) { return document.getElementById(id); }

  // Display the specified blob, unless it is too big to display
  function open(blob) {

    // If the app that initiated this activity wants us to do allow the
    // user to save this blob as a file, and if device storage is available
    // and if there is enough free space, then display a save button.
    if (activityData.allowSave && activityData.filename && checkFilename()) {
      getStorageIfAvailable('pictures', blob.size, function(ds) {
        storage = ds;
        showSaveButton();
      });
    }

    // Figure out how big (in pixels) the image is.
    // For JPEG images, this also gets us the preview image if there is one.
    getImageSize(blob, success, error);

    // Called if we get an image size
    function success(metadata) {
      var pixels = metadata.width * metadata.height;

      //
      // If the image is too big, reject it now so we don't have
      // memory trouble later.
      //
      // CONFIG_MAX_IMAGE_PIXEL_SIZE is maximum image resolution we
      // can handle.  It's from config.js which is generated at build
      // time (see build/application-data.js).
      //
      // For jpeg images, we can downsample while decoding so we can
      // handle images that are quite a bit larger
      //
      var imagesizelimit = CONFIG_MAX_IMAGE_PIXEL_SIZE;
      if (blob.type === 'image/jpeg') {
        imagesizelimit *= Downsample.MAX_AREA_REDUCTION;
      }

      //
      // Even if we can downsample an image while decoding it, we still
      // have to read the entire image file. If the file is particularly
      // large we might also have memory problems. (See bug 1008834: a 20mb
      // 80mp jpeg file will cause an OOM on Tarako even though we can
      // decode it at < 2mp). Rather than adding another build-time config
      // variable to specify the maximum file size, however, we'll just
      // base the file size limit on CONFIG_MAX_IMAGE_PIXEL_SIZE.
      // So if that variable is set to 2M, then we might use up to 12Mb of
      // memory. 2 * 2M bytes for the image file and 4 bytes times 2M pixels
      // for the decoded image. A 4mb file size limit should accomodate
      // most JPEG files up to 12 or 16 megapixels
      //
      var filesizelimit = 2 * CONFIG_MAX_IMAGE_PIXEL_SIZE;

      if (pixels > imagesizelimit || blob.size > filesizelimit) {
        displayError('imagetoobig');
        return;
      }

      // If there was no EXIF preview, or if the image is not very big,
      // display the full-size image.
      if (!metadata.preview || pixels < 512 * 1024) {
        frame.displayImage(blob,
                           metadata.width,
                           metadata.height,
                           null,
                           metadata.rotation,
                           metadata.mirrored);
      }
      else {
        // If we found an EXIF preview, and can determine its size, then
        // we can display it instead of the big image and save a lot of
        // memory.
        parseJPEGMetadata(blob.slice(metadata.preview.start,
                                     metadata.preview.end,
                                     'image/jpeg'),
                          function success(previewmetadata) {
                            //
                            // If we parsed the preview image, add its
                            // dimensions to the metdata.preview
                            // object, and then let the MediaFrame
                            // object display the preview instead of
                            // the full-size image.
                            //
                            metadata.preview.width = previewmetadata.width;
                            metadata.preview.height = previewmetadata.height;
                            frame.displayImage(blob,
                                               metadata.width,
                                               metadata.height,
                                               metadata.preview,
                                               metadata.rotation,
                                               metadata.mirrored);
                          },
                          function error() {
                            //
                            // If we couldn't parse the preview image,
                            // just display full-size.
                            //
                            frame.displayImage(blob,
                                               metadata.width,
                                               metadata.height,
                                               null,
                                               metadata.rotation,
                                               metadata.mirrored);

                          });
      }
    }

    // Called if getImageSize parsing fails.
    function error(msg) {
      //
      // This wasn't a JPEG, PNG, GIF, or BMP image and we can't figure
      // out the size of the image. Without the size, we can't pass the
      // image to MediaFrame.displayImage().
      //
      // XXX: Currently, we know how to get the sizes of all the image
      // types that this activity is registered to handle. If we add new
      // image types to manifest.webapp, then we should update
      // shared/js/media/image_size.js to compute the size or we should
      // add code here to load the image into an offscreen <img> to
      // determine its size (but only if the file size is not too large).
      //
      displayError('imageinvalid');
    }
  }

  function handleOpenActivity(request) {
    activity = request;
    activityData = activity.source.data;

    // Set up the UI, if it is not already set up
    if (!frame) {

      // Hook up the buttons
      $('header').addEventListener('action', done);
      $('save').addEventListener('click', save);

      // And register event handlers for gestures
      frame = new MediaFrame($('frame'), false, CONFIG_MAX_IMAGE_PIXEL_SIZE);

      if (CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH) {
        frame.setMinimumPreviewSize(CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH,
                                    CONFIG_REQUIRED_EXIF_PREVIEW_HEIGHT);
      }

      var gestureDetector = new GestureDetector(frame.container);
      gestureDetector.startDetecting();
      frame.container.addEventListener('dbltap', handleDoubleTap);
      frame.container.addEventListener('transform', handleTransform);
      frame.container.addEventListener('pan', handlePan);
      frame.container.addEventListener('swipe', handleSwipe);

      window.addEventListener('resize', frame.resize.bind(frame));
      if (activityData.exitWhenHidden) {
        window.addEventListener('visibilitychange', function() {
          if (document.hidden) {
            done();
          }
        });
      }

      // Report errors if we're passed an invalid image
      frame.onerror = function invalid() {
        displayError('imageinvalid');
      };
    }

    // Display the filename in the header, if there was one
    title = baseName(activityData.filename || '');
    $('filename').textContent = title;

    blob = activityData.blob;
    open(blob);
    NFC.share(blob, {
      foregroundElement: $('open'),
      backgroundElement: document.body
    });
  }

  function checkFilename() {
    // Hide save button for file names having hidden
    // .gallery/ directories. See Bug 992426
    if (activityData.filename.indexOf('.gallery/') != -1) {
      return false;
    }
    else {
      var dotIdx = activityData.filename.lastIndexOf('.');
      if (dotIdx > -1) {
        var ext = activityData.filename.substr(dotIdx + 1);
        return MimeMapper.guessTypeFromExtension(ext) === blob.type;
      } else {
        return false;
      }
    }
  }

  function displayError(msgid) {
    document.l10n.formatValue(msgid).then(function(val) {
      alert(val);
      done();
    });
  }

  function done() {
    activity.postResult({ saved: saved });
    activity = null;
    NFC.unshare();
  }

  function handleDoubleTap(e) {
    var scale;
    if (frame.fit.scale > frame.fit.baseScale) {
      scale = frame.fit.baseScale / frame.fit.scale;
    } else {
      scale = 2;
    }

    frame.zoom(scale, e.detail.clientX, e.detail.clientY, 200);
  }

  function handleTransform(e) {
    frame.zoom(e.detail.relative.scale,
               e.detail.midpoint.clientX,
               e.detail.midpoint.clientY);
  }

  function handlePan(e) {
    frame.pan(e.detail.relative.dx, e.detail.relative.dy);
  }

  function handleSwipe(e) {
    var direction = e.detail.direction;
    var velocity = e.detail.vy;
    if (direction === 'down' && velocity > 2) {
      done();
    }
  }

  function save() {

    // Hides the save button: we can only save once
    hideSaveButton();

    getUnusedFilename(storage, activityData.filename, function(filename) {
      var savereq = storage.addNamed(blob, filename);
      savereq.onsuccess = function() {
        // Remember that it has been saved so we can pass this back
        // to the invoking app
        saved = filename;
        // And tell the user
        showBanner('saved', title);
      };
      savereq.onerror = function(e) {
        // XXX we don't report this to the user because it is hard to
        // localize.
        console.error('Error saving', filename, e);
      };
    });
  }

  function showSaveButton() {
    $('save').classList.remove('hidden');
    // XXX work around bug 870619
    $('filename').textContent = $('filename').textContent;
  }

  function hideSaveButton() {
    $('save').classList.add('hidden');
    // XXX work around bug 870619
    $('filename').textContent = $('filename').textContent;
  }

  function showBanner(msg, title) {
    document.l10n.setAttributes($('message'), msg, {filename: title});
    $('banner').hidden = false;
    setTimeout(function() {
      $('banner').hidden = true;
    }, 3000);
  }

  // Strip directories and just return the base filename
  function baseName(filename) {
    return filename.substring(filename.lastIndexOf('/') + 1);
  }
})();
