/*
 * 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.hardware.camera2.params;

import static com.android.internal.util.Preconditions.*;

/**
 * Immutable class to store a 4-element vector of floats indexable by a bayer RAW 2x2 pixel block.
 */
public final class RggbChannelVector {
    /**
     * The number of color channels in this vector.
     */
    public static final int COUNT = 4;

    /** Red color channel in a bayer Raw pattern. */
    public static final int RED = 0;

    /** Green color channel in a bayer Raw pattern used by the even rows. */
    public static final int GREEN_EVEN = 1;

    /** Green color channel in a bayer Raw pattern used by the odd rows. */
    public static final int GREEN_ODD = 2;

    /** Blue color channel in a bayer Raw pattern. */
    public static final int BLUE = 3;

    /**
     * Create a new {@link RggbChannelVector} from an RGGB 2x2 pixel.
     *
     * <p>All pixel values are considered normalized within {@code [0.0f, 1.0f]}
     * (i.e. {@code 1.0f} could be linearized to {@code 255} if converting to a
     * non-floating point pixel representation).</p>
     *
     * <p>All arguments must be finite; NaN and infinity is not allowed.</p>
     *
     * @param red red pixel
     * @param greenEven green pixel (even row)
     * @param greenOdd green pixel (odd row)
     * @param blue blue pixel
     *
     * @throws IllegalArgumentException if any of the arguments were not finite
     */
    public RggbChannelVector(final float red, final float greenEven, final float greenOdd,
            final float blue) {
        mRed = checkArgumentFinite(red, "red");
        mGreenEven = checkArgumentFinite(greenEven, "greenEven");
        mGreenOdd = checkArgumentFinite(greenOdd, "greenOdd");
        mBlue = checkArgumentFinite(blue, "blue");
    }

    /**
     * Get the red component.
     *
     * @return a floating point value (guaranteed to be finite)
     */
    public final float getRed() {
        return mRed;
    }

    /**
     * Get the green (even rows) component.
     *
     * @return a floating point value (guaranteed to be finite)
     */
    public float getGreenEven() {
        return mGreenEven;
    }

    /**
     * Get the green (odd rows) component.
     *
     * @return a floating point value (guaranteed to be finite)
     */
    public float getGreenOdd() {
        return mGreenOdd;
    }

    /**
     * Get the blue component.
     *
     * @return a floating point value (guaranteed to be finite)
     */
    public float getBlue() {
        return mBlue;
    }

    /**
     * Get the component by the color channel index.
     *
     * <p>{@code colorChannel} must be one of {@link #RED}, {@link #GREEN_EVEN}, {@link #GREEN_ODD},
     * {@link #BLUE}.</p>
     *
     * @param colorChannel greater or equal to {@code 0} and less than {@link #COUNT}
     * @return a floating point value (guaranteed to be finite)
     *
     * @throws IllegalArgumentException if {@code colorChannel} was out of range
     */
    public float getComponent(final int colorChannel) {
        if (colorChannel < 0 || colorChannel >= COUNT) {
            throw new IllegalArgumentException("Color channel out of range");
        }

        switch (colorChannel) {
            case RED:
                return mRed;
            case GREEN_EVEN:
                return mGreenEven;
            case GREEN_ODD:
                return mGreenOdd;
            case BLUE:
                return mBlue;
            default:
                throw new AssertionError("Unhandled case " + colorChannel);
        }
    }

    /**
     * Copy the vector into the destination in the order {@code [R, Geven, Godd, B]}.
     *
     * @param destination
     *          an array big enough to hold at least {@value #COUNT} elements after the
     *          {@code offset}
     * @param offset
     *          a non-negative offset into the array
     *
     * @throws NullPointerException
     *          If {@code destination} was {@code null}
     * @throws ArrayIndexOutOfBoundsException
     *          If there's not enough room to write the elements at the specified destination and
     *          offset.
     */
    public void copyTo(final float[] destination, final int offset) {
        checkNotNull(destination, "destination must not be null");
        if (destination.length + offset < COUNT) {
            throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
        }

        destination[offset + RED] = mRed;
        destination[offset + GREEN_EVEN] = mGreenEven;
        destination[offset + GREEN_ODD] = mGreenOdd;
        destination[offset + BLUE] = mBlue;
    }

    /**
     * Check if this {@link RggbChannelVector} is equal to another {@link RggbChannelVector}.
     *
     * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
     *
     * @return {@code true} if the objects were equal, {@code false} otherwise
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (obj instanceof RggbChannelVector) {
            final RggbChannelVector other = (RggbChannelVector) obj;
            return mRed == other.mRed &&
                    mGreenEven == other.mGreenEven &&
                    mGreenOdd == other.mGreenOdd &&
                    mBlue == other.mBlue;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return Float.floatToIntBits(mRed) ^
                Float.floatToIntBits(mGreenEven) ^
                Float.floatToIntBits(mGreenOdd) ^
                Float.floatToIntBits(mBlue);
    }

    private final float mRed;
    private final float mGreenEven;
    private final float mGreenOdd;
    private final float mBlue;
}
