/*******************************************************
 * Copyright (c) 2014, ArrayFire
 * All rights reserved.
 *
 * This file is distributed under 3-clause BSD license.
 * The complete license agreement can be obtained at:
 * http://arrayfire.com/licenses/BSD-3-Clause
 ********************************************************/

#if defined(WITH_GRAPHICS)

#include <graphics_common.hpp>
#include <err_common.hpp>
#include <backend.hpp>
#include <platform.hpp>
#include <util.hpp>

using namespace std;

template<typename T>
GLenum getGLType() { return GL_FLOAT; }

fg::MarkerType getFGMarker(const af_marker_type af_marker) {
    fg::MarkerType fg_marker;
    switch (af_marker) {
        case AF_MARKER_NONE: fg_marker = fg::FG_NONE; break;
        case AF_MARKER_POINT: fg_marker = fg::FG_POINT; break;
        case AF_MARKER_CIRCLE: fg_marker = fg::FG_CIRCLE; break;
        case AF_MARKER_SQUARE: fg_marker = fg::FG_SQUARE; break;
        case AF_MARKER_TRIANGLE: fg_marker = fg::FG_TRIANGLE; break;
        case AF_MARKER_CROSS: fg_marker = fg::FG_CROSS; break;
        case AF_MARKER_PLUS: fg_marker = fg::FG_PLUS; break;
        case AF_MARKER_STAR: fg_marker = fg::FG_STAR; break;
        default: fg_marker = fg::FG_NONE; break;
    }
    return fg_marker;
}

#define INSTANTIATE_GET_FG_TYPE(T, ForgeEnum)\
    template<> fg::dtype getGLType<T>() { return ForgeEnum; }

INSTANTIATE_GET_FG_TYPE(float, fg::f32);
INSTANTIATE_GET_FG_TYPE(int  , fg::s32);
INSTANTIATE_GET_FG_TYPE(unsigned, fg::u32);
INSTANTIATE_GET_FG_TYPE(char, fg::s8);
INSTANTIATE_GET_FG_TYPE(unsigned char, fg::u8);
INSTANTIATE_GET_FG_TYPE(unsigned short, fg::u16);
INSTANTIATE_GET_FG_TYPE(short, fg::s16);

GLenum glErrorSkip(const char *msg, const char* file, int line)
{
#ifndef NDEBUG
    GLenum x = glGetError();
    if (x != GL_NO_ERROR) {
        char buf[1024];
        sprintf(buf, "GL Error Skipped at: %s:%d Message: %s Error Code: %d \"%s\"\n", file, line, msg, x, gluErrorString(x));
        AF_ERROR(buf, AF_ERR_INTERNAL);
    }
    return x;
#else
    return 0;
#endif
}

GLenum glErrorCheck(const char *msg, const char* file, int line)
{
// Skipped in release mode
#ifndef NDEBUG
    GLenum x = glGetError();

    if (x != GL_NO_ERROR) {
        char buf[1024];
        sprintf(buf, "GL Error at: %s:%d Message: %s Error Code: %d \"%s\"\n", file, line, msg, x, gluErrorString(x));
        AF_ERROR(buf, AF_ERR_INTERNAL);
    }
    return x;
#else
    return 0;
#endif
}

GLenum glForceErrorCheck(const char *msg, const char* file, int line)
{
    GLenum x = glGetError();

    if (x != GL_NO_ERROR) {
        char buf[1024];
        sprintf(buf, "GL Error at: %s:%d Message: %s Error Code: %d \"%s\"\n", file, line, msg, x, gluErrorString(x));
        AF_ERROR(buf, AF_ERR_INTERNAL);
    }
    return x;
}

size_t getTypeSize(GLenum type)
{
    switch(type) {
        case GL_FLOAT:          return sizeof(float);
        case GL_INT:            return sizeof(int  );
        case GL_UNSIGNED_INT:   return sizeof(unsigned);
        case GL_SHORT:          return sizeof(short);
        case GL_UNSIGNED_SHORT: return sizeof(unsigned short);
        case GL_BYTE:           return sizeof(char );
        case GL_UNSIGNED_BYTE:  return sizeof(unsigned char);
        default: return sizeof(float);
    }
}

namespace graphics
{

ForgeManager& ForgeManager::getInstance()
{
    static ForgeManager my_instance;
    return my_instance;
}

ForgeManager::~ForgeManager()
{
    destroyResources();
}

fg::Font* ForgeManager::getFont(const bool dontCreate)
{
    static bool flag = true;
    static fg::Font* fnt = NULL;

    CheckGL("Begin ForgeManager::getFont");

    if (flag && !dontCreate) {
        fnt = new fg::Font();
#if defined(_WIN32) || defined(_MSC_VER)
        fnt->loadSystemFont("Arial", 32);
#else
        fnt->loadSystemFont("Vera", 32);
#endif
        CheckGL("End ForgeManager::getFont");
        flag = false;
    };

    return fnt;
}

fg::Window* ForgeManager::getMainWindow(const bool dontCreate)
{
    static bool flag = true;
    static fg::Window* wnd = NULL;

    // Define AF_DISABLE_GRAPHICS with any value to disable initialization
    std::string noGraphicsENV = getEnvVar("AF_DISABLE_GRAPHICS");
    if(noGraphicsENV.empty()) { // If AF_DISABLE_GRAPHICS is not defined
        if (flag && !dontCreate) {
            wnd = new fg::Window(WIDTH, HEIGHT, "ArrayFire", NULL, true);
            CheckGL("End ForgeManager::getMainWindow");
            flag = false;
        };
    }
    return wnd;
}

fg::Image* ForgeManager::getImage(int w, int h, fg::ChannelFormat mode, fg::dtype type)
{
    /* w, h needs to fall in the range of [0, 2^16]
     * for the ForgeManager to correctly retrieve
     * the necessary Forge Image object. So, this implementation
     * is a limitation on how big of an image can be rendered
     * using arrayfire graphics funtionality */
    assert(w <= 2ll<<16);
    assert(h <= 2ll<<16);
    long long key = ((w & _16BIT) << 16) | (h & _16BIT);
    key = (((key << 16) | mode) << 16) | type;

    ImgMapIter iter = mImgMap.find(key);
    if (iter==mImgMap.end()) {
        fg::Image* temp = new fg::Image(w, h, mode, type);
        mImgMap[key] = temp;
    }

    return mImgMap[key];
}

fg::Plot* ForgeManager::getPlot(int nPoints, fg::dtype dtype, fg::PlotType ptype, fg::MarkerType mtype)
{
    /* nPoints needs to fall in the range of [0, 2^48]
     * for the ForgeManager to correctly retrieve
     * the necessary Forge Plot object. So, this implementation
     * is a limitation on how big of an plot graph can be rendered
     * using arrayfire graphics funtionality */
    assert(nPoints <= 2ll<<48);
    long long key = ((nPoints & _48BIT) << 48);
    key |= (((((dtype & 0x000F) << 12) | (ptype & 0x000F)) << 8) | (mtype & 0x000F));

    PltMapIter iter = mPltMap.find(key);
    if (iter==mPltMap.end()) {
        fg::Plot* temp = new fg::Plot(nPoints, dtype, ptype, mtype);
        mPltMap[key] = temp;
    }

    return mPltMap[key];
}

fg::Plot3* ForgeManager::getPlot3(int nPoints, fg::dtype dtype, fg::PlotType ptype, fg::MarkerType mtype)
{
    /* nPoints needs to fall in the range of [0, 2^48]
     * for the ForgeManager to correctly retrieve
     * the necessary Forge Plot object. So, this implementation
     * is a limitation on how big of an plot graph can be rendered
     * using arrayfire graphics funtionality */
    assert(nPoints <= 2ll<<48);
    long long key = ((nPoints & _48BIT) << 48);
    key |= (((((dtype & 0x000F) << 12) | (ptype & 0x000F)) << 8) | (mtype & 0x000F));

    Plt3MapIter iter = mPlt3Map.find(key);
    if (iter==mPlt3Map.end()) {
        fg::Plot3* temp = new fg::Plot3(nPoints, dtype, ptype, mtype);
        mPlt3Map[key] = temp;
    }

    return mPlt3Map[key];
}

fg::Histogram* ForgeManager::getHistogram(int nBins, fg::dtype type)
{
    /* nBins needs to fall in the range of [0, 2^48]
     * for the ForgeManager to correctly retrieve
     * the necessary Forge Histogram object. So, this implementation
     * is a limitation on how big of an histogram data can be rendered
     * using arrayfire graphics funtionality */
    assert(nBins <= 2ll<<48);
    long long key = ((nBins & _48BIT) << 48) | (type & _16BIT);

    HstMapIter iter = mHstMap.find(key);
    if (iter==mHstMap.end()) {
        fg::Histogram* temp = new fg::Histogram(nBins, type);
        mHstMap[key] = temp;
    }

    return mHstMap[key];
}

fg::Surface* ForgeManager::getSurface(int nX, int nY, fg::dtype type)
{
    /* nX * nY needs to fall in the range of [0, 2^48]
     * for the ForgeManager to correctly retrieve
     * the necessary Forge Plot object. So, this implementation
     * is a limitation on how big of an plot graph can be rendered
     * using arrayfire graphics funtionality */
    assert(nX * nY <= 2ll<<48);
    long long key = (((nX * nY) & _48BIT) << 48) | (type & _16BIT);

    SfcMapIter iter = mSfcMap.find(key);
    if (iter==mSfcMap.end()) {
        fg::Surface* temp = new fg::Surface(nX, nY, type);
        mSfcMap[key] = temp;
    }

    return mSfcMap[key];
}

void ForgeManager::destroyResources()
{
    /* clear all OpenGL resource objects (images, plots, histograms etc) first
     * and then delete the windows */
    for(ImgMapIter iter = mImgMap.begin(); iter != mImgMap.end(); iter++)
        delete (iter->second);

    for(PltMapIter iter = mPltMap.begin(); iter != mPltMap.end(); iter++)
        delete (iter->second);

    for(HstMapIter iter = mHstMap.begin(); iter != mHstMap.end(); iter++)
        delete (iter->second);

    delete getFont(true);
    delete getMainWindow(true);
}

}

#endif
