/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

//-- What should we do with these functions?         (vk/Sep09)
//--
//-- The old code used memory mapping for images, accessing each
//-- 8-bit pixel values directly, without any need to decode.
//-- This required 'offset' variable to tell where the data started...
//--
//-- With GRIB_API there is no method to get 'offset' (at least
//-- I have not spotted such a method)...
//--

#include <cstring>

#include "macro.h"
#include "grib_api.h"
#include "arith.h"
#include <math.h>
#include <sys/types.h>
/* Stupid hp as no extern C */
extern "C" {
#include <sys/mman.h>
}

//-- this one only while eccodes_port_missing() is called!
#include "MvFieldSet.h"

//-- to have an easy switch between gribex (1) and gribapi (0)
#define USE_OLD_GRIBEX_CODE 0

// 'round' clashes with math.h on hpux 11 => rename as 'l_round':
inline long l_round(double a)
{
    return ((long)((a) + (((a) >= 0) ? 0.5 : -0.5)));
}

inline unsigned char scale(double d)
{
    d *= 255;
    if (d < 0)
        d = 0;
    if (d > 255)
        d = 255;
    return (unsigned char)d;
}

inline double scale(unsigned char c)
{
    return (double)c / 255.0;
}


class Image
{
    long nx{0};           //-- pixels along x-axis
    long ny{0};           //-- pixels along y-axis
    char* name{nullptr};  //-- unique name for Macro prints/debugs
    char* path{nullptr};  //-- path to satellite GRIB file

    //-- DO WE NEED TO USE MEMORY MAPPING ALSO WITH ecCodes????

    unsigned char* buffer{nullptr};  //-- memory mapped GRIB file/msg
    unsigned char* data{nullptr};    //-- data part in memory mapped GRIB msg
    int length{0};                   //-- length of the GRIB file/msg
    long offset{0};                  //-- start of data in memory mapped GRIB msg
    size_t count{0};                 //-- total number of pixel values
    int mapped{0};                   //-- OBSOLETE? only set, not checked at all!!!?

    FILE* file{nullptr};  //-- store file descr between Map() and Unmap()

public:
    int Count(void) { return count; }
    int Nx(void) { return nx; }
    int Ny(void) { return ny; }

    void Map(void);
    void Unmap(void);
    unsigned char& operator[](int n) { return data[n]; }
    unsigned char& operator()(int i, int j) { return data[i + nx * j]; }

    const char* Path() { return path; }
    void ToRequest(request*&);
    Image(Image*);
    Image(Image*, long);
    Image(const char* p, const char* n);
    ~Image();
    Image(const Image&) = delete;
    Image& operator=(const Image&) = delete;
};

#if USE_OLD_GRIBEX_CODE
//-- you also need to link with libMars-old.a !!!
extern "C" {
err cgribex(fortint ksec0[], fortint ksec1[], fortint ksec2[], fortfloat rsec2[], fortint ksec3[], fortfloat rsec3[], fortint ksec4[], fortfloat rsec4[], fortint sec4len, char* buffer, fortint* buflen, char* op);
}
#endif

Image::Image(const char* p, const char* n)
{
#if USE_OLD_GRIBEX_CODE
    name = n ? strcache(n) : strcache(Context::UniqueName());
    path = strcache(p);
    buffer = 0;
    length = 0;
    offset = 0;
    mapped = 0;
    file = 0;
    nx = 0;
    ny = 0;
    count = 0;

    fortint ksec0[ISECTION_0];
    fortint ksec1[ISECTION_1];
    fortint ksec2[ISECTION_2];
    fortfloat zsec2[RSECTION_2];
    fortint ksec3[ISECTION_3];
    fortfloat zsec3[RSECTION_3];
    fortint ksec4[ISECTION_4];
    fortfloat zsec4[20];
    int len4 = 20;

    Map();
    fortint len = length;
    err e = cgribex(ksec0, ksec1,
                    ksec2, zsec2, ksec3, zsec3,
                    ksec4, zsec4, len4, (char*)buffer, &len, "G");

    offset = (int)ksec4[33] / 8;
    count = (int)ksec4[0];
    nx = (int)ksec2[1];
    ny = (int)ksec2[2];
    Unmap();
#else

    name = n ? strcache(n) : strcache(Context::UniqueName());
    path = strcache(p);

    FILE* in = fopen(path, "r");
    if (!in)
        marslog(LOG_EROR, "Image::Image - unable to open input file");

    int ret = 0;
    grib_handle* h = grib_handle_new_from_file(0, in, &ret);
    if (!h)
        marslog(LOG_EROR, "Image::Image - unable to get grib_handle");

    // ret = grib_get_long( h, "x", &offset ); //-- do we need offset with ecCodes???
    // if( ! ret ) ...
    ret = grib_get_size(h, "values", &count);
    if (ret != 0)
        marslog(LOG_EROR, "Image::Image - unable to read image size");

    ret = grib_get_long(h, "numberOfPointsAlongXAxis", &nx);
    if (ret != 0)
        marslog(LOG_EROR, "Image::Image - unable to read numberOfPointsAlongXAxis");

    ret = grib_get_long(h, "numberOfPointsAlongYAxis", &ny);
    if (ret != 0)
        marslog(LOG_EROR, "Image::Image - unable to read numberOfPointsAlongYAxis");

    return;

#endif
}

Image::Image(Image* i)
{
    name = strcache(Context::UniqueName());
    path = strcache(marstmp());
    buffer = 0;
    length = i->length;
    offset = i->offset;
    count = i->count;
    nx = i->nx;
    ny = i->ny;
    mapped = 0;


    file = fopen(path, "r+");
    fseek(file, length - 1, 0);
    char c = 0;
    fwrite(&c, 1, 1, file);
    fclose(file);

    Map();
    i->Map();

    /*
       std::memcpy(buffer,i->buffer,length);
    */

    std::memcpy(buffer, i->buffer, offset);
    std::memcpy(buffer + offset + count,
                i->buffer + offset + count,
                length - offset - count);
    Unmap();
    i->Unmap();

    data = buffer + offset;
}

Image::~Image()
{
    strfree(path);
    strfree(name);
}

void Image::Map(void)
{
    file = fopen(path, "r+");
    if (file == 0)
        marslog(LOG_EXIT | LOG_PERR, "%s", path);

    fseek(file, 0, 2);
    length = (int)ftell(file);

    buffer = (unsigned char*)mmap(0, length,
                                  PROT_READ | PROT_WRITE, MAP_SHARED, fileno(file), 0);

    if (buffer == 0)
        marslog(LOG_EXIT | LOG_PERR, "mmap");

    data = buffer + offset;
}

void Image::Unmap(void)
{
    munmap((caddr_t)buffer, length);
    fclose(file);
}


void Image::ToRequest(request*& s)
{
    static request* r = 0;
    free_all_requests(r);
    r = empty_request("IMAGE");
    set_value(r, "PATH", "%s", path);
    set_value(r, "_NAME", "%s", name);
    set_value(r, "_CLASS", "MACRO");
    s = r;
}

int CImage::Write(FILE* f)
{
    return CopyFile(image->Path(), f);
}

void CImage::ToRequest(request*& s)
{
    image->ToRequest(s);
    // Temp fix : Some one want the path, so don't delete it
    Attach();
}

CImage::CImage(Image* i) :
    InPool(timage)
{
    image = i;
}


/*
gribex 'g' opt:
psec4(1)  -> ref value;
psec4(2)  -> scale value;
ksec4(34) -> bit pointer
*/

CImage::CImage(request* r) :
    InPool(timage, r)
{
    const char* path = get_value(r, "PATH", 0);
    image = new Image(path, GetName());
}

CImage::~CImage()
{
    delete image;
}

//=============================================================

class ImageConvol : public Function
{
public:
    ImageConvol(const char* n) :
        Function(n)
    {
        info = "Applies a convolution matrix to an image.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int ImageConvol::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[0].GetType() != timage)
        return false;
    if (arg[1].GetType() != tlist)
        return false;

    CList* l;
    arg[1].GetValue(l);

    if (l->Count() != 9)
        return false;
    for (int i = 0; i < 9; i++)
        if ((*l)[i].GetType() != tnumber)
            return false;
    return true;
}

Value ImageConvol::Execute(int, Value* arg)
{
    Image* p;
    CList* l;

    arg[0].GetValue(p);
    arg[1].GetValue(l);

    double a[9];

    int n;
    for (n = 0; n < 9; n++)
        (*l)[n].GetValue(a[n]);

    double d = 0;
    for (n = 0; n < 9; n++)
        d += a[n];

    if (fabs(d) < 1e-10)
        d = 1;

    d *= 255;
    for (n = 0; n < 9; n++)
        a[n] /= d;

    auto* q = new Image(p);

    p->Map();
    q->Map();

    for (int i = 1; i < p->Nx() - 1; i++)
        for (int j = 1; j < p->Ny() - 1; j++)
            (*q)(i, j) = scale(

                (*p)(i - 1, j - 1) * a[0] +
                (*p)(i - 1, j) * a[1] +
                (*p)(i - 1, j + 1) * a[2] +
                (*p)(i, j - 1) * a[3] +
                (*p)(i, j) * a[4] +
                (*p)(i, j + 1) * a[5] +
                (*p)(i + 1, j - 1) * a[6] +
                (*p)(i + 1, j) * a[7] +
                (*p)(i + 1, j + 1) * a[8]

            );

    p->Unmap();
    q->Unmap();

    return Value(new CImage(q));
}

//=============================================================

class ImageLut : public Function
{
public:
    ImageLut(const char* n) :
        Function(n)
    {
        info = "Remaps an image's pixel values.";
    }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

int ImageLut::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;
    if (arg[0].GetType() != timage)
        return false;
    if (arg[1].GetType() != tlist)
        return false;

    CList* l;
    arg[1].GetValue(l);

    if (l->Count() != 256)
        return false;
    for (int i = 0; i < 256; i++) {
        if ((*l)[i].GetType() != tnumber)
            return false;
        double d;
        (*l)[i].GetValue(d);
        if (d < 0 || d > 255)
            return false;
    }
    return true;
}

Value ImageLut::Execute(int, Value* arg)
{
    Image* p;
    CList* l;

    arg[0].GetValue(p);
    arg[1].GetValue(l);

    unsigned char a[256];

    for (int n = 0; n < 256; n++) {
        double d;
        (*l)[n].GetValue(d);
        a[n] = (unsigned char)d;
    }

    auto* q = new Image(p);

    p->Map();
    q->Map();

    for (int i = 0; i < p->Count(); i++)
        (*q)[i] = a[(*p)[i]];

    p->Unmap();
    q->Unmap();

    return Value(new CImage(q));
}

//=============================================================
//=============================================================

class ImageUnOp : public Function
{
    uniproc F_;

public:
    ImageUnOp(const char* n, uniproc f) :
        Function(n, 1, timage) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
};

class ImageBinOp : public Function
{
    binproc F_;

public:
    ImageBinOp(const char* n, binproc f) :
        Function(n) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

class ImageMulOp : public Function
{
    using binproc = double (*)(double, double);
    binproc F_;

public:
    ImageMulOp(const char* n, binproc f) :
        Function(n) { F_ = f; }
    virtual Value Execute(int arity, Value* arg);
    virtual int ValidArguments(int arity, Value* arg);
};

//=============================================================

Value ImageBinOp::Execute(int, Value* arg)
{
    if (arg[0].GetType() == timage && arg[1].GetType() == timage) {
        Image *a, *b, *c;
        arg[0].GetValue(a);
        arg[1].GetValue(b);

        c = new Image(a);
        a->Map();
        b->Map();
        c->Map();
        for (int i = 0; i < a->Count(); i++)
            (*c)[i] = scale(F_(scale((*a)[i]), scale((*b)[i])));
        a->Unmap();
        b->Unmap();
        c->Unmap();

        return Value(new CImage(c));
    }
    else if (arg[0].GetType() == tnumber) {
        Image *b, *c;
        double d;
        arg[0].GetValue(d);
        arg[1].GetValue(b);

        c = new Image(b);
        b->Map();
        c->Map();
        for (int i = 0; i < b->Count(); i++)
            (*c)[i] = scale(F_(d, scale((*b)[i])));
        b->Unmap();
        c->Unmap();

        return Value(new CImage(c));
    }
    else {
        Image *a, *c;
        double d;
        arg[0].GetValue(a);
        arg[1].GetValue(d);

        c = new Image(a);
        a->Map();
        c->Map();
        for (int i = 0; i < a->Count(); i++)
            (*c)[i] = scale(F_(scale((*a)[i]), d));
        a->Unmap();
        c->Unmap();

        return Value(new CImage(c));
    }
}

int ImageBinOp::ValidArguments(int arity, Value* arg)
{
    if (arity != 2)
        return false;

    int image = 0;

    for (int i = 0; i < arity; i++)
        switch (arg[i].GetType()) {
            case timage:
                image++;
                break;

            case tnumber:
                break;

            default:
                return false;
        }
    return image != 0;
}

Value ImageUnOp::Execute(int, Value* arg)
{
    Image* p;
    arg[0].GetValue(p);

    auto* q = new Image(p);

    p->Map();
    q->Map();

    for (int i = 0; i < p->Count(); i++)
        (*q)[i] = scale(F_(scale((*p)[i])));

    p->Unmap();
    q->Unmap();

    return Value(new CImage(q));
}

int ImageMulOp::ValidArguments(int arity, Value* arg)
{
    if (arity < 1)
        return false;
    for (int i = 0; i < arity; i++)
        if (arg[i].GetType() != timage)
            return false;
    return true;
}

Value ImageMulOp::Execute(int arity, Value* arg)
{
    double d;
    arg[0].GetValue(d);

    for (int i = 1; i < arity; i++) {
        double x;
        arg[i].GetValue(x);
        d = F_(d, x);
    }

    return Value(d);
}

//=============================================================

/* height of Meteosat above earth centre devided by earth radius */
#define MSATALT 6.610839

Image::Image(Image* i, long scale)
{
#if USE_OLD_GRIBEX_CODE
    fortint ksec0[ISECTION_0];
    fortint ksec1[ISECTION_1];
    fortint ksec2[ISECTION_2];
    fortfloat zsec2[RSECTION_2];
    fortint ksec3[ISECTION_3];
    fortfloat zsec3[RSECTION_3];
    fortint ksec4[ISECTION_4];
    fortfloat zsec4[20];
    int len4 = 20;

    // Get info from gribex
    i->Map();
    fortint len = i->length;
    cgribex(ksec0, ksec1, ksec2, zsec2, ksec3, zsec3, ksec4, zsec4, len4, (char*)i->buffer, &len, "G");
    i->Unmap();

    // Get scaled pict

    /* nx/ny: number of points along x/y-axis */
    ksec2[1] = ksec2[1] / scale;
    ksec2[2] = ksec2[1];

    /* dx/y-apparent diameter of the earth in grid lenght in the x/y dir */
    double r = (M_PI / 10.0) / (double)ksec2[1];
    ksec2[6] = l_round((double)2.0 * asin((double)(1.0 / (double)MSATALT)) / r);
    ksec2[7] = ksec2[6];

    /* x/yp: x/y-coordinate of sub-satellite point: ksec2[1]/2 */
    ksec2[8] = l_round((double)ksec2[1] / (double)2.0);
    ksec2[9] = ksec2[8];


    /* # of data values in psec4: columns x rows */
    ksec4[0] = ksec2[1] * ksec2[2];

    name = strcache(Context::UniqueName());
    path = strcache(marstmp());
    buffer = 0;
    count = (int)ksec4[0];
    length = i->length - i->count + count;
    offset = i->offset;
    mapped = 0;
    file = 0;
    nx = (int)ksec2[1];
    ny = (int)ksec2[2];


    file = fopen(path, "r+");
    fseek(file, length - 1, 0);
    char c = 0;
    fwrite(&c, 1, 1, file);
    fclose(file);

    Map();
    i->Map();

    len = length;

    len4 = count;
    fortfloat* sec4 = new fortfloat[len4];

    cgribex(ksec0, ksec1, ksec2, zsec2, ksec3, zsec3, ksec4, sec4, len4, (char*)buffer, &len, "C");

    delete[] sec4;

    data = buffer + offset;

    for (int j = 0; j < nx; j++)
        for (int k = 0; k < ny; k++)
            (*this)(j, k) = (*i)(j * scale, k * scale);

    Unmap();
    i->Unmap();
#else

    eccodes_port_missing("Image::Image(Image*,long)");
    return;

#endif
}

//=============================================================

class ImageReduce : public Function
{
public:
    ImageReduce(const char* n) :
        Function(n, 2, timage, tnumber)
    {
        info = "Reduces the size of an image by a given scaling factor.";
    }
    virtual Value Execute(int arity, Value* arg);
};

Value ImageReduce::Execute(int, Value* arg)
{
    Image* i;
    double d;
    arg[0].GetValue(i);
    arg[1].GetValue(d);
    return Value(new CImage(new Image(i, d)));
}

//=============================================================

static void install(Context* c)
{
    int i;

    for (i = 0; BinOps[i].symb; i++)
        c->AddFunction(new ImageBinOp(BinOps[i].symb, BinOps[i].proc));

    // Note : MulOps for BinOp

    for (i = 0; MulOps[i].symb; i++)
        c->AddFunction(new ImageBinOp(MulOps[i].symb, MulOps[i].proc));

    for (i = 0; UniOps[i].symb; i++)
        c->AddFunction(new ImageUnOp(UniOps[i].symb, UniOps[i].proc));

    c->AddFunction(new ImageConvol("convolution"));
    c->AddFunction(new ImageReduce("reduce"));
    c->AddFunction(new ImageLut("filter"));
}

static Linkage linkage(install);
