/******************************************************************************
 * Project:  GDAL
 * Purpose:  Correlator
 * Author:   Andrew Migal, migal.drew@gmail.com
 *
 ******************************************************************************
 * Copyright (c) 2012, Andrew Migal
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "gdal_simplesurf.h"

CPL_CVSID("$Id");

/************************************************************************/
/* ==================================================================== */
/*                          GDALIntegralImage                           */
/* ==================================================================== */
/************************************************************************/

GDALIntegralImage::GDALIntegralImage()
{
    pMatrix = 0;
    nHeight = 0;
    nWidth = 0;
}

int GDALIntegralImage::GetHeight() { return nHeight; }

int GDALIntegralImage::GetWidth() { return nWidth; }

void GDALIntegralImage::Initialize(const double **padfImg, int nHeight, int nWidth)
{
    //Memory allocation
    pMatrix = new double*[nHeight];
    for (int i = 0; i < nHeight; i++)
        pMatrix[i] = new double[nWidth];

    this->nHeight = nHeight;
    this->nWidth = nWidth;

    //Integral image calculation
    for (int i = 0; i < nHeight; i++)
        for (int j = 0; j < nWidth; j++)
        {
            double val = padfImg[i][j];
            double a = 0, b = 0, c = 0;

            if (i - 1 >= 0 && j - 1 >= 0)
                a = pMatrix[i - 1][j - 1];
            if (j - 1 >= 0)
                b = pMatrix[i][j - 1];
            if (i - 1 >= 0)
                c = pMatrix[i - 1][j];

            //New value based on previous calculations
            pMatrix[i][j] = val - a + b + c;
        }
}

/*
 * Returns value of specified cell
 */
double GDALIntegralImage::GetValue(int nRow, int nCol)
{
    if ((nRow >= 0 && nRow < nHeight) && (nCol >= 0 && nCol < nWidth))
        return pMatrix[nRow][nCol];
    else
        return 0;
}

double GDALIntegralImage::GetRectangleSum(int nRow, int nCol, int nWidth, int nHeight)
{
    double a = 0, b = 0, c = 0, d = 0;

    //Left top point of rectangle is first
    int w = nWidth - 1;
    int h = nHeight - 1;

    int row = nRow;
    int col = nCol;

    //Left top point
    int lt_row = (row <= this->nHeight) ? (row - 1) : -1;
    int lt_col = (col <= this->nWidth) ? (col - 1) : -1;
    //Right bottom point of the rectangle
    int rb_row = (row + h < this->nHeight) ? (row + h) : (this->nHeight - 1);
    int rb_col = (col + w < this->nWidth) ? (col + w) : (this->nWidth - 1);

    if (lt_row >= 0 && lt_col >= 0)
        a = this->GetValue(lt_row, lt_col);

    if (lt_row >= 0 && rb_col >= 0)
        b = this->GetValue(lt_row, rb_col);

    if (rb_row >= 0 && rb_col >= 0)
        c = this->GetValue(rb_row, rb_col);

    if (rb_row >= 0 && lt_col >= 0)
        d = this->GetValue(rb_row, lt_col);

    double res = a + c - b - d;

    return (res > 0) ? res : 0;
}

double GDALIntegralImage::HaarWavelet_X(int nRow, int nCol, int nSize)
{
    return GetRectangleSum(nRow, nCol + nSize / 2, nSize / 2, nSize)
        - GetRectangleSum(nRow, nCol, nSize / 2, nSize);
}

double GDALIntegralImage::HaarWavelet_Y(int nRow, int nCol, int nSize)
{
    return GetRectangleSum(nRow + nSize / 2, nCol, nSize, nSize / 2)
        - GetRectangleSum(nRow, nCol, nSize, nSize / 2);
}

GDALIntegralImage::~GDALIntegralImage()
{
    //Clean up memory
    for (int i = 0; i < nHeight; i++)
        delete[] pMatrix[i];

    delete[] pMatrix;
}



/************************************************************************/
/* ==================================================================== */
/*                           GDALOctaveLayer                            */
/* ==================================================================== */
/************************************************************************/

GDALOctaveLayer::GDALOctaveLayer(int nOctave, int nInterval)
{
    this->octaveNum = nOctave;
    this->filterSize = 3 * ((int)pow(2.0, nOctave) * nInterval + 1);
    this->radius = (this->filterSize - 1) / 2;
    this->scale = (int)pow(2.0, nOctave);
    this->width = 0;
    this->height = 0;

    this->detHessians = 0;
    this->signs = 0;
}

void GDALOctaveLayer::ComputeLayer(GDALIntegralImage *poImg)
{
    this->width = poImg->GetWidth();
    this->height = poImg->GetHeight();

    //Allocate memory for arrays
    this->detHessians = new double*[this->height];
    this->signs = new int*[this->height];

    for (int i = 0; i < this->height; i++)
    {
        this->detHessians[i] = new double[this->width];
        this->signs[i] = new int[this->width];
    }
    //End Allocate memory for arrays

    //Values of Fast Hessian filters
    double dxx, dyy, dxy;
    // 1/3 of filter side
    int lobe = filterSize / 3;

    //Length of the longer side of the lobe in dxx and dyy filters
    int longPart = 2 * lobe - 1;

    int normalization = filterSize * filterSize;

    //Loop over image pixels
    //Filter should remain into image borders
    for (int r = radius; r <= height - radius; r++)
        for (int c = radius; c <= width - radius; c++)
        {
            dxx = poImg->GetRectangleSum(r - lobe + 1, c - radius, filterSize, longPart)
                - 3 * poImg->GetRectangleSum(r - lobe + 1, c - (lobe - 1) / 2, lobe, longPart);
            dyy = poImg->GetRectangleSum(r - radius, c - lobe - 1, longPart, filterSize)
                - 3 * poImg->GetRectangleSum(r - lobe + 1, c - lobe + 1, longPart, lobe);
            dxy = poImg->GetRectangleSum(r - lobe, c - lobe, lobe, lobe)
                + poImg->GetRectangleSum(r + 1, c + 1, lobe, lobe)
                - poImg->GetRectangleSum(r - lobe, c + 1, lobe, lobe)
                - poImg->GetRectangleSum(r + 1, c - lobe, lobe, lobe);

            dxx /= normalization;
            dyy /= normalization;
            dxy /= normalization;

            //Memorize Hessian values and their signs
            detHessians[r][c] = dxx * dyy - 0.9 * 0.9 * dxy * dxy;
            signs[r][c] = (dxx + dyy >= 0) ? 1 : -1;
        }
}

GDALOctaveLayer::~GDALOctaveLayer()
{
    for (int i = 0; i < height; i++)
    {
        delete[] detHessians[i];
        delete[] signs[i];
    }

    delete[] detHessians;
    delete[] signs;
}

/************************************************************************/
/* ==================================================================== */
/*                            GDALOctaveMap                             */
/* ==================================================================== */
/************************************************************************/

GDALOctaveMap::GDALOctaveMap(int nOctaveStart, int nOctaveEnd)
{
    this->octaveStart = nOctaveStart;
    this->octaveEnd = nOctaveEnd;

    pMap = new GDALOctaveLayer**[octaveEnd];

    for (int i = 0; i < nOctaveEnd; i++)
        pMap[i] = new GDALOctaveLayer*[INTERVALS];

    for (int oct = octaveStart; oct <= octaveEnd; oct++)
        for (int i = 1; i <= INTERVALS; i++)
            pMap[oct - 1][i - 1] = new GDALOctaveLayer(oct, i);
}

void GDALOctaveMap::ComputeMap(GDALIntegralImage *poImg)
{
    for (int oct = octaveStart; oct <= octaveEnd; oct++)
        for (int i = 1; i <= INTERVALS; i++)
            pMap[oct - 1][i - 1]->ComputeLayer(poImg);
}

bool GDALOctaveMap::PointIsExtremum(int row, int col, GDALOctaveLayer *bot,
                                    GDALOctaveLayer *mid, GDALOctaveLayer *top, double threshold)
{
    //Check that point in middle layer has all neighbours
    if (row <= top->radius || col <= top->radius ||
        row + top->radius >= top->height || col + top->radius >= top->width)
        return false;

    double curPoint = mid->detHessians[row][col];

    //Hessian should be higher than threshold
    if (curPoint < threshold)
        return false;

    //Hessian should be higher than hessians of all neighbours
    for (int i = -1; i <= 1; i++)
        for (int j = -1; j <= 1; j++)
        {
            double topPoint = top->detHessians[row + i][col + j];
            double midPoint = mid->detHessians[row + i][col + j];
            double botPoint = bot->detHessians[row + i][col + j];

            if (topPoint >= curPoint || botPoint >= curPoint)
                return false;

            if (i != 0 || j != 0)
                if (midPoint >= curPoint)
                    return false;
        }

    return true;
}

GDALOctaveMap::~GDALOctaveMap()
{
    // Clean up Octave layers
    for (int oct = octaveStart; oct <= octaveEnd; oct++)
        for(int i = 0; i < INTERVALS; i++)
            delete pMap[oct - 1][i];

    //Clean up allocated memory
    for (int oct = 0; oct < octaveEnd; oct++)
        delete[] pMap[oct];

    delete[] pMap;
}
