/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.imagepipeline.producers;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Executor;

import android.content.ContentResolver;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Pair;

import com.facebook.common.internal.ImmutableMap;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.util.UriUtil;
import com.facebook.imageformat.ImageFormat;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.memory.PooledByteBuffer;
import com.facebook.imagepipeline.memory.PooledByteBufferFactory;
import com.facebook.imagepipeline.memory.PooledByteBufferInputStream;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imageutils.BitmapUtil;
import com.facebook.imageutils.JfifUtil;

/**
 * A producer that retrieves exif thumbnails.
 *
 * <p>At present, these thumbnails are retrieved on the java heap before being put into native
 * memory.
 */
public class LocalExifThumbnailProducer implements Producer<EncodedImage> {

  @VisibleForTesting static final String PRODUCER_NAME = "LocalExifThumbnailProducer";
  @VisibleForTesting static final String CREATED_THUMBNAIL = "createdThumbnail";

  private final Executor mExecutor;
  private final PooledByteBufferFactory mPooledByteBufferFactory;
  private final ContentResolver mContentResolver;

  public LocalExifThumbnailProducer(
      Executor executor,
      PooledByteBufferFactory pooledByteBufferFactory,
      ContentResolver contentResolver) {
    mExecutor = executor;
    mPooledByteBufferFactory = pooledByteBufferFactory;
    mContentResolver = contentResolver;
  }

  @Override
  public void produceResults(
      final Consumer<EncodedImage> consumer,
      final ProducerContext producerContext) {

    final ProducerListener listener = producerContext.getListener();
    final String requestId = producerContext.getId();
    final ImageRequest imageRequest = producerContext.getImageRequest();

    final StatefulProducerRunnable cancellableProducerRunnable =
        new StatefulProducerRunnable<EncodedImage>(
            consumer,
            listener,
            PRODUCER_NAME,
            requestId) {
          @Override
          protected EncodedImage getResult()
              throws Exception {
            final Uri sourceUri = imageRequest.getSourceUri();

            final ExifInterface exifInterface = getExifInterface(sourceUri);
            if (exifInterface == null || !exifInterface.hasThumbnail()) {
              return null;
            }

            byte[] bytes = exifInterface.getThumbnail();
            PooledByteBuffer pooledByteBuffer = mPooledByteBufferFactory.newByteBuffer(bytes);
            return buildEncodedImage(pooledByteBuffer, exifInterface);
          }

          @Override
          protected void disposeResult(EncodedImage result) {
            EncodedImage.closeSafely(result);
          }

          @Override
          protected Map<String, String> getExtraMapOnSuccess(final EncodedImage result) {
            return ImmutableMap.of(CREATED_THUMBNAIL, Boolean.toString(result != null));
          }
        };
    producerContext.addCallbacks(
        new BaseProducerContextCallbacks() {
          @Override
          public void onCancellationRequested() {
            cancellableProducerRunnable.cancel();
          }
        });
    mExecutor.execute(cancellableProducerRunnable);
  }

  @VisibleForTesting ExifInterface getExifInterface(Uri uri) throws IOException {
    final String realPath = getRealPathFromUri(uri);
    if (canReadAsFile(realPath)) {
        return new ExifInterface(realPath);
    }
    return null;
  }

  private EncodedImage buildEncodedImage(
      PooledByteBuffer imageBytes,
      ExifInterface exifInterface) {
    Pair<Integer, Integer> dimensions =
        BitmapUtil.decodeDimensions(new PooledByteBufferInputStream(imageBytes));
    int rotationAngle = getRotationAngle(exifInterface);
    int width = dimensions != null ? dimensions.first : EncodedImage.UNKNOWN_WIDTH;
    int height = dimensions != null ? dimensions.second : EncodedImage.UNKNOWN_HEIGHT;
    EncodedImage encodedImage = new EncodedImage(CloseableReference.of(imageBytes));
    encodedImage.setImageFormat(ImageFormat.JPEG);
    encodedImage.setRotationAngle(rotationAngle);
    encodedImage.setWidth(width);
    encodedImage.setHeight(height);
    return encodedImage;
  }

  // Gets the correction angle based on the image's orientation
  private int getRotationAngle(final ExifInterface exifInterface) {
    return JfifUtil.getAutoRotateAngleFromOrientation(
        Integer.parseInt(exifInterface.getAttribute(ExifInterface.TAG_ORIENTATION)));
  }

  /**
   * Get the path of a file from the Uri
   * @param srcUri The source uri
   * @return The Path for the file or null if doesn't exists
   */
  private String getRealPathFromUri(final Uri srcUri) {
    String result = null;
    if (UriUtil.isLocalContentUri(srcUri)) {
      Cursor cursor = mContentResolver.query(srcUri, null, null, null, null);
      if (cursor != null) {
        cursor.moveToFirst();
        int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
        result = cursor.getString(idx);
        cursor.close();
      }
    } else if (UriUtil.isLocalFileUri(srcUri)) {
      result = srcUri.getPath();
    }
    return result;
  }

  @VisibleForTesting boolean canReadAsFile(String realPath) throws IOException {
    if (realPath == null) {
      return false;
    }
    final File file = new File(realPath);
    return file.exists() && file.canRead();
  }

}

@@ -5485,16 +5485,51 @@
 cursor =
+ null;%0A      try %7B%0A        cursor =
  mConten
@@ -5579,24 +5579,26 @@
 ull);%0A      
+ 
+ 
 if (cursor !
@@ -5603,24 +5603,26 @@
  != null) %7B%0A
+  
         curs
@@ -5635,24 +5635,26 @@
 eToFirst();%0A
+  
         int 
@@ -5719,16 +5719,18 @@
 .DATA);%0A
+  
         
@@ -5761,16 +5761,76 @@
 g(idx);%0A
+        %7D%0A      %7D finally %7B%0A        if (cursor != null) %7B%0A  
         
@@ -5841,24 +5841,34 @@
 or.close();%0A
+        %7D%0A
       %7D%0A    

use try/finally to close cursor