/*
 * Copyright (C) 2011 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.
 */

#include <stdlib.h>
#include "dbstabsmooth.h"

///// TODO TODO ////////// Replace this with the actual definition from Jayan's reply /////////////
#define vp_copy_motion_no_id vp_copy_motion
///////////////////////////////////////////////////////////////////////////////////////////////////

static bool vpmotion_add(VP_MOTION *in1, VP_MOTION *in2, VP_MOTION *out);
static bool vpmotion_multiply(VP_MOTION *in1, double factor, VP_MOTION *out);

db_StabilizationSmoother::db_StabilizationSmoother()
{
    Init();
}

void db_StabilizationSmoother::Init()
{
    f_smoothOn = true;
    f_smoothReset = false;
    f_smoothFactor = 1.0f;
    f_minDampingFactor = 0.2f;
    f_zoom = 1.0f;
    VP_MOTION_ID(f_motLF);
    VP_MOTION_ID(f_imotLF);
    f_hsize = 0;
    f_vsize = 0;

    VP_MOTION_ID(f_disp_mot);
    VP_MOTION_ID(f_src_mot);
    VP_MOTION_ID(f_diff_avg);

    for( int i = 0; i < MOTION_ARRAY-1; i++) {
        VP_MOTION_ID(f_hist_mot_speed[i]);
        VP_MOTION_ID(f_hist_mot[i]);
        VP_MOTION_ID(f_hist_diff_mot[i]);
    }
    VP_MOTION_ID(f_hist_mot[MOTION_ARRAY-1]);

}

db_StabilizationSmoother::~db_StabilizationSmoother()
{}


bool db_StabilizationSmoother::smoothMotion(VP_MOTION *inmot, VP_MOTION *outmot)
{
    VP_MOTION_ID(f_motLF);
    VP_MOTION_ID(f_imotLF);
    f_motLF.insid = inmot->refid;
    f_motLF.refid = inmot->insid;

    if(f_smoothOn) {
        if(!f_smoothReset) {
            MXX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXX(f_motLF) + (1.0-f_smoothFactor)* (double) MXX(*inmot));
            MXY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXY(f_motLF) + (1.0-f_smoothFactor)* (double) MXY(*inmot));
            MXZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXZ(f_motLF) + (1.0-f_smoothFactor)* (double) MXZ(*inmot));
            MXW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXW(f_motLF) + (1.0-f_smoothFactor)* (double) MXW(*inmot));

            MYX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYX(f_motLF) + (1.0-f_smoothFactor)* (double) MYX(*inmot));
            MYY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYY(f_motLF) + (1.0-f_smoothFactor)* (double) MYY(*inmot));
            MYZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYZ(f_motLF) + (1.0-f_smoothFactor)* (double) MYZ(*inmot));
            MYW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYW(f_motLF) + (1.0-f_smoothFactor)* (double) MYW(*inmot));

            MZX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZX(f_motLF) + (1.0-f_smoothFactor)* (double) MZX(*inmot));
            MZY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZY(f_motLF) + (1.0-f_smoothFactor)* (double) MZY(*inmot));
            MZZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZZ(f_motLF) + (1.0-f_smoothFactor)* (double) MZZ(*inmot));
            MZW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZW(f_motLF) + (1.0-f_smoothFactor)* (double) MZW(*inmot));

            MWX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWX(f_motLF) + (1.0-f_smoothFactor)* (double) MWX(*inmot));
            MWY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWY(f_motLF) + (1.0-f_smoothFactor)* (double) MWY(*inmot));
            MWZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWZ(f_motLF) + (1.0-f_smoothFactor)* (double) MWZ(*inmot));
            MWW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWW(f_motLF) + (1.0-f_smoothFactor)* (double) MWW(*inmot));
        }
        else
            vp_copy_motion_no_id(inmot, &f_motLF); // f_smoothFactor = 0.0

        // Only allow LF motion to be compensated. Remove HF motion from
        // the output transformation
        if(!vp_invert_motion(&f_motLF, &f_imotLF))
            return false;

        if(!vp_cascade_motion(&f_imotLF, inmot, outmot))
            return false;
    }
    else {
        vp_copy_motion_no_id(inmot, outmot);
    }

    return true;
}

bool db_StabilizationSmoother::smoothMotionAdaptive(/*VP_BIMG *bimg,*/int hsize, int vsize, VP_MOTION *inmot, VP_MOTION *outmot)
{
    VP_MOTION tmpMotion, testMotion;
    VP_PAR p1x, p2x, p3x, p4x;
    VP_PAR p1y, p2y, p3y, p4y;
    double smoothFactor;
    double minSmoothFactor = f_minDampingFactor;

//  int hsize = bimg->w;
//  int vsize = bimg->h;
    double border_factor = 0.01;//0.2;
    double border_x = border_factor * hsize;
    double border_y = border_factor * vsize;

    VP_MOTION_ID(f_motLF);
    VP_MOTION_ID(f_imotLF);
    VP_MOTION_ID(testMotion);
    VP_MOTION_ID(tmpMotion);

    if (f_smoothOn) {
        VP_MOTION identityMotion;
        VP_MOTION_ID(identityMotion); // initialize the motion
        vp_copy_motion(inmot/*in*/, &testMotion/*out*/);
        VP_PAR delta = vp_motion_cornerdiff(&testMotion, &identityMotion, 0, 0,(int)hsize, (int)vsize);

        smoothFactor = 0.99 - 0.0015 * delta;

        if(smoothFactor < minSmoothFactor)
            smoothFactor = minSmoothFactor;

        // Find the amount of motion that must be compensated so that no "border" pixels are seen in the stable video
        for (smoothFactor = smoothFactor; smoothFactor >= minSmoothFactor; smoothFactor -= 0.01) {
            // Compute the smoothed motion
            if(!smoothMotion(inmot, &tmpMotion, smoothFactor))
                break;

            // TmpMotion, or Qsi where s is the smoothed display reference and i is the
            // current image, tells us how points in the S co-ordinate system map to
            // points in the I CS.  We would like to check whether the four corners of the
            // warped and smoothed display reference lies entirely within the I co-ordinate
            // system.  If yes, then the amount of smoothing is sufficient so that NO
            // border pixels are seen at the output.  We test for f_smoothFactor terms
            // between 0.9 and 1.0, in steps of 0.01, and between 0.5 ands 0.9 in steps of 0.1

            (void) vp_zoom_motion2d(&tmpMotion, &testMotion, 1, hsize, vsize, (double)f_zoom); // needs to return bool

            VP_WARP_POINT_2D(0, 0, testMotion, p1x, p1y);
            VP_WARP_POINT_2D(hsize - 1, 0, testMotion, p2x, p2y);
            VP_WARP_POINT_2D(hsize - 1, vsize - 1, testMotion, p3x, p3y);
            VP_WARP_POINT_2D(0, vsize - 1, testMotion, p4x, p4y);

            if (!is_point_in_rect((double)p1x,(double)p1y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
                continue;
            }
            if (!is_point_in_rect((double)p2x, (double)p2y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
                continue;
            }
            if (!is_point_in_rect((double)p3x,(double)p3y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
                continue;
            }
            if (!is_point_in_rect((double)p4x, (double)p4y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
                continue;
            }

            // If we get here, then all the points are in the rectangle.
            // Therefore, break out of this loop
            break;
        }

        // if we get here and f_smoothFactor <= fMinDampingFactor, reset the stab reference
        if (smoothFactor < f_minDampingFactor)
            smoothFactor = f_minDampingFactor;

        // use the smoothed motion for stabilization
        vp_copy_motion_no_id(&tmpMotion/*in*/, outmot/*out*/);
    }
    else
    {
        vp_copy_motion_no_id(inmot, outmot);
    }

    return true;
}

bool db_StabilizationSmoother::smoothMotion(VP_MOTION *inmot, VP_MOTION *outmot, double smooth_factor)
{
    f_motLF.insid = inmot->refid;
    f_motLF.refid = inmot->insid;

    if(f_smoothOn) {
        if(!f_smoothReset) {
            MXX(f_motLF) = (VP_PAR) (smooth_factor*(double) MXX(f_motLF) + (1.0-smooth_factor)* (double) MXX(*inmot));
            MXY(f_motLF) = (VP_PAR) (smooth_factor*(double) MXY(f_motLF) + (1.0-smooth_factor)* (double) MXY(*inmot));
            MXZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MXZ(f_motLF) + (1.0-smooth_factor)* (double) MXZ(*inmot));
            MXW(f_motLF) = (VP_PAR) (smooth_factor*(double) MXW(f_motLF) + (1.0-smooth_factor)* (double) MXW(*inmot));

            MYX(f_motLF) = (VP_PAR) (smooth_factor*(double) MYX(f_motLF) + (1.0-smooth_factor)* (double) MYX(*inmot));
            MYY(f_motLF) = (VP_PAR) (smooth_factor*(double) MYY(f_motLF) + (1.0-smooth_factor)* (double) MYY(*inmot));
            MYZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MYZ(f_motLF) + (1.0-smooth_factor)* (double) MYZ(*inmot));
            MYW(f_motLF) = (VP_PAR) (smooth_factor*(double) MYW(f_motLF) + (1.0-smooth_factor)* (double) MYW(*inmot));

            MZX(f_motLF) = (VP_PAR) (smooth_factor*(double) MZX(f_motLF) + (1.0-smooth_factor)* (double) MZX(*inmot));
            MZY(f_motLF) = (VP_PAR) (smooth_factor*(double) MZY(f_motLF) + (1.0-smooth_factor)* (double) MZY(*inmot));
            MZZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MZZ(f_motLF) + (1.0-smooth_factor)* (double) MZZ(*inmot));
            MZW(f_motLF) = (VP_PAR) (smooth_factor*(double) MZW(f_motLF) + (1.0-smooth_factor)* (double) MZW(*inmot));

            MWX(f_motLF) = (VP_PAR) (smooth_factor*(double) MWX(f_motLF) + (1.0-smooth_factor)* (double) MWX(*inmot));
            MWY(f_motLF) = (VP_PAR) (smooth_factor*(double) MWY(f_motLF) + (1.0-smooth_factor)* (double) MWY(*inmot));
            MWZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MWZ(f_motLF) + (1.0-smooth_factor)* (double) MWZ(*inmot));
            MWW(f_motLF) = (VP_PAR) (smooth_factor*(double) MWW(f_motLF) + (1.0-smooth_factor)* (double) MWW(*inmot));
        }
        else
            vp_copy_motion_no_id(inmot, &f_motLF); // smooth_factor = 0.0

        // Only allow LF motion to be compensated. Remove HF motion from
        // the output transformation
        if(!vp_invert_motion(&f_motLF, &f_imotLF))
            return false;

        if(!vp_cascade_motion(&f_imotLF, inmot, outmot))
            return false;
    }
    else {
        vp_copy_motion_no_id(inmot, outmot);
    }

    return true;
}

//! Overloaded smoother function that takes in user-specidied smoothing factor
bool
db_StabilizationSmoother::smoothMotion1(VP_MOTION *inmot, VP_MOTION *outmot, VP_MOTION *motLF, VP_MOTION *imotLF, double factor)
{

    if(!f_smoothOn) {
        vp_copy_motion(inmot, outmot);
        return true;
    }
    else {
        if(!f_smoothReset) {
            MXX(*motLF) = (VP_PAR) (factor*(double) MXX(*motLF) + (1.0-factor)* (double) MXX(*inmot));
            MXY(*motLF) = (VP_PAR) (factor*(double) MXY(*motLF) + (1.0-factor)* (double) MXY(*inmot));
            MXZ(*motLF) = (VP_PAR) (factor*(double) MXZ(*motLF) + (1.0-factor)* (double) MXZ(*inmot));
            MXW(*motLF) = (VP_PAR) (factor*(double) MXW(*motLF) + (1.0-factor)* (double) MXW(*inmot));

            MYX(*motLF) = (VP_PAR) (factor*(double) MYX(*motLF) + (1.0-factor)* (double) MYX(*inmot));
            MYY(*motLF) = (VP_PAR) (factor*(double) MYY(*motLF) + (1.0-factor)* (double) MYY(*inmot));
            MYZ(*motLF) = (VP_PAR) (factor*(double) MYZ(*motLF) + (1.0-factor)* (double) MYZ(*inmot));
            MYW(*motLF) = (VP_PAR) (factor*(double) MYW(*motLF) + (1.0-factor)* (double) MYW(*inmot));

            MZX(*motLF) = (VP_PAR) (factor*(double) MZX(*motLF) + (1.0-factor)* (double) MZX(*inmot));
            MZY(*motLF) = (VP_PAR) (factor*(double) MZY(*motLF) + (1.0-factor)* (double) MZY(*inmot));
            MZZ(*motLF) = (VP_PAR) (factor*(double) MZZ(*motLF) + (1.0-factor)* (double) MZZ(*inmot));
            MZW(*motLF) = (VP_PAR) (factor*(double) MZW(*motLF) + (1.0-factor)* (double) MZW(*inmot));

            MWX(*motLF) = (VP_PAR) (factor*(double) MWX(*motLF) + (1.0-factor)* (double) MWX(*inmot));
            MWY(*motLF) = (VP_PAR) (factor*(double) MWY(*motLF) + (1.0-factor)* (double) MWY(*inmot));
            MWZ(*motLF) = (VP_PAR) (factor*(double) MWZ(*motLF) + (1.0-factor)* (double) MWZ(*inmot));
            MWW(*motLF) = (VP_PAR) (factor*(double) MWW(*motLF) + (1.0-factor)* (double) MWW(*inmot));
        }
        else {
            vp_copy_motion(inmot, motLF);
        }
        // Only allow LF motion to be compensated. Remove HF motion from the output transformation
        if(!vp_invert_motion(motLF, imotLF)) {
#if DEBUG_PRINT
            printfOS("Invert failed \n");
#endif
            return false;
        }
        if(!vp_cascade_motion(imotLF, inmot, outmot)) {
#if DEBUG_PRINT
            printfOS("cascade failed \n");
#endif
            return false;
        }
    }
    return true;
}




bool db_StabilizationSmoother::is_point_in_rect(double px, double py, double rx, double ry, double w, double h)
{
    if (px < rx)
        return(false);
    if (px >= rx + w)
        return(false);
    if (py < ry)
        return(false);
    if (py >= ry + h)
        return(false);

    return(true);
}



static bool vpmotion_add(VP_MOTION *in1, VP_MOTION *in2, VP_MOTION *out)
{
    int i;
    if(in1 == NULL || in2 == NULL || out == NULL)
        return false;

    for(i = 0; i < VP_MAX_MOTION_PAR; i++)
        out->par[i] = in1->par[i] + in2->par[i];

    return true;
}

static bool vpmotion_multiply(VP_MOTION *in1, double factor, VP_MOTION *out)
{
    int i;
    if(in1 == NULL || out == NULL)
        return false;

    for(i = 0; i < VP_MAX_MOTION_PAR; i++)
        out->par[i] = in1->par[i] * factor;

    return true;
}