/*
 *  Copyright (c) 2010 The WebM project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */


#include <float.h>
#include <math.h>
#include <stdio.h>
#include "vpx_mem/vpx_mem.h"
#include "vpxscale_arbitrary.h"

extern BICUBIC_SCALER_STRUCT g_b_scaler;

int bicubic_scale_c64(int in_width, int in_height, int in_stride,
                      int out_width, int out_height, int out_stride,
                      unsigned char *input_image, unsigned char *output_image)
{
    short *restrict l_w, * restrict l_h;
    short *restrict c_w, * restrict c_h;
    unsigned char *restrict ip, * restrict op, *restrict op_w;
    unsigned char *restrict hbuf;
    int h, w, lw, lh;
    int phase_offset_w, phase_offset_h;
    double coeff;
    int max_phase;

    c_w = g_b_scaler.c_w;
    c_h = g_b_scaler.c_h;

    op = output_image;

    l_w = g_b_scaler.l_w;
    l_h = g_b_scaler.l_h;

    phase_offset_h = 0;

    for (h = 0; h < out_height; h++)
    {
        // select the row to work on
        lh = l_h[h];
        ip = input_image + (in_stride * lh);

        coeff = _memd8_const(&c_h[phase_offset_h*4]);

        // vp8_filter the row vertically into an temporary buffer.
        //  If the phase offset == 0 then all the multiplication
        //  is going to result in the output equalling the input.
        //  So instead point the temporary buffer to the input.
        //  Also handle the boundry condition of not being able to
        //  filter that last lines.
        if (phase_offset_h && (lh < in_height - 2))
        {
            hbuf = g_b_scaler.hbuf;

            for (w = 0; w < in_width; w += 4)
            {
                int ip1, ip2, ip3, ip4;
                int y13_12, y11_10, y23_22, y21_20, y33_32, y31_30, y43_42, y41_40;
                int y10_20, y11_21, y12_22, y13_23, y30_40, y31_41, y32_42, y33_43;
                int s1, s2, s3, s4;

                ip1 = _mem4_const(&ip[w - in_stride]);
                ip2 = _mem4_const(&ip[w]);
                ip3 = _mem4_const(&ip[w + in_stride]);
                ip4 = _mem4_const(&ip[w + 2*in_stride]);

                // realignment of data.  Unpack the data so that it is in short
                //  format instead of bytes.
                y13_12 = _unpkhu4(ip1);
                y11_10 = _unpklu4(ip1);
                y23_22 = _unpkhu4(ip2);
                y21_20 = _unpklu4(ip2);
                y33_32 = _unpkhu4(ip3);
                y31_30 = _unpklu4(ip3);
                y43_42 = _unpkhu4(ip4);
                y41_40 = _unpklu4(ip4);

                // repack the data so that elements 1 and 2 are together.  this
                //  lines up so that a dot product with the coefficients can be
                //  done.
                y10_20 = _pack2(y11_10, y21_20);
                y11_21 = _packh2(y11_10, y21_20);
                y12_22 = _pack2(y13_12, y23_22);
                y13_23 = _packh2(y13_12, y23_22);

                s1 = _dotp2(_hi(coeff), y10_20);
                s2 = _dotp2(_hi(coeff), y11_21);
                s3 = _dotp2(_hi(coeff), y12_22);
                s4 = _dotp2(_hi(coeff), y13_23);

                y30_40 = _pack2(y31_30, y41_40);
                y31_41 = _packh2(y31_30, y41_40);
                y32_42 = _pack2(y33_32, y43_42);
                y33_43 = _packh2(y33_32, y43_42);

                // now repack elements 3 and 4 together.
                s1 += _dotp2(_lo(coeff), y30_40);
                s2 += _dotp2(_lo(coeff), y31_41);
                s3 += _dotp2(_lo(coeff), y32_42);
                s4 += _dotp2(_lo(coeff), y33_43);

                s1 = s1 >> 12;
                s2 = s2 >> 12;
                s3 = s3 >> 12;
                s4 = s4 >> 12;

                s1 = _pack2(s2, s1);
                s2 = _pack2(s4, s3);

                _amem4(&hbuf[w])  = _spacku4(s2, s1);
            }
        }
        else
            hbuf = ip;

        // increase the phase offset for the next time around.
        if (++phase_offset_h >= g_b_scaler.nh)
            phase_offset_h = 0;

        op_w = op;

        // will never be able to interpolate first pixel, so just copy it
        // over here.
        phase_offset_w = 1;
        *op_w++ = hbuf[0];

        if (1 >= g_b_scaler.nw) phase_offset_w = 0;

        max_phase = g_b_scaler.nw;

        for (w = 1; w < out_width; w++)
        {
            double coefficients;
            int hbuf_high, hbuf_low, hbuf_both;
            int sum_high, sum_low, sum;

            // get the index to use to expand the image
            lw = l_w[w];
            coefficients = _amemd8_const(&c_w[phase_offset_w*4]);
            hbuf_both = _mem4_const(&hbuf[lw-1]);

            hbuf_high = _unpkhu4(hbuf_both);
            hbuf_low  = _unpklu4(hbuf_both);

            sum_high = _dotp2(_hi(coefficients), hbuf_high);
            sum_low  = _dotp2(_lo(coefficients), hbuf_low);

            sum = (sum_high + sum_low) >> 12;

            if (++phase_offset_w >= max_phase)
                phase_offset_w = 0;

            if ((lw + 2) >= in_width)
                sum = hbuf[lw];

            *op_w++ = sum;
        }

        op += out_stride;
    }

    return 0;
}

void bicubic_scale_frame_c64(YV12_BUFFER_CONFIG *src, YV12_BUFFER_CONFIG *dst,
                             int new_width, int new_height)
{

    dst->y_width = new_width;
    dst->y_height = new_height;
    dst->uv_width = new_width / 2;
    dst->uv_height = new_height / 2;

    dst->y_stride = dst->y_width;
    dst->uv_stride = dst->uv_width;

    bicubic_scale_c64(src->y_width, src->y_height, src->y_stride,
                      new_width, new_height, dst->y_stride,
                      src->y_buffer, dst->y_buffer);

    bicubic_scale_c64(src->uv_width, src->uv_height, src->uv_stride,
                      new_width / 2, new_height / 2, dst->uv_stride,
                      src->u_buffer, dst->u_buffer);

    bicubic_scale_c64(src->uv_width, src->uv_height, src->uv_stride,
                      new_width / 2, new_height / 2, dst->uv_stride,
                      src->v_buffer, dst->v_buffer);
}
