/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ex.chips;

import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.ContactsContract;
import androidx.collection.LruCache;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Default implementation of {@link com.android.ex.chips.PhotoManager} that
 * queries for photo bytes by using the {@link com.android.ex.chips.RecipientEntry}'s
 * photoThumbnailUri.
 */
public class DefaultPhotoManager implements PhotoManager {
    private static final String TAG = "DefaultPhotoManager";

    private static final boolean DEBUG = false;

    /**
     * For reading photos for directory contacts, this is the chunk size for
     * copying from the {@link InputStream} to the output stream.
     */
    private static final int BUFFER_SIZE = 1024*16;

    private static class PhotoQuery {
        public static final String[] PROJECTION = {
            ContactsContract.CommonDataKinds.Photo.PHOTO
        };

        public static final int PHOTO = 0;
    }

    private final ContentResolver mContentResolver;
    private final LruCache<Uri, byte[]> mPhotoCacheMap;

    public DefaultPhotoManager(ContentResolver contentResolver) {
        mContentResolver = contentResolver;
        mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE);
    }

    @Override
    public void populatePhotoBytesAsync(RecipientEntry entry, PhotoManagerCallback callback) {
        final Uri photoThumbnailUri = entry.getPhotoThumbnailUri();
        if (photoThumbnailUri != null) {
            final byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri);
            if (photoBytes != null) {
                entry.setPhotoBytes(photoBytes);
                if (callback != null) {
                    callback.onPhotoBytesPopulated();
                }
            } else {
                if (DEBUG) {
                    Log.d(TAG, "No photo cache for " + entry.getDisplayName()
                            + ". Fetch one asynchronously");
                }
                fetchPhotoAsync(entry, photoThumbnailUri, callback);
            }
        } else if (callback != null) {
            callback.onPhotoBytesAsyncLoadFailed();
        }
    }

    private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri,
            final PhotoManagerCallback callback) {
        final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() {
            @Override
            protected byte[] doInBackground(Void... params) {
                // First try running a query. Images for local contacts are
                // loaded by sending a query to the ContactsProvider.
                final Cursor photoCursor = mContentResolver.query(
                        photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null);
                if (photoCursor != null) {
                    try {
                        if (photoCursor.moveToFirst()) {
                            return photoCursor.getBlob(PhotoQuery.PHOTO);
                        }
                    } finally {
                        photoCursor.close();
                    }
                } else {
                    // If the query fails, try streaming the URI directly.
                    // For remote directory images, this URI resolves to the
                    // directory provider and the images are loaded by sending
                    // an openFile call to the provider.
                    try {
                        InputStream is = mContentResolver.openInputStream(
                                photoThumbnailUri);
                        if (is != null) {
                            byte[] buffer = new byte[BUFFER_SIZE];
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            try {
                                int size;
                                while ((size = is.read(buffer)) != -1) {
                                    baos.write(buffer, 0, size);
                                }
                            } finally {
                                is.close();
                            }
                            return baos.toByteArray();
                        }
                    } catch (IOException ex) {
                        // ignore
                    }
                }
                return null;
            }

            @Override
            protected void onPostExecute(final byte[] photoBytes) {
                entry.setPhotoBytes(photoBytes);
                if (photoBytes != null) {
                    mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
                    if (callback != null) {
                        callback.onPhotoBytesAsynchronouslyPopulated();
                    }
                } else if (callback != null) {
                    callback.onPhotoBytesAsyncLoadFailed();
                }
            }
        };
        photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
    }
}
