
/**
 * @author Samuel Thompson
 * @file DataMask.cpp
 * @brief  Contains the DataMask class for describing the spatial sampling pattern on a landscape.
 *
 * @copyright <a href="https://opensource.org/licenses/MIT"> MIT Licence.</a>
 */

#include "DataMask.h"
#include "Landscape.h"
#include "Logging.h"

DataMask::DataMask() : inputfile(""), isNullSample(true), isGridOffset(false), x_offset(0), y_offset(0), x_dim(0),
                       y_dim(0), mask_x_dim(0), mask_y_dim(0), getProportionfptr(nullptr), sample_mask(),
                       sample_mask_exact()
{
    getProportionfptr = &DataMask::getBoolProportion;
}

bool DataMask::isNull()
{
    return isNullSample;
}

void DataMask::setup(const shared_ptr<SimParameters> sim_parameters)
{
#ifdef DEBUG
    if((sim_parameters->grid_x_size > sim_parameters->sample_x_size ||
        sim_parameters->grid_y_size > sim_parameters->sample_y_size) && !isNullSample)
    {
        writeLog(50, "Grid size: " + to_string(sim_parameters->grid_x_size) + ", " +
                     to_string(sim_parameters->grid_y_size));
        writeLog(50, "Sample mask size: " + to_string(sim_parameters->sample_x_size) + ", " +
                     to_string(sim_parameters->sample_y_size));
        throw FatalException("Datamask dimensions do not make sense");
    }
#endif // DEBUG
    inputfile = sim_parameters->sample_mask_file;
    x_dim = sim_parameters->grid_x_size;
    y_dim = sim_parameters->grid_y_size;
    mask_x_dim = sim_parameters->sample_x_size;
    mask_y_dim = sim_parameters->sample_y_size;
    x_offset = sim_parameters->sample_x_offset;
    y_offset = sim_parameters->sample_y_offset;
    if(x_dim != mask_x_dim || y_dim != mask_y_dim)
    {
        isGridOffset = true;
    }
    else
    {
        isGridOffset = false;
    }
}

bool DataMask::checkCanUseDefault(const shared_ptr<SimParameters> sim_parameters)
{
    if(sim_parameters->sample_mask_file == "null")
    {
        if(sim_parameters->fine_map_x_size == sim_parameters->sample_x_size &&
           sim_parameters->fine_map_y_size == sim_parameters->sample_y_size &&
           sim_parameters->fine_map_x_offset == 0 && sim_parameters->fine_map_y_offset == 0)
        {
            isNullSample = true;
        }
        else
        {
            isNullSample = false;
        }
    }
    else
    {
        isNullSample = sim_parameters->sample_mask_file == "none";
    }
    return isNullSample;
}

void DataMask::importBooleanMask(unsigned long xdim, unsigned long ydim, unsigned long mask_xdim,
                                 unsigned long mask_ydim,
                                 unsigned long xoffset, unsigned long yoffset, string inputfile_in)
{
    shared_ptr<SimParameters> tmp_sim_parameters = make_shared<SimParameters>();
    tmp_sim_parameters->sample_mask_file = inputfile_in;
    tmp_sim_parameters->grid_x_size = xdim;
    tmp_sim_parameters->grid_y_size = ydim;
    tmp_sim_parameters->sample_x_size = mask_xdim;
    tmp_sim_parameters->sample_y_size = mask_ydim;
    tmp_sim_parameters->sample_x_offset = xoffset;
    tmp_sim_parameters->sample_y_offset = yoffset;
    setup(tmp_sim_parameters);
    isNullSample = inputfile_in == "null" || inputfile_in == "none";
    if(!isNullSample)
    {
        doImport();
    }
}

void DataMask::doImport()
{
    sample_mask.setSize(mask_y_dim, mask_x_dim);
    sample_mask.import(inputfile);
    sample_mask.close();
    completeBoolImport();
}

void DataMask::completeBoolImport()
{
    mask_x_dim = sample_mask.getCols();
    mask_y_dim = sample_mask.getRows();
    getProportionfptr = &DataMask::getBoolProportion;
}

void DataMask::setupNull(const shared_ptr<SimParameters> mapvarin)
{
    sample_mask.setSize(mapvarin->fine_map_y_size, mapvarin->fine_map_x_size);
    for(unsigned long i = 0; i < sample_mask.getRows(); i++)
    {
        for(unsigned long j = 0; j < sample_mask.getCols(); j++)
        {
            sample_mask[i][j] = i + y_offset < mask_y_dim && j + x_offset < mask_x_dim;
        }
    }
    completeBoolImport();
}

void DataMask::importSampleMask(const shared_ptr<SimParameters> mapvarin)
{
    setup(mapvarin);
    if(!checkCanUseDefault(mapvarin))
    {
        if(inputfile == "null")
        {
            setupNull(mapvarin);
        }
        else if(mapvarin->uses_spatial_sampling)
        {
#ifdef DEBUG
            writeLog(10, "Using spatial sampling.");
            writeLog(10, "Mask dimensions: " + to_string(mask_x_dim) + ", " + to_string(mask_y_dim));
#endif // DEBUG
            sample_mask_exact.setSize(mask_y_dim, mask_x_dim);
            sample_mask_exact.import(inputfile);
            sample_mask_exact.close();
            mask_x_dim = sample_mask_exact.getCols();
            mask_y_dim = sample_mask_exact.getRows();
            getProportionfptr = &DataMask::getSampleProportion;
        }
        else
        {
            doImport();
        }
    }
    else
    {
        if(mapvarin->uses_spatial_sampling)
        {
            // This could perhaps be a warning, but I'd prefer to have the warning/prohibit potential in python
            // and throw a full exception here.
            throw FatalException("Cannot use a spatial sampling routine when the map file is null.");
        }
        getProportionfptr = &DataMask::getNullProportion;
    }
}

bool DataMask::getVal(const long &x, const long &y, const long &xwrap, const long &ywrap)
{
    long xval = x + (xwrap * x_dim) + x_offset;
    long yval = y + (ywrap * y_dim) + y_offset;
    if(isNullSample)
    {
        return true;
    }
#ifdef DEBUG
    if(xval < 0 || xval >= (long) mask_x_dim || yval < 0 || yval >= (long) mask_y_dim)
    {
        stringstream ss;
        ss << "Get value on samplemask requested for non index." << endl;
        ss << "x, y: " << x << ", " << y << endl;
        ss << "dimensions x,y: " << mask_x_dim << ", " << mask_y_dim << endl;
        ss << "x, y wrap: " << xwrap << ", " << ywrap << endl;
        ss << "xval, yval: " << xval << ", " << yval << endl;
        ss << "offsets x, y: " << x_offset << ", " << y_offset << endl;
        writeLog(50, ss);
        ss.str("Get value on samplemask requested for non index.");
        throw out_of_range(ss.str());
    }
#endif
    return sample_mask[yval][xval];
}

double DataMask::getNullProportion(const long &x, const long &y, const long &xwrap, const long &ywrap)
{
    return 1.0;
}

double DataMask::getBoolProportion(const long &x, const long &y, const long &xwrap, const long &ywrap)
{

    if(getVal(x, y, xwrap, ywrap))
    {
        return 1.0;
    }
    else
    {
        return 0.0;
    }
}

double DataMask::getSampleProportion(const long &x, const long &y, const long &xwrap, const long &ywrap)
{
#ifdef DEBUG
    if(isNullSample || sample_mask_exact.getCols() == 0)
    {
        throw out_of_range("Cannot get the exact value from a samplemask if we are using a null mask, or the "
                           "exact samplemask has not been properly imported.");
    }
#endif // DEBUG
    long xval = x + (xwrap * x_dim) + x_offset;
    long yval = y + (ywrap * y_dim) + y_offset;
    return sample_mask_exact[yval][xval];
}

double DataMask::getExactValue(const long &x, const long &y, const long &xwrap, const long &ywrap)
{
    return (this->*getProportionfptr)(x, y, xwrap, ywrap);
}

void DataMask::convertBoolean(shared_ptr<Landscape> map1, const double &deme_sampling, const double &generation)
{
    // Clear the old boolean object and set the new size
    sample_mask.setSize(y_dim, x_dim);
    for(unsigned long y = 0; y < y_dim; y++)
    {
        for(unsigned long x = 0; x < x_dim; x++)
        {
            long tmp_x = x;
            long tmp_y = y;
            long tmp_xwrap = 0;
            long tmp_ywrap = 0;
            recalculateCoordinates(tmp_x, tmp_y, tmp_xwrap, tmp_ywrap);
            double density = map1->getVal(tmp_x, tmp_y, tmp_xwrap, tmp_ywrap, generation) * deme_sampling;
            sample_mask[y][x] = density >= 1.0;
        }
    }
}

void DataMask::clearSpatialMask()
{
    sample_mask_exact.setSize(0, 0);
}

void DataMask::recalculateCoordinates(long &x, long &y, long &x_wrap, long &y_wrap)
{
    if(isGridOffset)
    {
        x_wrap = (long) ((floor((x - (double) x_offset) / (double) x_dim)));
        y_wrap = (long) ((floor((y - (double) y_offset) / (double) y_dim)));
        x += -x_offset - (x_wrap * x_dim);
        y += -y_offset - (y_wrap * y_dim);
    }
}
