/*
 * 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 android.view.inputmethod;

import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Arrays;

/**
 * An implementation of SparseArray specialized for {@link android.graphics.RectF}.
 * <p>
 * As this is a sparse array, it represents an array of {@link RectF} most of which are null. This
 * class could be in some other packages like android.graphics or android.util but currently
 * belong to android.view.inputmethod because this class is hidden and used only in input method
 * framework.
 * </p>
 * @hide
 */
public final class SparseRectFArray implements Parcelable {
    /**
     * The keys, in ascending order, of those {@link RectF} that are not null. For example,
     * {@code [null, null, null, Rect1, null, Rect2]} would be represented by {@code [3,5]}.
     * @see #mCoordinates
     */
    private final int[] mKeys;

    /**
     * Stores coordinates of the rectangles, in the order of
     * {@code rects[mKeys[0]].left}, {@code rects[mKeys[0]].top},
     * {@code rects[mKeys[0]].right}, {@code rects[mKeys[0]].bottom},
     * {@code rects[mKeys[1]].left}, {@code rects[mKeys[1]].top},
     * {@code rects[mKeys[1]].right}, {@code rects[mKeys[1]].bottom},
     * {@code rects[mKeys[2]].left}, {@code rects[mKeys[2]].top}, ....
     */
    private final float[] mCoordinates;

    public SparseRectFArray(final Parcel source) {
        mKeys = source.createIntArray();
        mCoordinates = source.createFloatArray();
    }

    /**
     * Used to package this object into a {@link Parcel}.
     *
     * @param dest The {@link Parcel} to be written.
     * @param flags The flags used for parceling.
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeIntArray(mKeys);
        dest.writeFloatArray(mCoordinates);
    }

    @Override
    public int hashCode() {
        // TODO: Improve the hash function.
        if (mKeys == null || mKeys.length == 0) {
            return 0;
        }
        int hash = mKeys.length;
        // For performance reasons, only the first rectangle is used for the hash code now.
        for (int i = 0; i < 4; i++) {
            hash *= 31;
            hash += mCoordinates[i];
        }
        return hash;
    }

    @Override
    public boolean equals(Object obj){
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof SparseRectFArray)) {
            return false;
        }
        final SparseRectFArray that = (SparseRectFArray) obj;

        return Arrays.equals(mKeys, that.mKeys) && Arrays.equals(mCoordinates, that.mCoordinates);
    }

    @Override
    public String toString() {
        if (mKeys == null || mCoordinates == null) {
            return "SparseRectFArray{}";
        }
        final StringBuilder sb = new StringBuilder();
        sb.append("SparseRectFArray{");
        for (int i = 0; i < mKeys.length; i++) {
            if (i != 0) {
                sb.append(", ");
            }
            final int baseIndex = i * 4;
            sb.append(mKeys[i]);
            sb.append(":[");
            sb.append(mCoordinates[baseIndex + 0]);
            sb.append(",");
            sb.append(mCoordinates[baseIndex + 1]);
            sb.append("],[");
            sb.append(mCoordinates[baseIndex + 2]);
            sb.append(",");
            sb.append(mCoordinates[baseIndex + 3]);
            sb.append("]");
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Builder for {@link SparseRectFArray}. This class is not designed to be thread-safe.
     * @hide
     */
    public static final class SparseRectFArrayBuilder {
        /**
         * Throws {@link IllegalArgumentException} to make sure that this class is correctly used.
         * @param key key to be checked.
         */
        private void checkIndex(final int key) {
            if (mCount == 0) {
                return;
            }
            if (mKeys[mCount - 1] >= key) {
                throw new IllegalArgumentException("key must be greater than all existing keys.");
            }
        }

        /**
         * Extends the internal array if necessary.
         */
        private void ensureBufferSize() {
            if (mKeys == null) {
                mKeys = new int[INITIAL_SIZE];
            }
            if (mCoordinates == null) {
                mCoordinates = new float[INITIAL_SIZE * 4];
            }
            final int requiredIndexArraySize = mCount + 1;
            if (mKeys.length <= requiredIndexArraySize) {
                final int[] newArray = new int[requiredIndexArraySize * 2];
                System.arraycopy(mKeys, 0, newArray, 0, mCount);
                mKeys = newArray;
            }
            final int requiredCoordinatesArraySize = (mCount + 1) * 4;
            if (mCoordinates.length <= requiredCoordinatesArraySize) {
                final float[] newArray = new float[requiredCoordinatesArraySize * 2];
                System.arraycopy(mCoordinates, 0, newArray, 0, mCount * 4);
                mCoordinates = newArray;
            }
        }

        /**
         * Puts the rectangle with an integer key.
         * @param key the key to be associated with the rectangle. It must be greater than all
         * existing keys that have been previously specified.
         * @param left left of the rectangle.
         * @param top top of the rectangle.
         * @param right right of the rectangle.
         * @param bottom bottom of the rectangle.
         * @return the receiver object itself for chaining method calls.
         * @throws IllegalArgumentException If the index is not greater than all of existing keys.
         */
        public SparseRectFArrayBuilder append(final int key,
                final float left, final float top, final float right, final float bottom) {
            checkIndex(key);
            ensureBufferSize();
            final int baseCoordinatesIndex = mCount * 4;
            mCoordinates[baseCoordinatesIndex + 0] = left;
            mCoordinates[baseCoordinatesIndex + 1] = top;
            mCoordinates[baseCoordinatesIndex + 2] = right;
            mCoordinates[baseCoordinatesIndex + 3] = bottom;
            mKeys[mCount] = key;
            ++mCount;
            return this;
        }
        private int mCount = 0;
        private int[] mKeys = null;
        private float[] mCoordinates = null;
        private static int INITIAL_SIZE = 16;

        /**
         * @return {@link SparseRectFArray} using parameters in this {@link SparseRectFArray}.
         */
        public SparseRectFArray build() {
            return new SparseRectFArray(this);
        }

        public void reset() {
            if (mCount == 0) {
                mKeys = null;
                mCoordinates = null;
            }
            mCount = 0;
        }
    }

    private SparseRectFArray(final SparseRectFArrayBuilder builder) {
        if (builder.mCount == 0) {
            mKeys = null;
            mCoordinates = null;
        } else {
            mKeys = new int[builder.mCount];
            mCoordinates = new float[builder.mCount * 4];
            System.arraycopy(builder.mKeys, 0, mKeys, 0, builder.mCount);
            System.arraycopy(builder.mCoordinates, 0, mCoordinates, 0, builder.mCount * 4);
        }
    }

    public RectF get(final int index) {
        if (mKeys == null) {
            return null;
        }
        if (index < 0) {
            return null;
        }
        final int arrayIndex = Arrays.binarySearch(mKeys, index);
        if (arrayIndex < 0) {
            return null;
        }
        final int baseCoordIndex = arrayIndex * 4;
        return new RectF(mCoordinates[baseCoordIndex],
                mCoordinates[baseCoordIndex + 1],
                mCoordinates[baseCoordIndex + 2],
                mCoordinates[baseCoordIndex + 3]);
    }

    /**
     * Used to make this class parcelable.
     */
    public static final Parcelable.Creator<SparseRectFArray> CREATOR =
            new Parcelable.Creator<SparseRectFArray>() {
                @Override
                public SparseRectFArray createFromParcel(Parcel source) {
                    return new SparseRectFArray(source);
                }
                @Override
                public SparseRectFArray[] newArray(int size) {
                    return new SparseRectFArray[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }
}

