/*
 * (C) Copyright 1996-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*! \file Filter.cc
    Implementation of Filter class.

    Magics Team - ECMWF 2004

    Created: Wed 14-Jun-2004

*/

#include "Filter.h"
#include "MagLog.h"
#include "Timer.h"

using namespace magics;


Filter::Filter(MatrixHandler& matrix, int nrows, int ncols) : matrix_(matrix), nrows_(nrows), ncols_(ncols) {
    flr_  = 0;
    fbuf_ = NULL;

    // Cache input data
    //	matrix.cache(matrix_);
}


Filter::~Filter() {
    FreeImageBuffers();
}


bool Filter::AllocImageBuffers() {
    int i;

    FreeImageBuffers();

    // Allocate space rotating buffer
    fbuf_ = new double*[nrows_];
    if (fbuf_ == NULL)
        return false;
    int nb_columns = matrix_.columns();
    for (i = 0; i < nrows_; i++) {
        fbuf_[i] = NULL;
        fbuf_[i] = new double[nb_columns];
        if (fbuf_[i] == NULL)
            return false;
    }

    return true;
}


void Filter::FreeImageBuffers() {
    if (fbuf_ == NULL)
        return;
    int i;
    for (i = 0; i < nrows_; i++) {
        if (fbuf_[i] != NULL) {
            delete[] fbuf_[i];
            fbuf_[i] = NULL;
        }
    }

    delete[] fbuf_;
    fbuf_ = NULL;

    return;
}


bool Filter::InitBuffer() {
    int lin, col;

    // Allocate space for output buffer
    if (AllocImageBuffers() == false)
        return false;
    int nb_columns = matrix_.columns();
    // Fill buffer with first nrows_ input data
    for (lin = 0; lin < nrows_; lin++)
        for (col = 0; col < nb_columns; col++)
            fbuf_[lin][col] = matrix_(lin, col);

    flr_ = nrows_ - 1;

    return true;
}


bool Filter::ScrollBuffer() {
    int ind, col;
    int nr = nrows_ - 1;
    double* aux;
    int nb_rows    = matrix_.rows();
    int nb_columns = matrix_.columns();

    // verify if the line is within of the image
    flr_++;
    if (flr_ < nb_rows) {
        aux = fbuf_[0];
        for (ind = 0; ind < nrows_ - 1; ind++)
            fbuf_[ind] = fbuf_[ind + 1];
        fbuf_[nr] = aux;
        for (col = 0; col < nb_columns; col++)
            fbuf_[nr][col] = matrix_(flr_, col);
    }
    else
        return false;

    return true;
}


MinMaxFilter::MinMaxFilter(MatrixHandler& matrix, int nrows, int ncols, int flag) :
    Filter(matrix, nrows, ncols), mflag_(flag) {}


MinMaxFilter::~MinMaxFilter() {}

/*!
  \brief Method to find extrema
*/

bool MinMaxFilter::Process() {
    if (mflag_ == 0)
        return false;
    double val;  // central point value
    bool found;
    int lin, col;
    int i, j;

    int fcr = this->nrows_ * .5;  // central line
    int fcc = this->ncols_ * .5;  // central column

    const bool doMin = (mflag_ == 1 || mflag_ == 3) ? true : false;
    const bool doMax = (mflag_ == 2 || mflag_ == 3) ? true : false;

    // Initialize input buffer
    if (this->InitBuffer() == false) {
        // FMagLog::debug() << " InitBuffer Error" << "\n";
        return false;
    }

    // Processing
    long hi        = 0;
    long lo        = 0;
    double nb_rows = this->matrix_.rows() - this->nrows_;
    double nb_cols = this->matrix_.columns() - this->ncols_;

    for (lin = 0; lin <= nb_rows; lin++) {
        for (col = 0; col <= nb_cols; col++) {
            // check min value
            if (doMin) {
                val   = this->fbuf_[fcr][col + fcc];
                found = true;
                for (i = 0; i < this->nrows_; i++) {
                    for (j = 0; j < this->ncols_; j++) {
                        if (val >= this->fbuf_[i][col + j]) {
                            if (i != fcr || j != fcc) {
                                found = false;  // stop searching
                                break;
                            }
                        }
                    }
                    if (!found)
                        break;
                }

                if (found)  // minimum found
                {
                    // FMagLog::debug() << "Min " << lin+fcr+1 << " " << col+fcc+1 << " " << val << "\n";
                    lo = lo + 1;
                    push_back(UserPoint(this->matrix_.column(lin + fcr, col + fcc),
                                        this->matrix_.row(lin + fcr, col + fcc), val));
                    this->back().low(true);
                    this->back().high(false);
                }
            }

            // check max value
            if (doMax) {
                val   = this->fbuf_[fcr][col + fcc];
                found = true;
                for (i = 0; i < this->nrows_; i++) {
                    for (j = 0; j < this->ncols_; j++) {
                        if (val <= this->fbuf_[i][col + j]) {
                            if (i != fcr || j != fcc) {
                                found = false;  // stop searching
                                break;
                            }
                        }
                    }
                    if (!found)
                        break;
                }

                if (found)  // minimum found
                {
                    // FMagLog::debug() << "Max " << lin+fcr+1 << " " << col+fcc+1 << " " << val << "\n";
                    hi = hi + 1;
                    push_back(UserPoint(this->matrix_.column(lin + fcr, col + fcc),
                                        this->matrix_.row(lin + fcr, col + fcc), val));
                    this->back().high(true);
                    this->back().low(false);
                }
            }
        }  // end column

        if (this->ScrollBuffer() == false)
            break;
    }

    // FMagLog::debug() << "TOTAL POINTS LOW = " << lo << "\n"
    // F                << "TOTAL POINTS HI  = " << hi << "\n";
    return true;
}


// TEST, REMOVE LATER

void MinMaxFilter::test_build_data() {
    printf("\n\n TESTING   \n");
    double val;  // central point value
    bool found;
    int lin, col;
    int i, j;

    int fcr = this->nrows_ / 2;  // central line
    int fcc = this->ncols_ / 2;  // central column

    bool doMin = (mflag_ == 1 || mflag_ == 3) ? true : false;
    bool doMax = (mflag_ == 2 || mflag_ == 3) ? true : false;

    // Initialize input buffer
    if (this->InitBuffer() == false) {
        // FMagLog::debug() << " InitBuffer Error" << "\n";
        return;
    }

    long hi = 0;
    long lo = 0;
    // Processing
    for (lin = 0; lin <= this->matrix_.rows() - this->nrows_; lin++) {
        printf("\n");
        // Ffor (i = 0; i < this->nrows_; i++) {
        // F    for (j = 0; j < this->matrix_.columns(); j++)
        // FMagLog::debug() << this->fbuf_[i][j] << " ";
        // F    printf("\n");
        // F}

        for (col = 0; col <= this->matrix_.columns() - this->ncols_; col++) {
            // check min value
            if (doMin) {
                val   = this->fbuf_[fcr][col + fcc];
                found = true;
                for (i = 0; i < this->nrows_; i++) {
                    for (j = 0; j < this->ncols_; j++) {
                        if (val >= this->fbuf_[i][col + j]) {
                            if (i != fcr || j != fcc) {
                                found = false;  // stop searching
                                break;
                            }
                        }
                    }
                    if (!found)
                        break;
                }

                if (found)  // minimum found
                {
                    // FMagLog::debug() << "\n"
                    // F                << "Min " << lin + fcr + 1 << " " << col + fcc + 1 << " " << val << "\n";
                    lo = lo + 1;
                }
            }

            // check max value
            if (doMax) {
                val   = this->fbuf_[fcr][col + fcc];
                found = true;
                for (i = 0; i < this->nrows_; i++) {
                    for (j = 0; j < this->ncols_; j++) {
                        if (val <= this->fbuf_[i][col + j]) {
                            if (i != fcr || j != fcc) {
                                found = false;  // stop searching
                                break;
                            }
                        }
                    }
                    if (!found)
                        break;
                }

                if (found)  // minimum found
                {
                    // FMagLog::debug() << "\n"
                    // F                << "Max " << lin + fcr + 1 << " " << col + fcc + 1 << " " << val << "\n";
                    hi = hi + 1;
                }
            }
        }  // end column

        if (this->ScrollBuffer() == false)
            break;
    }

    // FMagLog::debug() << "TOTAL POINTS LOW=" << lo << "\n";
    // FMagLog::debug() << "TOTAL POINTS HI =" << hi << "\n";

    printf("\n\nEND TEST\n");
}
