/***********************************************************************************
 *                                                                                 *
 * Voreen - The Volume Rendering Engine                                            *
 *                                                                                 *
 * Copyright (C) 2005-2013 University of Muenster, Germany.                        *
 * Visualization and Computer Graphics Group <http://viscg.uni-muenster.de>        *
 * For a list of authors please refer to the file "CREDITS.txt".                   *
 *                                                                                 *
 * This file is part of the Voreen software package. Voreen is free software:      *
 * you can redistribute it and/or modify it under the terms of the GNU General     *
 * Public License version 2 as published by the Free Software Foundation.          *
 *                                                                                 *
 * Voreen is distributed in the hope that it will be useful, but WITHOUT ANY       *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR   *
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.      *
 *                                                                                 *
 * You should have received a copy of the GNU General Public License in the file   *
 * "LICENSE.txt" along with this file. If not, see <http://www.gnu.org/licenses/>. *
 *                                                                                 *
 * For non-commercial academic use see the license exception specified in the file *
 * "LICENSE-academic.txt". To get information about commercial licensing please    *
 * contact the authors.                                                            *
 *                                                                                 *
 ***********************************************************************************/

#include "voreen/core/datastructures/transfunc/transfunc2dprimitives.h"
#include "voreen/core/datastructures/transfunc/transfuncprimitive.h"

#include "tgt/glmath.h"
#include "tgt/gpucapabilities.h"
#include "tgt/framebufferobject.h"

#ifdef VRN_MODULE_DEVIL
    #include <IL/il.h>
#endif

#include <fstream>

using tgt::Texture;

namespace voreen {

const std::string TransFunc2DPrimitives::loggerCat_("voreen.TransFunc2DPrimitives");

TransFunc2DPrimitives::TransFunc2DPrimitives(int width, int height)
    : TransFunc(width, height, 1, GL_RGBA, GL_FLOAT, Texture::LINEAR)
    , fbo_(0)
    , domainIntensity_(0.0f, 1.0f)
    , domainGradientMagnitude_(0.0f, 1.0f)
{
    loadFileFormats_.push_back("tfig");

    saveFileFormats_.push_back("tfig");
#ifdef VRN_MODULE_DEVIL
    saveFileFormats_.push_back("png");
#endif

}

TransFunc2DPrimitives::~TransFunc2DPrimitives() {
    if (fbo_) {
        delete fbo_;
        fbo_ = 0;
        LGL_ERROR;
    }

    std::vector<TransFuncPrimitive*>::iterator it;
    while (!primitives_.empty()) {
        it = primitives_.begin();
        delete *it;
        primitives_.erase(it);
    }
}

TransFunc* TransFunc2DPrimitives::clone() const {
    TransFunc2DPrimitives* func = new TransFunc2DPrimitives();

    func->updateFrom(this);

    return func;
}

void TransFunc2DPrimitives::updateFrom(const TransFunc* transfunc) {
    tgtAssert(transfunc, "null pointer passed");

    const TransFunc2DPrimitives* tf2D = dynamic_cast<const TransFunc2DPrimitives*>(transfunc);
    if (!tf2D) {
        LWARNING("updateFrom(): passed parameter is not of type TransFunc2DPrimitives");
        return;
    }

    dimensions_ = tf2D->dimensions_;
    format_ = tf2D->format_;
    dataType_ = tf2D->dataType_;
    filter_ = tf2D->filter_;
    domainIntensity_ = tf2D->domainIntensity_;
    domainGradientMagnitude_ = tf2D->domainGradientMagnitude_;

    primitives_.clear();
    std::vector<TransFuncPrimitive*>::const_iterator it;
    for(it = tf2D->primitives_.begin(); it!=tf2D->primitives_.end(); it++) {
        primitives_.push_back((*it)->clone());
    }

    textureInvalid_ = true;
}

std::string TransFunc2DPrimitives::getShaderDefines(const std::string& defineName) const {
    return TransFunc::getShaderDefines(defineName) + "#define CLASSIFICATION_REQUIRES_GRADIENT\n";
}

bool TransFunc2DPrimitives::operator==(const TransFunc2DPrimitives& tf) {

    // check type information
    if (dimensions_ != tf.dimensions_ || format_ != tf.format_ || dataType_ != tf.dataType_)
        return false;

    // textures available?
    if (!tex_ || !tf.tex_ || !tex_->getPixelData() || !tf.tex_->getPixelData())
        return false;

    // compare texture data
    return (memcmp(tex_->getPixelData(), tf.tex_->getPixelData(), tgt::hmul(tex_->getDimensions())*tex_->getBpp()) == 0);
}

bool TransFunc2DPrimitives::operator!=(const TransFunc2DPrimitives& tf) {
    return !(*this == tf);
}

bool TransFunc2DPrimitives::save(const std::string& filename) {
    //look for fileExtension
    std::string fileExtension;
    size_t dotPosition = filename.rfind(".");
    if (dotPosition == std::string::npos)
        return false;
    else
        fileExtension = filename.substr(dotPosition+1);

    if (fileExtension == "tfig")
        return saveTfig(filename);
    else
        return saveImage(filename);
}

bool TransFunc2DPrimitives::saveTfig(const std::string& filename) {

    // open file stream
    std::ofstream stream(filename.c_str(), std::ios_base::out);
    if (stream.fail()) {
        LWARNING("Unable to open file " << filename << " for writing.");
        return false;
    }

    // serialize to stream
    bool success = true;
    try {
        XmlSerializer s(filename);
        s.serialize("TransFunc2DPrimitives", this);

        s.write(stream);
        if (stream.bad()) {
            LWARNING("Unable to write to file: " << filename);
            success = false;
        }
        stream.close();
    }
    catch (SerializationException &e) {
        LWARNING("SerializationException: " << e.what());
        stream.close();
        success = false;
    }

    // log result
    if (success)
        LINFO("Saved transfer function to file: " << filename);
    else
        LWARNING("Saving transfer function failed.");

    return success;

}

bool TransFunc2DPrimitives::saveImage(const std::string& filename) {
#ifdef VRN_MODULE_DEVIL
    //extract file extension
    std::string fileExtension;
    size_t dotPosition = filename.rfind(".");
    fileExtension = filename.substr(dotPosition+1);

    //IL does _NOT_ overwrite files by default
    ilEnable(IL_FILE_OVERWRITE);
    //download texture and save as png:
    ILuint img;
    ilGenImages(1, &img);
    ilBindImage(img);

    GLubyte* im = new GLubyte[tex_->getWidth()*tex_->getHeight()*4];

    tex_->bind();
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, im);

    ilTexImage(tex_->getWidth(), tex_->getHeight(), 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, im);

    if (fileExtension == "png")
        ilSave(IL_PNG, (ILstring)filename.c_str());
    else {
        ilDeleteImages(1, &img);
        delete[] im;
        return false;
    }

    ilDeleteImages(1, &img);
    delete[] im;
    return true;
#else
    LERROR("Saving of " << filename  << " failed: No DevIL support.");
    return false;
#endif // VRN_MODULE_DEVIL
}


bool TransFunc2DPrimitives::load(const std::string& filename) {
    // Extract the file extension
    std::string fileExtension;
    size_t dotPosition = filename.rfind(".");
    if (dotPosition != std::string::npos)
        // => the last (seperating) dot was found
        fileExtension = filename.substr(dotPosition+1);
    else
        return false;

    if (fileExtension == "tfig")
        return loadTfig(filename);
    else
        return false;
}

bool TransFunc2DPrimitives::loadTfig(const std::string& filename) {

    // open file stream
    std::ifstream stream(filename.c_str(), std::ios_base::in);
    if (stream.fail()) {
        LWARNING("Unable to open file " << filename << " for reading.");
        return false;
    }

    // deserialize from stream
    bool success = true;
    try {
        XmlDeserializer d(filename);
        d.read(stream);
        d.deserialize("TransFunc2DPrimitives", *this);
        stream.close();
    }
    catch (SerializationException &e) {
        LWARNING("SerializationException: " << e.what());
        stream.close();
        success = false;
    }

    // log result
    if (success)
        LINFO("Loaded transfer function from file: " << filename);
    else
        LWARNING("Loading transfer function failed.");

    return success;
}

void TransFunc2DPrimitives::addPrimitive(TransFuncPrimitive* p) {
    primitives_.push_back(p);
}

void TransFunc2DPrimitives::removePrimitive(TransFuncPrimitive* p) {
    std::vector<TransFuncPrimitive*>::iterator it;
    for (it = primitives_.begin(); it != primitives_.end(); ++it) {
        if (*it == p) {
            delete *it;
            primitives_.erase(it);
            break;
        }
    }
}

void TransFunc2DPrimitives::clear() {
    std::vector<TransFuncPrimitive*>::iterator it;
    while (!primitives_.empty()) {
        it = primitives_.begin();
        delete *it;
        primitives_.erase(it);
    }

    invalidateTexture();
}

void TransFunc2DPrimitives::updateTexture() {
    // (re-)create tf texture and fbo, if necessary
    if (!fbo_ || !tex_ || (tex_->getDimensions() != dimensions_))
        createTex();
    if (!tex_) {
        LERROR("Failed to create texture");
        return;
    }
    // FBO should have been created by createTex()
    if (!fbo_) {
        LERROR("No framebuffer object");
        return;
    }

    // activate FBO
    fbo_->activate();
    if (!fbo_->isComplete()) {
        LERROR("Invalid framebuffer object");
        fbo_->deactivate();
        return;
    }
    LGL_ERROR;

    // render primitives to fbo
    glViewport(0, 0, tex_->getWidth(), tex_->getHeight());

    // clear previous content
    glClear(GL_COLOR_BUFFER_BIT);

    // set correct projection and modelview matrices
    MatStack.matrixMode(tgt::MatrixStack::PROJECTION);
    MatStack.pushMatrix();
    MatStack.loadIdentity();
    glOrtho(0.f, 1.f, 0.f, 1.f, -2.f, 1.f);

    MatStack.matrixMode(tgt::MatrixStack::MODELVIEW);
    MatStack.pushMatrix();
    MatStack.loadIdentity();

    // paint primitives
    paint();

    MatStack.matrixMode(tgt::MatrixStack::PROJECTION);
    MatStack.popMatrix();
    MatStack.matrixMode(tgt::MatrixStack::MODELVIEW);
    MatStack.popMatrix();

    fbo_->deactivate();
    LGL_ERROR;

    textureInvalid_ = false;
}

void TransFunc2DPrimitives::paint() {
    for (size_t i = 0; i < primitives_.size(); ++i)
        primitives_[i]->paint();
}

void TransFunc2DPrimitives::paintForSelection() {
    for (size_t i = 0; i < primitives_.size(); ++i)
        primitives_[i]->paintForSelection(static_cast<GLubyte>(i));
}

void TransFunc2DPrimitives::paintInEditor() {
    for (size_t i = 0; i < primitives_.size(); ++i)
        primitives_[i]->paintInEditor();
}

const TransFuncPrimitive* TransFunc2DPrimitives::getPrimitive(int i) const {
    if ((i < 0) || (i >= static_cast<int>(primitives_.size())))
        return 0;
    else
        return primitives_[i];
}

TransFuncPrimitive* TransFunc2DPrimitives::getPrimitive(int i) {
    if ((i < 0) || (i >= static_cast<int>(primitives_.size())))
        return 0;
    else
        return primitives_[i];
}

TransFuncPrimitive* TransFunc2DPrimitives::getPrimitiveForClickedControlPoint(const tgt::vec2& pos) const {
    if (primitives_.empty())
        return 0;

    size_t min = 0;
    // A distance of 2 can never happen because the canvas is normalized to [0,1]x[0,1],
    // so this value is huge enough.
    float mindist = 2.f;
    float d;
    for (size_t i = 0; i < primitives_.size(); ++i) {
         d = primitives_[i]->getClosestControlPointDist(pos);
         if ((d < mindist) && (d < primitives_[i]->getControlPointSize())) {
            mindist = d;
            min = i;
         }
    }
    if (mindist == 2.f)
        return 0;
    else
        return primitives_[min];
}

void TransFunc2DPrimitives::serialize(XmlSerializer& s) const {
    TransFunc::serialize(s);
    s.serialize("Primitives", primitives_, "Primitive");
    s.serialize("domainIntensity", domainIntensity_);
    s.serialize("domainGradientMagnitude", domainGradientMagnitude_);
}

void TransFunc2DPrimitives::deserialize(XmlDeserializer& s) {
    TransFunc::deserialize(s);
    s.deserialize("Primitives", primitives_, "Primitive");
    s.optionalDeserialize("domainIntensity", domainIntensity_, tgt::vec2(0.0f, 1.0f));
    s.optionalDeserialize("domainGradientMagnitude", domainGradientMagnitude_, tgt::vec2(0.0f, 1.0f));
}

void TransFunc2DPrimitives::reset() {
    clear();
}

tgt::vec2 TransFunc2DPrimitives::getDomain(int dimension /*= 0*/) const {
    if(dimension == 0)
        return domainIntensity_;
    else if(dimension == 1)
        return domainGradientMagnitude_;
    else {
        tgtAssert(false, "invalid dimension");
        return tgt::vec2(0.0f, 1.0f);
    }
}

void TransFunc2DPrimitives::setDomain(tgt::vec2 domain, int dimension) {
    if(dimension == 0)
        domainIntensity_ = domain;
    else if(dimension == 1)
        domainGradientMagnitude_ = domain;
    else {
        tgtAssert(false, "invalid dimension");
    }
}

void TransFunc2DPrimitives::createTex() {

    // create or clear FBO
    if (!fbo_) {
        LDEBUG("Creating FBO...");
        fbo_ = new tgt::FramebufferObject();
        LGL_ERROR;
        if (!fbo_) {
            LERROR("Failed to initialize framebuffer object");
            delete fbo_;
            fbo_ = 0;
            return;
        }
    }
    else {
        LDEBUG("Clearing FBO...");
        fbo_->activate();
        fbo_->detachAll();
        fbo_->deactivate();
        LGL_ERROR;
    }

    // create tf texture
    TransFunc::createTex();
    if (!tex_) {
        LERROR("Failed to create texture");
        return;
    }
    tex_->uploadTexture();
    LGL_ERROR;

    // attach texture to fbo
    fbo_->activate();
    fbo_->attachTexture(tex_);
    fbo_->isComplete();
    fbo_->deactivate();
    LGL_ERROR;
}

} // namespace voreen
