/*
 * Copyright 2013 ZXing authors
 *
 * 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.
 */

namespace ZXing
{
    /// <summary>
    /// A wrapper implementation of {@link LuminanceSource} which inverts the luminances it returns -- black becomes
    /// white and vice versa, and each value becomes (255-value).
    /// </summary>
    /// <author>Sean Owen</author>
    public sealed class InvertedLuminanceSource : LuminanceSource
    {
        private readonly LuminanceSource @delegate;
        private byte[] invertedMatrix;

        /// <summary>
        /// Initializes a new instance of the <see cref="InvertedLuminanceSource"/> class.
        /// </summary>
        /// <param name="delegate">The @delegate.</param>
        public InvertedLuminanceSource(LuminanceSource @delegate)
           : base(@delegate.Width, @delegate.Height)
        {
            this.@delegate = @delegate;
        }

        /// <summary>
        /// Fetches one row of luminance data from the underlying platform's bitmap. Values range from
        /// 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
        /// to bitwise and with 0xff for each value. It is preferable for implementations of this method
        /// to only fetch this row rather than the whole image, since no 2D Readers may be installed and
        /// getMatrix() may never be called.
        /// </summary>
        /// <param name="y">The row to fetch, 0 &lt;= y &lt; Height.</param>
        /// <param name="row">An optional preallocated array. If null or too small, it will be ignored.
        /// Always use the returned object, and ignore the .length of the array.</param>
        /// <returns>
        /// An array containing the luminance data.
        /// </returns>
        override public byte[] getRow(int y, byte[] row)
        {
            row = @delegate.getRow(y, row);
            int width = Width;
            for (int i = 0; i < width; i++)
            {
                row[i] = (byte)(255 - (row[i] & 0xFF));
            }
            return row;
        }

        /// <summary>
        /// Fetches luminance data for the underlying bitmap. Values should be fetched using:
        /// int luminance = array[y * width + x] &amp; 0xff;
        /// </summary>
        /// <returns> A row-major 2D array of luminance values. Do not use result.length as it may be
        /// larger than width * height bytes on some platforms. Do not modify the contents
        /// of the result.
        ///   </returns>
        override public byte[] Matrix
        {
            get
            {
                if (invertedMatrix == null)
                {
                    byte[] matrix = @delegate.Matrix;
                    int length = Width * Height;
                    invertedMatrix = new byte[length];
                    for (int i = 0; i < length; i++)
                    {
                        invertedMatrix[i] = (byte)(255 - (matrix[i] & 0xFF));
                    }
                }
                return invertedMatrix;
            }
        }

        /// <summary>
        /// </summary>
        /// <returns> Whether this subclass supports cropping.</returns>
        override public bool CropSupported
        {
            get { return @delegate.CropSupported; }
        }

        /// <summary>
        /// Returns a new object with cropped image data. Implementations may keep a reference to the
        /// original data rather than a copy. Only callable if CropSupported is true.
        /// </summary>
        /// <param name="left">The left coordinate, 0 &lt;= left &lt; Width.</param>
        /// <param name="top">The top coordinate, 0 &lt;= top &lt;= Height.</param>
        /// <param name="width">The width of the rectangle to crop.</param>
        /// <param name="height">The height of the rectangle to crop.</param>
        /// <returns>
        /// A cropped version of this object.
        /// </returns>
        override public LuminanceSource crop(int left, int top, int width, int height)
        {
            return new InvertedLuminanceSource(@delegate.crop(left, top, width, height));
        }

        /// <summary>
        /// </summary>
        /// <returns> Whether this subclass supports counter-clockwise rotation.</returns>
        override public bool RotateSupported
        {
            get { return @delegate.RotateSupported; }
        }

        /// <summary>
        /// Inverts this instance.
        /// </summary>
        /// <returns>original delegate {@link LuminanceSource} since invert undoes itself</returns>
        override public LuminanceSource invert()
        {
            return @delegate;
        }

        /// <summary>
        /// Returns a new object with rotated image data by 90 degrees counterclockwise.
        /// Only callable if {@link #isRotateSupported()} is true.
        /// </summary>
        /// <returns>
        /// A rotated version of this object.
        /// </returns>
        override public LuminanceSource rotateCounterClockwise()
        {
            return new InvertedLuminanceSource(@delegate.rotateCounterClockwise());
        }

        /// <summary>
        /// Returns a new object with rotated image data by 45 degrees counterclockwise.
        /// Only callable if {@link #isRotateSupported()} is true.
        /// </summary>
        /// <returns>
        /// A rotated version of this object.
        /// </returns>
        override public LuminanceSource rotateCounterClockwise45()
        {
            return new InvertedLuminanceSource(@delegate.rotateCounterClockwise45());
        }
    }
}