/*****************************************************************************************
 *                                                                                       *
 * OpenSpace                                                                             *
 *                                                                                       *
 * Copyright (c) 2014-2016                                                               *
 *                                                                                       *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this  *
 * software and associated documentation files (the "Software"), to deal in the Software *
 * without restriction, including without limitation the rights to use, copy, modify,    *
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    *
 * permit persons to whom the Software is furnished to do so, subject to the following   *
 * conditions:                                                                           *
 *                                                                                       *
 * The above copyright notice and this permission notice shall be included in all copies *
 * or substantial portions of the Software.                                              *
 *                                                                                       *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   *
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         *
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    *
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  *
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  *
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         *
 ****************************************************************************************/

#include <openspace/rendering/renderable.h>
#include <openspace/util/factorymanager.h>
#include <openspace/util/updatestructures.h>
#include <openspace/util/spicemanager.h>
#include <openspace/scene/scenegraphnode.h>

#include <openspace/documentation/verifier.h>

#include <ghoul/misc/dictionary.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/misc/assert.h>

namespace {
    const std::string _loggerCat = "Renderable";
    const std::string keyStart = "StartTime";
    const std::string keyEnd = "EndTime";
    const std::string KeyType = "Type";
}

namespace openspace {

Documentation Renderable::Documentation() {
    using namespace openspace::documentation;

    return {
        "Renderable",
        "renderable",
        {
        {
            KeyType,
            new StringAnnotationVerifier("A valid Renderable created by a factory"),
            "This key specifies the type of Renderable that gets created. It has to be one"
            "of the valid Renderables that are available for creation (see the "
            "FactoryDocumentation for a list of possible Renderables), which depends on "
            "the configration of the application",
            Optional::No
        }
        }
    };
}

Renderable* Renderable::createFromDictionary(const ghoul::Dictionary& dictionary) {
    // The name is passed down from the SceneGraphNode
    std::string name;
    bool success = dictionary.getValue(SceneGraphNode::KeyName, name);
    ghoul_assert(success, "The SceneGraphNode did not set the 'name' key");

    documentation::testSpecificationAndThrow(Documentation(), dictionary, "Renderable");

    std::string renderableType = dictionary.value<std::string>(KeyType);

    auto factory = FactoryManager::ref().factory<Renderable>();
    Renderable* result = factory->create(renderableType, dictionary);
    if (result == nullptr) {
        LERROR("Failed to create a Renderable object of type '" << renderableType << "'");
        return nullptr;
    }

    return result;
}

Renderable::Renderable()
    : _enabled("enabled", "Is Enabled", true)
    , _renderBin(RenderBin::Opaque)
    , _startTime("")
    , _endTime("")
    , _hasTimeInterval(false)
{}

Renderable::Renderable(const ghoul::Dictionary& dictionary)
    : _enabled("enabled", "Is Enabled", true)
    , _renderBin(RenderBin::Opaque)
    , _startTime("")
    , _endTime("")
    , _hasTimeInterval(false)
{
    setName("renderable");

    ghoul_assert(
        dictionary.hasKeyAndValue<std::string>(SceneGraphNode::KeyName),
        "SceneGraphNode must specify '" << SceneGraphNode::KeyName << "'"
    );

    dictionary.getValue(keyStart, _startTime);
    dictionary.getValue(keyEnd, _endTime);

    if (_startTime != "" && _endTime != "") {
        _hasTimeInterval = true;
    }

    addProperty(_enabled);
}

Renderable::~Renderable() {}

void Renderable::setBoundingSphere(PowerScaledScalar boundingSphere) {
    boundingSphere_ = std::move(boundingSphere);
}

PowerScaledScalar Renderable::getBoundingSphere() {
    return boundingSphere_;
}

void Renderable::update(const UpdateData&) {}

void Renderable::render(const RenderData& data, RendererTasks&) {
    render(data);
}

void Renderable::render(const RenderData& data) {}

void Renderable::postRender(const RenderData& data) {}

void Renderable::setPscUniforms(
    ghoul::opengl::ProgramObject& program, 
    const Camera& camera,
    const PowerScaledCoordinate& position) 
{
    program.setUniform("campos", camera.position().vec4());
    program.setUniform("objpos", position.vec4());
    program.setUniform("camrot", glm::mat4(camera.viewRotationMatrix()));
    program.setUniform("scaling", camera.scaling());
}

Renderable::RenderBin Renderable::renderBin() const {
    return _renderBin;
}

void Renderable::setRenderBin(RenderBin bin) {
    _renderBin = bin;
}

bool Renderable::matchesRenderBinMask(int binMask) {
    return binMask & static_cast<int>(renderBin());
}

bool Renderable::isVisible() const {
    return _enabled;
}

bool Renderable::hasTimeInterval() {
    return _hasTimeInterval;
}

bool Renderable::getInterval(double& start, double& end) {
    if (_startTime != "" && _endTime != "") {
        start = SpiceManager::ref().ephemerisTimeFromDate(_startTime);
        end = SpiceManager::ref().ephemerisTimeFromDate(_endTime);
        return true;
    }
    else
        return false;
}

bool Renderable::isReady() const {
    return true;
}

bool Renderable::isEnabled() const {
    return _enabled;
}

void Renderable::onEnabledChange(std::function<void(bool)> callback) {
    _enabled.onChange([=] () {
            callback(isEnabled());
    });
}

}  // namespace openspace
