//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
//  Copyright (C) 2010-2015 Steve Baker, Joerg Henrichs
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 3
//  of the License, or (at your option) any later version.
//
//  This program 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
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#include "material.h"

#include <stdexcept>
#include <iostream>


#include "../config/user_config.h"
#include "../config/stk_config.h"
#include "../guiengine/engine.h"
#include "central_settings.h"
#include "glwrap.h"
#include "irr_driver.h"
#include "shaders.h"
#include "../io/file_manager.h"
#include "../io/xml_node.h"
#include "../utils/string_utils.h"
#include "../modes/world.h"
#include "../maps/map.h"
#include "../utils/log.h"
#include "../utils/vs.h"

#include <IMaterialRendererServices.h>
#include <ISceneNode.h>
#include <IShaderConstantSetCallBack.h>

using namespace irr::video;

const unsigned int UCLAMP = 1;
const unsigned int VCLAMP = 2;

//-----------------------------------------------------------------------------
/** Create a new material using the parameters specified in the xml file.
 *  \param node Node containing the parameters for this material.
 */
Material::Material(const XMLNode *node, bool deprecated)
{
    m_shader_type = SHADERTYPE_SOLID;
    m_deprecated = deprecated;

    node->get("name",      &m_texname);
    if (m_texname=="")
    {
        throw std::runtime_error("[Material] No texture name specified "
                                 "in file\n");
    }

    std::string relativePath = file_manager->searchTexture(m_texname);
    if (relativePath.size() == 0)
        Log::warn("Material", "Cannot determine texture full path : <%s>", m_texname.c_str());
    else
        m_full_path = file_manager->getFileSystem()->getAbsolutePath(relativePath.c_str()).c_str();
    init();

    node->get("lazy-load", &m_lazy_load);
    bool b = false;


    std::string s;


    s = "";
    if (node->get("shader", &s))
    {
        if (s == "solid")
        {
            m_shader_type = SHADERTYPE_SOLID;
        }
        else if (s == "unlit")
        {
            m_shader_type = SHADERTYPE_SOLID_UNLIT;
        }
        else if (s == "additive")
        {
            m_shader_type = SHADERTYPE_ADDITIVE;
        }
        else if (s == "alphatest")
        {
            m_shader_type = SHADERTYPE_ALPHA_TEST;
        }
        else if (s == "alphablend")
        {
            m_shader_type = SHADERTYPE_ALPHA_BLEND;
        }
        else if (s == "spheremap")
        {
            m_shader_type = SHADERTYPE_SPHERE_MAP;
        }

        else
        {
            Log::warn("Material", "Unknown shader type <%s> for <%s>", s.c_str(), m_texname.c_str());
        }
    }

    if (m_shader_type == SHADERTYPE_SOLID)
    {
        node->get("normal-map", &m_normal_map_tex);
    }


    // Terrain-specifc sound effect
    /*
    const unsigned int children_count = node->getNumNodes();
    for (unsigned int i=0; i<children_count; i++)
    {
        const XMLNode *child_node = node->getNode(i);


        else
        {
            Log::warn("material", "Unknown node type '%s' for texture "
                      "'%s' - ignored.",
                      child_node->getName().c_str(), m_texname.c_str());
        }

    }   // for i <node->getNumNodes()
    */


    install(/*is_full_path*/false);
}   // Material

//-----------------------------------------------------------------------------
/** Create a standard material using the default settings for materials.
 *  \param fname Name of the texture file.
 *  \param is_full_path If the fname contains the full path.
 */
Material::Material(const std::string& fname, bool is_full_path,
                   bool complain_if_not_found, bool load_texture)
{
    m_deprecated = false;

    m_texname = fname;
    init();
    m_full_path = file_manager->getFileSystem()->getAbsolutePath(
        file_manager->searchTexture(m_texname).c_str()).c_str();

    if (load_texture)
        install(is_full_path, complain_if_not_found);
}   // Material

//-----------------------------------------------------------------------------
/** Inits all material data with the default settings.
 */
void Material::init()
{
    m_lazy_load                 = false;
    m_texture                   = NULL;
    m_shader_type               = SHADERTYPE_SOLID;

}   // init

//-----------------------------------------------------------------------------
void Material::install(bool is_full_path, bool complain_if_not_found)
{
    // Don't load a texture that is lazily loaded.
    if(m_lazy_load) return;

    const std::string &full_path = is_full_path
                                 ? m_texname
                                 : file_manager->searchTexture(m_texname);

    if (complain_if_not_found && full_path.size() == 0)
    {
        Log::error("material", "Cannot find texture '%s'.", m_texname.c_str());
        m_texture = NULL;
    }
    else
    {
        m_texture = irr_driver->getTexture(full_path,
                                           false, //isPreMul(),
                                           false, //isPreDiv(),
                                           complain_if_not_found);
    }

    if (m_texture == NULL) return;

    // now set the name to the basename, so that all tests work as expected
    m_texname  = StringUtils::getBasename(m_texname);

    m_texture->grab();
}   // install

//-----------------------------------------------------------------------------
Material::~Material()
{
    if (m_texture != NULL)
    {
        m_texture->drop();
        if(m_texture->getReferenceCount()==1)
            irr_driver->removeTexture(m_texture);
    }

    // If a special sfx is installed (that isn't part of stk itself), the
    // entry needs to be removed from the sfx_manager's mapping, since other
    // tracks might use the same name.

}   // ~Material


//-----------------------------------------------------------------------------
/** Sets the appropriate flags in an irrlicht SMaterial.
 *  \param material The irrlicht SMaterial which gets the flags set.
 */
void  Material::setMaterialProperties(video::SMaterial *m, scene::IMeshBuffer* mb)
{
    if (m_deprecated ||
        (m->getTexture(0) != NULL &&
         ((core::stringc)m->getTexture(0)->getName()).find("deprecated") != -1))
    {
        Log::warn("material", "Track uses deprecated texture '%s'",
                  m_texname.c_str());
    }

    if (CVS->isGLSL())
    {
        ITexture *tex;
        ITexture *glossytex = getUnicolorTexture(SColor(0, 0, 0, 0));

        switch (m_shader_type)
        {
        case SHADERTYPE_SOLID_UNLIT:
            m->MaterialType = Shaders::getShader(ES_OBJECT_UNLIT);
            m->setTexture(1, glossytex);
            return;
        case SHADERTYPE_ALPHA_TEST:
            m->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
            m->setTexture(1, glossytex);
            return;
        case SHADERTYPE_ALPHA_BLEND:
            m->MaterialType = video::EMT_ONETEXTURE_BLEND;
            m->MaterialTypeParam =
                pack_textureBlendFunc(video::EBF_SRC_ALPHA,
                video::EBF_ONE_MINUS_SRC_ALPHA,
                video::EMFN_MODULATE_1X,
                video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
            return;
        case SHADERTYPE_ADDITIVE:
            m->MaterialType = video::EMT_ONETEXTURE_BLEND;
            m->MaterialTypeParam = pack_textureBlendFunc(video::EBF_SRC_ALPHA,
                video::EBF_ONE,
                video::EMFN_MODULATE_1X,
                video::EAS_TEXTURE |
                video::EAS_VERTEX_COLOR);
            return;
        case SHADERTYPE_SPHERE_MAP:
            m->MaterialType = Shaders::getShader(ES_SPHERE_MAP);
            m->setTexture(1, glossytex);
            return;
        }

        if (!m->getTexture(0))
            m->setTexture(0, getUnicolorTexture(SColor(255, 255, 255, 255)));

        if (m_normal_map_tex.size() > 0)
        {
            tex = irr_driver->getTexture(m_normal_map_tex);
            m->setTexture(2, tex);

            // Material and shaders
            m->MaterialType = Shaders::getShader(ES_NORMAL_MAP);
            m->setTexture(1, glossytex);
            return;
        }

        // Detail map : move it to slot 3 and add glossy to slot 2
        // Sometimes the material will be parsed twice, in this case we dont want to swap 1 and 2 again.
        if (mb && mb->getVertexType() == video::EVT_2TCOORDS)
        {
            if (m->getTexture(1) != glossytex)
                m->setTexture(2, m->getTexture(1));
            if (!m->getTexture(2))
                m->setTexture(2, getUnicolorTexture(SColor(255, 255, 255, 255)));
        }
        m->setTexture(1, glossytex);
    }


    if (m_shader_type == SHADERTYPE_SOLID_UNLIT)
    {
        m->AmbientColor = video::SColor(255, 255, 255, 255);
        m->DiffuseColor = video::SColor(255, 255, 255, 255);
        m->EmissiveColor = video::SColor(255, 255, 255, 255);
        m->SpecularColor = video::SColor(255, 255, 255, 255);
    }

    if (m_shader_type == SHADERTYPE_ALPHA_TEST)
    {
        m->MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
    }

    if (m_shader_type == SHADERTYPE_ALPHA_BLEND)
    {
        // EMT_TRANSPARENT_ALPHA_CHANNEL doesn't include vertex color alpha into
        // account, which messes up fading in/out effects. So we use the more
        // customizable EMT_ONETEXTURE_BLEND instead.
        m->MaterialType = video::EMT_ONETEXTURE_BLEND;
        m->MaterialTypeParam =
            pack_textureBlendFunc(video::EBF_SRC_ALPHA,
                                  video::EBF_ONE_MINUS_SRC_ALPHA,
                                  video::EMFN_MODULATE_1X,
                                  video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
    }

    if (m_shader_type == SHADERTYPE_ADDITIVE)
    {
        // EMT_TRANSPARENT_ADD_COLOR doesn't include vertex color alpha into
        // account, which messes up fading in/out effects. So we use the
        // more customizable EMT_ONETEXTURE_BLEND instead
        m->MaterialType = video::EMT_ONETEXTURE_BLEND;
        m->MaterialTypeParam = pack_textureBlendFunc(video::EBF_SRC_ALPHA,
            video::EBF_ONE,
            video::EMFN_MODULATE_1X,
            video::EAS_TEXTURE |
            video::EAS_VERTEX_COLOR);
    }

    if (m_shader_type == SHADERTYPE_SPHERE_MAP)
    {
        m->MaterialType = video::EMT_SPHERE_MAP;
    }

    if (m_shader_type == SHADERTYPE_SOLID && m_normal_map_tex.size() > 0)
    {
        // remove normal map texture so that it's not blended with the rest
        m->setTexture(1, NULL);
    }

    if (m_shader_type == SHADERTYPE_SPLATTING)
    {
        m->MaterialType = video::EMT_SOLID;
    }


    // Modify lightmap materials so that vertex colors are taken into account.
    // But disable lighting because we assume all lighting is already part
    // of the lightmap
    if (m->MaterialType == video::EMT_LIGHTMAP)
    {
        m->MaterialType = video::EMT_LIGHTMAP_LIGHTING;
        m->AmbientColor  = video::SColor(255, 255, 255, 255);
        m->DiffuseColor  = video::SColor(255, 255, 255, 255);
        m->EmissiveColor = video::SColor(255, 255, 255, 255);
        m->SpecularColor = video::SColor(255, 255, 255, 255);
    }



#ifdef DEBUG
    if(UserConfigParams::m_rendering_debug)
    {
        m->Shininess = 100.0f;
        m->DiffuseColor  = video::SColor(200, 255, 0, 0);
        m->AmbientColor  = video::SColor(200, 0, 0, 255);
        m->SpecularColor = video::SColor(200, 0, 255, 0);
    }
#endif


    // Material color
    m->ColorMaterial = video::ECM_DIFFUSE_AND_AMBIENT;

#ifdef DEBUG
    if (UserConfigParams::m_rendering_debug)
    {
        m->ColorMaterial = video::ECM_NONE; // Override one above
    }
#endif

} // setMaterialProperties


//-----------------------------------------------------------------------------

/** Callback from LOD nodes to create some effects */
void Material::onMadeVisible(scene::IMeshBuffer* who)
{
    if (!CVS->isGLSL()) return;
}

//-----------------------------------------------------------------------------

/** Callback from LOD nodes to create some effects */
void Material::onHidden(scene::IMeshBuffer* who)
{
    if (!CVS->isGLSL()) return;
}

//-----------------------------------------------------------------------------

void Material::isInitiallyHidden(scene::IMeshBuffer* who)
{
    if (!CVS->isGLSL()) return;
}

//-----------------------------------------------------------------------------

