public final class

DisplayItem

extends Object
/*
 * 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.cooliris.media;

import java.util.Random;

import android.content.Context;

import com.cooliris.app.App;
import com.cooliris.media.FloatUtils;

/**
 * A simple structure for a MediaItem that can be rendered.
 */
public final class DisplayItem {
    private static final float STACK_SPACING = 0.2f;
    private DirectLinkedList.Entry<DisplayItem> mAnimatablesEntry = new DirectLinkedList.Entry<DisplayItem>(this);
    private static final Random random = new Random();
    private Vector3f mStacktopPosition = new Vector3f(-1.0f, -1.0f, -1.0f);
    private Vector3f mJitteredPosition = new Vector3f();
    private boolean mHasFocus;
    private Vector3f mTargetPosition = new Vector3f();
    private float mTargetTheta;
    private float mImageTheta;
    private int mStackId;
    private MediaItemTexture mThumbnailImage = null;
    private Texture mScreennailImage = null;
    private UriTexture mHiResImage = null;
    private float mConvergenceSpeed = 1.0f;

    public final MediaItem mItemRef;
    public float mAnimatedTheta;
    public float mAnimatedImageTheta;
    public float mAnimatedPlaceholderFade = 0f;
    public boolean mAlive;
    public Vector3f mAnimatedPosition = new Vector3f();
    public int mCurrentSlotIndex;
    private boolean mPerformingScale;
    private float mSpan;
    private float mSpanDirection;
    private float mStartOffset;
    private float mSpanSpeed;
    private static final String TAG = "DisplayItem";

    public DisplayItem(MediaItem item) {
        mItemRef = item;
        mAnimatedImageTheta = item.mRotation;
        mImageTheta = item.mRotation;
        if (item == null)
            throw new UnsupportedOperationException("Cannot create a displayitem from a null MediaItem.");
        mCurrentSlotIndex = Shared.INVALID;
    }

    public DirectLinkedList.Entry<DisplayItem> getAnimatablesEntry() {
        return mAnimatablesEntry;
    }

    public final void rotateImageBy(float theta) {
        mImageTheta += theta;
    }

    public final void set(Vector3f position, int stackIndex, boolean performTransition) {
        mConvergenceSpeed = 1.0f;
        Vector3f animatedPosition = mAnimatedPosition;
        Vector3f targetPosition = mTargetPosition;
        int seed = stackIndex;
        int randomSeed = stackIndex;

        if (seed > 3) {
            seed = 3;
            randomSeed = 0;
        }

        if (!mAlive) {
            animatedPosition.set(position);
            animatedPosition.z = -3.0f + stackIndex * STACK_SPACING;
        }

        targetPosition.set(position);
        if (mStackId != stackIndex && stackIndex >= 0) {
            mStackId = stackIndex;
        }

        if (randomSeed == 0) {
            if (stackIndex == 0) {
                mTargetTheta = 0.0f;
            } else if (mTargetTheta == 0.0f){
                mTargetTheta = 30.0f * (0.5f - (float) Math.random());
            }
            mTargetPosition.z = seed * STACK_SPACING;
            mJitteredPosition.set(0, 0, seed * STACK_SPACING);
        } else {
            int sign = (seed % 2 == 0) ? 1 : -1;
            if (seed != 0 && !mStacktopPosition.equals(position) && mTargetTheta == 0) {
                mTargetTheta = 30.0f * (0.5f - (float) Math.random());
                mJitteredPosition.x = sign * 12.0f * seed + (0.5f - random.nextFloat()) * 4 * seed;
                mJitteredPosition.y = sign * 4 + ((sign == 1) ? -8.0f : sign * (random.nextFloat()) * 16.0f);
                mJitteredPosition.x *= App.PIXEL_DENSITY;
                mJitteredPosition.y *= App.PIXEL_DENSITY;
                mJitteredPosition.z = seed * STACK_SPACING;
            }
        }
        mTargetPosition.add(mJitteredPosition);
        mStacktopPosition.set(position);
        mStartOffset = 0.0f;
    }

    public int getStackIndex() {
        return mStackId;
    }

    public Texture getThumbnailImage(Context context, MediaItemTexture.Config config) {
        MediaItemTexture texture = mThumbnailImage;
        if (texture == null && config != null) {
            if (mItemRef.mId != Shared.INVALID) {
                texture = new MediaItemTexture(context, config, mItemRef);
            }
            mThumbnailImage = texture;
        }
        return texture;
    }

    public Texture getScreennailImage(Context context) {
        Texture texture = mScreennailImage;
        if (texture == null || texture.mState == Texture.STATE_ERROR) {
            MediaSet parentMediaSet = mItemRef.mParentMediaSet;
            if (parentMediaSet != null && parentMediaSet.mDataSource.getThumbnailCache() == LocalDataSource.sThumbnailCache) {
                if (mItemRef.mId != Shared.INVALID && mItemRef.mId != 0) {
                    texture = new MediaItemTexture(context, null, mItemRef);
                } else if (mItemRef.mContentUri != null) {
                    texture = new UriTexture(mItemRef.mContentUri);
                }
            } else {
                texture = new UriTexture(mItemRef.mScreennailUri);
                ((UriTexture) texture).setCacheId(Utils.Crc64Long(mItemRef.mFilePath));
            }
            mScreennailImage = texture;
        }
        return texture;
    }

    public void clearScreennailImage() {
        if (mScreennailImage != null) {
            mScreennailImage = null;
            mHiResImage = null;
        }
    }

    public void clearHiResImage() {
        mHiResImage = null;
    }

    public void clearThumbnail() {
        mThumbnailImage = null;
    }

    /**
     * Use this function to query the animation state of the display item
     * 
     * @return true if the display item is animating
     */
    public boolean isAnimating() {
        return mAlive
                && (mPerformingScale ||
                        !mAnimatedPosition.equals(mTargetPosition) || mAnimatedTheta != mTargetTheta
                        || mAnimatedImageTheta != mImageTheta || mAnimatedPlaceholderFade != 1f);
    }

    /**
     * This function should be called every time the frame needs to be updated.
     */
    public final void update(float timeElapsedInSec) {
        if (mAlive) {
            timeElapsedInSec *= 1.25f;
            Vector3f animatedPosition = mAnimatedPosition;
            Vector3f targetPosition = mTargetPosition;
            timeElapsedInSec *= mConvergenceSpeed;
            animatedPosition.x = FloatUtils.animate(animatedPosition.x, targetPosition.x, timeElapsedInSec);
            animatedPosition.y = FloatUtils.animate(animatedPosition.y, targetPosition.y, timeElapsedInSec);
            mAnimatedTheta = FloatUtils.animate(mAnimatedTheta, mTargetTheta, timeElapsedInSec);
            mAnimatedImageTheta = FloatUtils.animate(mAnimatedImageTheta, mImageTheta, timeElapsedInSec);
            mAnimatedPlaceholderFade = FloatUtils.animate(mAnimatedPlaceholderFade, 1f, timeElapsedInSec);
            animatedPosition.z = FloatUtils.animate(animatedPosition.z, targetPosition.z, timeElapsedInSec);
        }
    }

    /**
     * Commits all animations for the Display Item
     */
    public final void commit() {
        mAnimatedPosition.set(mTargetPosition);
        mAnimatedTheta = mTargetTheta;
        mAnimatedImageTheta = mImageTheta;
    }

    public final void setHasFocus(boolean hasFocus, boolean pushDown) {
        mConvergenceSpeed = 2.0f;
        mHasFocus = hasFocus;
        int seed = mStackId;
        if (seed > 3) {
            seed = 3;
        }
        if (hasFocus) {
            mTargetPosition.set(mStacktopPosition);
            mTargetPosition.add(mJitteredPosition);
            mTargetPosition.add(mJitteredPosition);
            mTargetPosition.z = seed * STACK_SPACING + (pushDown ? 1.0f : -0.5f);
        } else {
            mTargetPosition.set(mStacktopPosition);
            mTargetPosition.add(mJitteredPosition);
            mTargetPosition.z = seed * STACK_SPACING;
        }
    }

    public final void setSingleOffset(boolean useOffset, boolean pushAway, float x, float y, float z, float spreadValue) {
        int seed = mStackId;
        if (useOffset) {
            mTargetPosition.set(mStacktopPosition);
            if (spreadValue > 4.0f)
                spreadValue = 4.0f + 0.1f * spreadValue;
            if (spreadValue < 1.0f) {
                spreadValue = 1.0f / spreadValue;
                pushAway = true;
            }
            if (!pushAway) {
                if (seed == 0) {
                    mTargetPosition.add(0, -spreadValue * 14, 0);
                }
                if (seed == 1) {
                    mTargetPosition.add(-spreadValue * 32, 0, 0);
                }
                if (seed == 2) {
                    mTargetPosition.add(0, spreadValue * 14, 0);
                }
                if (seed == 3) {
                    mTargetPosition.add(spreadValue * 32, 0, 0);
                }
                mTargetPosition.z = -1.0f * spreadValue + seed * STACK_SPACING * spreadValue;
                mTargetTheta = 0.0f;
            } else {
                mTargetPosition.z = seed * STACK_SPACING + spreadValue * 0.5f;
            }
        } else {
            if (seed > 3) {
                seed = 3;
            }
            mTargetPosition.set(mStacktopPosition);
            mTargetPosition.add(mJitteredPosition);
            mTargetPosition.z = seed * STACK_SPACING;
            if (seed != 0 && mTargetTheta == 0.0f) {
                mTargetTheta = 30.0f * (0.5f - (float) Math.random());
            }
            mStartOffset = 0.0f;
        }
    }

    public final void setOffset(boolean useOffset, boolean pushDown, float span, float dx1, float dy1, float dx2, float dy2) {
        int seed = mStackId;
        if (useOffset) {
            mPerformingScale = true;
            float spanDelta = span - mSpan;
            float maxSlots = mItemRef.mParentMediaSet.getNumExpectedItems();
            maxSlots = FloatUtils.clamp(maxSlots, 0, GridLayer.MAX_ITEMS_PER_SLOT);
            if (Math.abs(spanDelta) < 10 * App.PIXEL_DENSITY) {
                // almost the same span
                mStartOffset += (mSpanDirection * mSpanSpeed);
                mStartOffset = FloatUtils.clamp(mStartOffset, 0, maxSlots);
            } else {
                mSpanSpeed = Math.abs(span / (600 * App.PIXEL_DENSITY));
                if (mSpanSpeed > 2.0f) {
                    mSpanSpeed = 2.0f;
                }
                mSpanSpeed *= 0.1f;
                mSpanDirection = Math.signum(spanDelta);
            }
            mSpan = span;
            mTargetPosition.set(mStacktopPosition);
            if (!pushDown) {
                if (maxSlots < 2)
                    return;
                // If it is the stacktop, we track the top finger, ie, x1, y1
                // else
                // we track bottom finger x2, y2
                // Instead of using linear interpolation, we will also try to
                // look at the spread value to decide how many move at a given
                // point of time.
                int maxSeedVal = (int)(span / (125 * App.PIXEL_DENSITY));
                maxSeedVal = (int)FloatUtils.clamp(maxSeedVal, 2, maxSlots - 1);
                float startOffset = FloatUtils.clamp(mStartOffset, 0, maxSlots - maxSeedVal - 1);
                float offsetSeed = seed - startOffset;
                float seedFactor = offsetSeed / maxSeedVal;
                seedFactor = FloatUtils.clamp(seedFactor, 0.0f, 1.0f);
                float dx = dx2 * seedFactor + (1.0f - seedFactor) * dx1;
                float dy = dy2 * seedFactor + (1.0f - seedFactor) * dy1;
                mTargetPosition.add(dx, dy, seed * 0.1f);
                mTargetTheta = 0.0f;
            } else {
                mStartOffset = 0.0f;
                mTargetPosition.z = seed * STACK_SPACING + 3.0f;
            }
        } else {
            mPerformingScale = false;
            mStartOffset = 0.0f;
            if (seed > 3) {
                seed = 3;
            }
            mTargetPosition.set(mStacktopPosition);
            mTargetPosition.add(mJitteredPosition);
            mTargetPosition.z = seed * STACK_SPACING;
            if (seed != 0 && mTargetTheta == 0.0f) {
                mTargetTheta = 30.0f * (0.5f - (float) Math.random());
            }
        }
    }

    public final boolean getHasFocus() {
        return mHasFocus;
    }

    public final Texture getHiResImage(Context context) {
        UriTexture texture = mHiResImage;
        if (texture == null) {
            texture = new UriTexture(mItemRef.mContentUri);
            texture.setCacheId(Utils.Crc64Long(mItemRef.mFilePath));
            mHiResImage = texture;
        }
        return texture;
    }

    public boolean isAlive() {
        return mAlive;
    }

    public float getImageTheta() {
        return mImageTheta;
    }
}