/*
 * Copyright (C) 2009 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.videoeditor;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.widget.FrameLayout;
import android.widget.RadioGroup;
import android.widget.Toast;

import com.android.videoeditor.widgets.ImageViewTouchBase;

/**
 * Activity for setting the begin and end Ken Burns viewing rectangles
 */
public class KenBurnsActivity extends Activity {
    // Logging
    private static final String TAG = "KenBurnsActivity";

    // State keys
    private static final String STATE_WHICH_RECTANGLE_ID = "which";
    private static final String STATE_START_RECTANGLE = "start";
    private static final String STATE_END_RECTANGLE = "end";

    // Intent extras
    public static final String PARAM_WIDTH = "width";
    public static final String PARAM_HEIGHT = "height";
    public static final String PARAM_FILENAME = "filename";
    public static final String PARAM_MEDIA_ITEM_ID = "media_item_id";
    public static final String PARAM_START_RECT = "start_rect";
    public static final String PARAM_END_RECT = "end_rect";

    private static final int MAX_HW_BITMAP_WIDTH = 2048;
    private static final int MAX_HW_BITMAP_HEIGHT = 2048;
    private static final int MAX_WIDTH = 1296;
    private static final int MAX_HEIGHT = 720;
    private static final int MAX_PAN = 3;

    // Instance variables
    private final Rect mStartRect = new Rect(0, 0, 0, 0);
    private final Rect mEndRect = new Rect(0, 0, 0, 0);
    private final RectF mMatrixRect = new RectF(0, 0, 0, 0);
    private RadioGroup mRadioGroup;
    private ImageViewTouchBase mImageView;
    private View mDoneButton;
    private GestureDetector mGestureDetector;
    private ScaleGestureDetector mScaleGestureDetector;
    private boolean mPaused = true;
    private int mMediaItemWidth, mMediaItemHeight;
    private float mImageViewScale;
    private int mImageSubsample;
    private Bitmap mBitmap;

    /**
     * The simple gestures listener
     */
    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (mImageView.getScale() > 1F) {
                mImageView.postTranslateCenter(-distanceX, -distanceY);
                saveBitmapRectangle();
            }

            return true;
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            // Switch between the original scale and 3x scale.
            if (mImageView.getScale() > 2F) {
                mImageView.zoomTo(1F);
            } else {
                mImageView.zoomTo(3F, e.getX(), e.getY());
            }

            saveBitmapRectangle();
            return true;
        }
    }

    /**
     * Scale gesture listener
     */
    private class MyScaleGestureListener implements OnScaleGestureListener {
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            final float relativeScaleFactor = detector.getScaleFactor();
            final float newAbsoluteScale = relativeScaleFactor * mImageView.getScale();
            if (newAbsoluteScale < 1.0F) {
                return false;
            }

            mImageView.zoomTo(newAbsoluteScale, detector.getFocusX(), detector.getFocusY());
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            saveBitmapRectangle();
        }
    }

    /**
     * Image loader class
     */
    private class ImageLoaderAsyncTask extends AsyncTask<Void, Void, Bitmap> {
        // Instance variables
        private final String mFilename;

        /**
         * Constructor
         *
         * @param filename The filename
         */
        public ImageLoaderAsyncTask(String filename) {
            mFilename = filename;
            showProgress(true);
        }

        @Override
        protected Bitmap doInBackground(Void... zzz) {
            if (mPaused) {
                return null;
            }

            // Wait for the layout to complete
            while (mImageView.getWidth() <= 0) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException ex) {
                }
            }

            if (mBitmap != null) {
                return mBitmap;
            } else {
                final BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = mImageSubsample;
                return BitmapFactory.decodeFile(mFilename, options);
            }
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (bitmap == null) {
                if (!mPaused) {
                    finish();
                }
                return;
            }

            if (!mPaused) {
                showProgress(false);
                mRadioGroup.setEnabled(true);
                mImageView.setImageBitmapResetBase(bitmap, true);
                mBitmap = bitmap;
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Bitmap size: " + bitmap.getWidth() + "x" + bitmap.getHeight()
                            + ", bytes: " + (bitmap.getRowBytes() * bitmap.getHeight()));
                }

                showBitmapRectangle();
            }
        }
    }

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.ken_burns_layout);
        setFinishOnTouchOutside(true);

        mMediaItemWidth = getIntent().getIntExtra(PARAM_WIDTH, 0);
        mMediaItemHeight = getIntent().getIntExtra(PARAM_HEIGHT, 0);
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Media item size: " + mMediaItemWidth + "x" + mMediaItemHeight);
        }

        // Setup the image view
        mImageView = (ImageViewTouchBase)findViewById(R.id.ken_burns_image);

        // Set the width and height of the image view
        final FrameLayout.LayoutParams lp =
            (FrameLayout.LayoutParams)mImageView.getLayoutParams();
        if (mMediaItemWidth >= mMediaItemHeight) {
            lp.width = Math.min(mMediaItemWidth, MAX_WIDTH) / MAX_PAN;
            // Compute the height by preserving the aspect ratio
            lp.height = (lp.width * mMediaItemHeight) / mMediaItemWidth;
            mImageSubsample = mMediaItemWidth / (lp.width * MAX_PAN);
        } else {
            lp.height = Math.min(mMediaItemHeight, MAX_HEIGHT) / MAX_PAN;
            // Compute the width by preserving the aspect ratio
            lp.width = (lp.height * mMediaItemWidth) / mMediaItemHeight;
            mImageSubsample = mMediaItemHeight / (lp.height * MAX_PAN);
        }

        // Ensure that the size of the bitmap will not exceed the size supported
        // by HW vendors
        while ((mMediaItemWidth / mImageSubsample > MAX_HW_BITMAP_WIDTH) ||
                (mMediaItemHeight / mImageSubsample > MAX_HW_BITMAP_HEIGHT)) {
            mImageSubsample++;
        }

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "View size: " + lp.width + "x" + lp.height
                    + ", subsample: " + mImageSubsample);
        }

        // If the image is too small the image view may be too small to pinch
        if (lp.width < 120 || lp.height < 120) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Image is too small: " + lp.width + "x" + lp.height);
            }

            Toast.makeText(this, getString(R.string.pan_zoom_small_image_error),
                    Toast.LENGTH_LONG).show();
            finish();
            return;
        }

        mImageView.setLayoutParams(lp);
        mImageViewScale = ((float)lp.width) / ((float)mMediaItemWidth);

        mGestureDetector = new GestureDetector(this, new MyGestureListener());
        mScaleGestureDetector = new ScaleGestureDetector(this, new MyScaleGestureListener());

        mRadioGroup = (RadioGroup)findViewById(R.id.which_rectangle);
        if (state != null) {
            mRadioGroup.check(state.getInt(STATE_WHICH_RECTANGLE_ID));
            mStartRect.set((Rect)state.getParcelable(STATE_START_RECTANGLE));
            mEndRect.set((Rect)state.getParcelable(STATE_END_RECTANGLE));
        } else {
            mRadioGroup.check(R.id.start_rectangle);
            final Rect startRect = (Rect)getIntent().getParcelableExtra(PARAM_START_RECT);
            if (startRect != null) {
                mStartRect.set(startRect);
            } else {
                mStartRect.set(0, 0, mMediaItemWidth, mMediaItemHeight);
            }

            final Rect endRect = (Rect)getIntent().getParcelableExtra(PARAM_END_RECT);
            if (endRect != null) {
                mEndRect.set(endRect);
            } else {
                mEndRect.set(0, 0, mMediaItemWidth, mMediaItemHeight);
            }
        }

        mDoneButton = findViewById(R.id.done);
        enableDoneButton();

        // Disable the ratio buttons until we load the image
        mRadioGroup.setEnabled(false);

        mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                switch (checkedId) {
                    case R.id.start_rectangle: {
                        showBitmapRectangle();
                        break;
                    }

                    case R.id.end_rectangle: {
                        showBitmapRectangle();
                        break;
                    }

                    case R.id.done: {
                        final Intent extra = new Intent();
                        extra.putExtra(PARAM_MEDIA_ITEM_ID,
                                getIntent().getStringExtra(PARAM_MEDIA_ITEM_ID));
                        extra.putExtra(PARAM_START_RECT, mStartRect);
                        extra.putExtra(PARAM_END_RECT, mEndRect);
                        setResult(RESULT_OK, extra);
                        finish();
                        break;
                    }

                    default: {
                        break;
                    }
                }
            }
        });

        mBitmap = (Bitmap) getLastNonConfigurationInstance();

        mImageView.setEventListener(new ImageViewTouchBase.ImageTouchEventListener() {
            @Override
            public boolean onImageTouchEvent(MotionEvent ev) {
                if (null != mScaleGestureDetector) {
                    mScaleGestureDetector.onTouchEvent(ev);
                    if (mScaleGestureDetector.isInProgress()) {
                        return true;
                    }
                }

                mGestureDetector.onTouchEvent(ev);
                return true;
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();

        mPaused = false;
        // Load the image
        new ImageLoaderAsyncTask(getIntent().getStringExtra(PARAM_FILENAME)).execute();
    }

    @Override
    protected void onPause() {
        super.onPause();

        mPaused = true;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (!isChangingConfigurations()) {
            if (mBitmap != null) {
                mBitmap.recycle();
                mBitmap = null;
            }

            System.gc();
        }
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        return mBitmap;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        final RadioGroup radioGroup = (RadioGroup)findViewById(R.id.which_rectangle);

        outState.putInt(STATE_WHICH_RECTANGLE_ID, radioGroup.getCheckedRadioButtonId());
        outState.putParcelable(STATE_START_RECTANGLE, mStartRect);
        outState.putParcelable(STATE_END_RECTANGLE, mEndRect);
    }

    public void onClickHandler(View target) {
        switch (target.getId()) {
            case R.id.done: {
                final Intent extra = new Intent();
                extra.putExtra(PARAM_MEDIA_ITEM_ID,
                        getIntent().getStringExtra(PARAM_MEDIA_ITEM_ID));
                extra.putExtra(PARAM_START_RECT, mStartRect);
                extra.putExtra(PARAM_END_RECT, mEndRect);
                setResult(RESULT_OK, extra);
                finish();
                break;
            }

            default: {
                break;
            }
        }
    }

    /**
     * Show/hide the progress bar
     *
     * @param show true to show the progress
     */
    private void showProgress(boolean show) {
        if (show) {
            findViewById(R.id.image_loading).setVisibility(View.VISIBLE);
        } else {
            findViewById(R.id.image_loading).setVisibility(View.GONE);
        }
    }

    /**
     * Enable the "Done" button if both rectangles are set
     */
    private void enableDoneButton() {
        mDoneButton.setEnabled(!mStartRect.isEmpty() && !mEndRect.isEmpty());
    }

    /**
     * Show the bitmap rectangle
     */
    private void showBitmapRectangle() {
        final int checkedRect = mRadioGroup.getCheckedRadioButtonId();
        switch (checkedRect) {
            case R.id.start_rectangle: {
                if (!mStartRect.isEmpty()) {
                    mImageView.reset();
                    final float scale = ((float)mMediaItemWidth)
                            / ((float)(mStartRect.right - mStartRect.left));
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, "showBitmapRectangle START: " + scale + " "
                                + mStartRect.left + ", " + mStartRect.top + ", "
                                + mStartRect.right + ", " + mStartRect.bottom);
                    }
                    if (scale > 1F) {
                        mImageView.zoomToOffset(scale, mStartRect.left * scale * mImageViewScale,
                                mStartRect.top * scale * mImageViewScale);
                    }
                }
                break;
            }

            case R.id.end_rectangle: {
                if (!mEndRect.isEmpty()) {
                    mImageView.reset();
                    final float scale = ((float)mMediaItemWidth)
                            / ((float)(mEndRect.right - mEndRect.left));
                    if (Log.isLoggable(TAG, Log.DEBUG)) {
                        Log.d(TAG, "showBitmapRectangle END: " + scale + " "
                                + mEndRect.left + ", " + mEndRect.top + ", "
                                + mEndRect.right + ", " + mEndRect.bottom);
                    }
                    if (scale > 1F) {
                        mImageView.zoomToOffset(scale, mEndRect.left * scale * mImageViewScale,
                                mEndRect.top * scale * mImageViewScale);
                    }
                }
                break;
            }

            default: {
                break;
            }
        }
    }

    /**
     * Show the bitmap rectangle
     */
    private void saveBitmapRectangle() {
        final int checkedRect = mRadioGroup.getCheckedRadioButtonId();
        final FrameLayout.LayoutParams lp =
            (FrameLayout.LayoutParams)mImageView.getLayoutParams();
        switch (checkedRect) {
            case R.id.start_rectangle: {
                mMatrixRect.set(0, 0, lp.width, lp.height);

                mImageView.mapRect(mMatrixRect);
                final float scale = mImageView.getScale();

                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "START RAW: " + scale + ", rect: " + mMatrixRect.left
                            + ", " + mMatrixRect.top + ", " + mMatrixRect.right
                            + ", " + mMatrixRect.bottom);
                }

                final int left = (int)((-mMatrixRect.left/scale) / mImageViewScale);
                final int top = (int)((-mMatrixRect.top/scale) / mImageViewScale);
                final int right = (int)(((-mMatrixRect.left + lp.width)/scale) / mImageViewScale);
                final int bottom = (int)(((-mMatrixRect.top + lp.height)/scale) / mImageViewScale);

                mStartRect.set(left, top, right, bottom);
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "START: " + mStartRect.left + ", " + mStartRect.top + ", "
                            + mStartRect.right + ", " + mStartRect.bottom);
                }

                enableDoneButton();
                break;
            }

            case R.id.end_rectangle: {
                mMatrixRect.set(0, 0, lp.width, lp.height);

                mImageView.mapRect(mMatrixRect);
                final float scale = mImageView.getScale();

                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "END RAW: " + scale + ", rect: " + mMatrixRect.left
                            + ", " + mMatrixRect.top + ", " + mMatrixRect.right
                            + ", " + mMatrixRect.bottom);
                }

                final int left = (int)((-mMatrixRect.left/scale) / mImageViewScale);
                final int top = (int)((-mMatrixRect.top/scale) / mImageViewScale);
                final int right = (int)(((-mMatrixRect.left + lp.width)/scale) / mImageViewScale);
                final int bottom = (int)(((-mMatrixRect.top + lp.height)/scale) / mImageViewScale);

                mEndRect.set(left, top, right, bottom);
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "END: " + mEndRect.left + ", " + mEndRect.top + ", "
                            + mEndRect.right + ", " + mEndRect.bottom);
                }

                enableDoneButton();
                break;
            }

            default: {
                break;
            }
        }
    }
}
