/*
 * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of The Linux Foundation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <errno.h>
#include <stdlib.h>

#include <log/log.h>

#include "software_converter.h"

/** Convert YV12 to YCrCb_420_SP */
int convertYV12toYCrCb420SP(const copybit_image_t *src, private_handle_t *yv12_handle)
{
    private_handle_t* hnd = (private_handle_t*)src->handle;

    if(hnd == NULL || yv12_handle == NULL){
        ALOGE("Invalid handle");
        return -1;
    }

    // Please refer to the description of YV12 in hardware.h
    // for the formulae used to calculate buffer sizes and offsets

    // In a copybit_image_t, w is the stride and
    // stride - horiz_padding is the actual width
    // vertical stride is the same as height, so not considered
    unsigned int   stride  = src->w;
    unsigned int   width   = src->w - src->horiz_padding;
    unsigned int   height  = src->h;
    unsigned int   y_size  = stride * src->h;
    unsigned int   c_width = ALIGN(stride/2, (unsigned int)16);
    unsigned int   c_size  = c_width * src->h/2;
    unsigned int   chromaPadding = c_width - width/2;
    unsigned int   chromaSize = c_size * 2;
    unsigned char* newChroma = (unsigned char *)(yv12_handle->base + y_size);
    unsigned char* oldChroma = (unsigned char*)(hnd->base + y_size);
    memcpy((char *)yv12_handle->base,(char *)hnd->base,y_size);

#if defined(__ARM_HAVE_NEON) && !defined(__aarch64__)
   /* interleave */
    if(!chromaPadding) {
        unsigned char * t1 = newChroma;
        unsigned char * t2 = oldChroma;
        unsigned char * t3 = t2 + chromaSize/2;
        for(unsigned int i=0; i < (chromaSize/2)>>3; i++) {
            __asm__ __volatile__ (
                                    "vld1.u8 d0, [%0]! \n"
                                    "vld1.u8 d1, [%1]! \n"
                                    "vst2.u8 {d0, d1}, [%2]! \n"
                                    :"+r"(t2), "+r"(t3), "+r"(t1)
                                    :
                                    :"memory","d0","d1"
                                 );

        }
    }
#else  //__ARM_HAVE_NEON
    if(!chromaPadding) {
        for(unsigned int i = 0; i< chromaSize/2; i++) {
            newChroma[i*2]   = oldChroma[i];
            newChroma[i*2+1] = oldChroma[i+chromaSize/2];
        }

    }
#endif
    // If the image is not aligned to 16 pixels,
    // convert using the C routine below
    // r1 tracks the row of the source buffer
    // r2 tracks the row of the destination buffer
    // The width/2 checks are to avoid copying
    // from the padding

    if(chromaPadding) {
        unsigned int r1 = 0, r2 = 0, i = 0, j = 0;
        while(r1 < height/2) {
            if(j == width) {
                j = 0;
                r2++;
                continue;
            }
            if (j+1 == width) {
                newChroma[r2*width + j] = oldChroma[r1*c_width+i];
                r2++;
                newChroma[r2*width] = oldChroma[r1*c_width+i+c_size];
                j = 1;
            } else {
                newChroma[r2*width + j] = oldChroma[r1*c_width+i];
                newChroma[r2*width + j + 1] = oldChroma[r1*c_width+i+c_size];
                j+=2;
            }
            i++;
            if (i == width/2 ) {
                i = 0;
                r1++;
            }
        }
    }

  return 0;
}

struct copyInfo{
    int width;
    int height;
    int src_stride;
    int dst_stride;
    size_t src_plane1_offset;
    size_t src_plane2_offset;
    size_t dst_plane1_offset;
    size_t dst_plane2_offset;
};

/* Internal function to do the actual copy of source to destination */
static int copy_source_to_destination(const uintptr_t src_base,
                                      const uintptr_t dst_base,
                                      copyInfo& info)
{
    if (!src_base || !dst_base) {
        ALOGE("%s: invalid memory src_base = 0x%p dst_base=0x%p",
             __FUNCTION__, (void*)src_base, (void*)dst_base);
         return COPYBIT_FAILURE;
    }

    int width = info.width;
    int height = info.height;
    unsigned char *src = (unsigned char*)src_base;
    unsigned char *dst = (unsigned char*)dst_base;

    // Copy the luma
    for (int i = 0; i < height; i++) {
        memcpy(dst, src, width);
        src += info.src_stride;
        dst += info.dst_stride;
    }

    // Copy plane 1
    src = (unsigned char*)(src_base + info.src_plane1_offset);
    dst = (unsigned char*)(dst_base + info.dst_plane1_offset);
    width = width/2;
    height = height/2;
    for (int i = 0; i < height; i++) {
        memcpy(dst, src, info.src_stride);
        src += info.src_stride;
        dst += info.dst_stride;
    }
    return 0;
}


/*
 * Function to convert the c2d format into an equivalent Android format
 *
 * @param: source buffer handle
 * @param: destination image
 *
 * @return: return status
 */
int convert_yuv_c2d_to_yuv_android(private_handle_t *hnd,
                                   struct copybit_image_t const *rhs)
{
    ALOGD("Enter %s", __FUNCTION__);
    if (!hnd || !rhs) {
        ALOGE("%s: invalid inputs hnd=%p rhs=%p", __FUNCTION__, hnd, rhs);
        return COPYBIT_FAILURE;
    }

    int ret = COPYBIT_SUCCESS;
    private_handle_t *dst_hnd = (private_handle_t *)rhs->handle;

    copyInfo info;
    info.width = rhs->w;
    info.height = rhs->h;
    info.src_stride = ALIGN(info.width, 32);
    info.dst_stride = ALIGN(info.width, 16);
    switch(rhs->format) {
        case HAL_PIXEL_FORMAT_YCbCr_420_SP:
        case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
            info.src_plane1_offset = info.src_stride*info.height;
            info.dst_plane1_offset = info.dst_stride*info.height;
        } break;
        case HAL_PIXEL_FORMAT_NV12_ENCODEABLE: {
            // Chroma is 2K aligned for the NV12 encodeable format.
            info.src_plane1_offset = ALIGN(info.src_stride*info.height, 2048);
            info.dst_plane1_offset = ALIGN(info.dst_stride*info.height, 2048);
        } break;
        default:
            ALOGE("%s: unsupported format (format=0x%x)", __FUNCTION__,
                 rhs->format);
            return COPYBIT_FAILURE;
    }

    ret = copy_source_to_destination((uintptr_t) hnd->base, (uintptr_t) dst_hnd->base, info);
    return ret;
}

/*
 * Function to convert the Android format into an equivalent C2D format
 *
 * @param: source buffer handle
 * @param: destination image
 *
 * @return: return status
 */
int convert_yuv_android_to_yuv_c2d(private_handle_t *hnd,
                                   struct copybit_image_t const *rhs)
{
    if (!hnd || !rhs) {
        ALOGE("%s: invalid inputs hnd=%p rhs=%p", __FUNCTION__, hnd, rhs);
        return COPYBIT_FAILURE;
    }

    int ret = COPYBIT_SUCCESS;
    private_handle_t *dst_hnd = (private_handle_t *)rhs->handle;

    copyInfo info;
    info.width = rhs->w;
    info.height = rhs->h;
    info.src_stride = ALIGN(hnd->width, 16);
    info.dst_stride = ALIGN(info.width, 32);
    switch(rhs->format) {
        case HAL_PIXEL_FORMAT_YCbCr_420_SP:
        case HAL_PIXEL_FORMAT_YCrCb_420_SP: {
            info.src_plane1_offset = info.src_stride*info.height;
            info.dst_plane1_offset = info.dst_stride*info.height;
        } break;
        case HAL_PIXEL_FORMAT_NV12_ENCODEABLE: {
            // Chroma is 2K aligned for the NV12 encodeable format.
            info.src_plane1_offset = ALIGN(info.src_stride*info.height, 2048);
            info.dst_plane1_offset = ALIGN(info.dst_stride*info.height, 2048);
        } break;
        default:
            ALOGE("%s: unsupported format (format=0x%x)", __FUNCTION__,
                 rhs->format);
            return -1;
    }

    ret = copy_source_to_destination((uintptr_t) hnd->base, (uintptr_t) dst_hnd->base, info);
    return ret;
}