#include "MTexture.h"
#include <maya/MStatus.h>
#include <maya/MGlobal.h>
#include "MNormalMapConverter.h"
MTexture::MTexture()
{
    
    m_levels = NULL;
    m_numLevels = 0;
}
#define MIN(x, y) (((x) < (y)) ? (x) : (y) )
#define MAX(x, y) (((x) > (y)) ? (x) : (y) )
bool MTexture::load(
MString filename, 
 
                    MTexture::Type type, 
                    bool mipmapped ,
                    GLenum target )
{
    if (!stat)
    {
        return false;
    }
    return set( image, type, mipmapped, target );
}
bool MTexture::set(
MImage &image, Type type, 
 
                   bool mipmapped , 
                   GLenum target )
{
    unsigned int i; 
    
    
    
    m_type = type;
    if ( (m_type == RGBA) || (m_type == NMAP) )
    {
        m_internalFormat = GL_RGBA8;
        m_format = GL_RGBA;
        m_componentFormat = GL_UNSIGNED_BYTE;
    }
    else if (m_type == HILO)
    {
#if NVIDIA_SPECIFIC
        m_internalFormat = GL_SIGNED_HILO_NV;
        m_format = GL_HILO_NV;
        m_componentFormat = GL_SHORT;
#endif
    }
    else assert(0);
    
    assert(stat);
    m_mipmapped = mipmapped;
    unsigned int maxWidthLevels  = highestPowerOf2(m_width);
    unsigned int maxHeightLevels = highestPowerOf2(m_height);
    
    
    bool widthIsExponent = (m_width == (unsigned int) (1 << maxWidthLevels));
    bool heightIsExponent = (m_height == (unsigned int) (1 << maxHeightLevels));
    if (!widthIsExponent || !heightIsExponent)
    {
        
        if (!widthIsExponent)
            maxWidthLevels++;
        if (!heightIsExponent)
            maxHeightLevels++;
        
        m_width = 1 << maxWidthLevels;
        m_height = 1 << maxHeightLevels;
        image.
resize(m_width, m_height, 
false);
    }
    
    if (m_levels != NULL)
    {
        for (i=0; i < m_numLevels; i++)
        {
            if (m_levels[i])
            {
                delete [] m_levels[i];
                m_levels[i] = NULL;
            }
        }       
        delete [] m_levels;
    }
    
    
    
    m_numLevels = mipmapped ? MAX(maxWidthLevels, maxHeightLevels) + 1 : 1;
    
    m_levels = new unsigned char* [m_numLevels];
    for (i=0; i < m_numLevels; i++)
    {
        m_levels[i] = new unsigned char [width(i) * height(i) * 4];
    }
    
    memcpy(m_levels[0], image.
pixels(), m_width * m_height * 4);
    
    
    
    
    
    
    
    
    
    for (unsigned int current_level = 1; current_level < m_numLevels; current_level++)
    {
        unsigned int width_ratio = width(i-1) / width(i);
        unsigned int height_ratio = height(i-1) / height(i-1);
        unsigned int previous_level = current_level - 1;
        for (unsigned int target_t = 0; target_t < height(current_level); target_t++)
        {
            for (unsigned int target_s = 0; target_s < width(current_level); target_s++)
            {
                
                unsigned int source_s = target_s * width_ratio;
                unsigned int source_t = target_t * height_ratio;
                unsigned int source_s2 = source_s + ((width_ratio == 2) ? 1 : 0);
                unsigned int source_t2 = source_t + ((height_ratio == 2) ? 1 : 0);
                unsigned char *destination  = internalFetch(target_s,   target_t,   current_level);
                unsigned char *source1      = internalFetch(source_s,   source_t,   previous_level);
                unsigned char *source2      = internalFetch(source_s2,  source_t,   previous_level);
                unsigned char *source3      = internalFetch(source_s,   source_t2,  previous_level);
                unsigned char *source4      = internalFetch(source_s2,  source_t2,  previous_level);
                
                unsigned int average1 = (*source1++ + *source2++ + *source3++ + *source4++) / 4;
                *destination++ = average1;
                unsigned int average2 = (*source1++ + *source2++ + *source3++ + *source4++) / 4;
                *destination++ = average2;
                unsigned int average3 = (*source1++ + *source2++ + *source3++ + *source4++) / 4;
                *destination++ = average3;
                unsigned int average4 = (*source1++ + *source2++ + *source3++ + *source4++) / 4;
                *destination++ = average4;
            }
        }
    }
    if( type == NMAP )
    {
        
        
        MNormalMapConverter mapConverter;
        for (unsigned int i = 0; i < m_numLevels; i++)
        {
            mapConverter.convertToNormalMap( m_levels[i], width(i), height(i), MNormalMapConverter::RGBA, 2.0f );
        }
    }
    specify(target);
    return true;
}
bool MTexture::specify(GLenum target )
{
    assert(glGetError() == GL_NO_ERROR);
    m_texObj.bind();
    assert(glGetError() == GL_NO_ERROR);
    for (unsigned int i=0; i < m_numLevels; i++)
    {
        glTexImage2D(target, i, m_internalFormat, width(i), height(i), 0,
                     m_format, m_componentFormat, m_levels[i]);
        assert(glGetError() == GL_NO_ERROR);
    }
    if (mipmapped())
    {
        
        m_texObj.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        assert(glGetError() == GL_NO_ERROR);
        m_texObj.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        assert(glGetError() == GL_NO_ERROR);
    }
    else
    {
        m_texObj.parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        m_texObj.parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    m_texObj.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP);
    m_texObj.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP);
    return true;
}
bool MTexture::bind()
{
    m_texObj.bind();
    
    return true;
}
int highestPowerOf2(int num)
{
    int power = 0;
    while (num > 1)
    {
        power++;
        num = num >> 1;
    }
    return power;
}