#include "cgfxTextureCache.h"
#include "cgfxFindImage.h"
#include "cgfxProfile.h"
#include <maya/MHardwareRenderer.h>
#include <maya/MFileObject.h>
#include <maya/MGLFunctionTable.h>
#include "nv_dds.h"
#include <map>
namespace {
    
    
    
    bool textureInitPowerOfTwo(unsigned int val, unsigned int & retval)
    {
        unsigned int res = 0;               
        if (val)
        {
            
            
            val <<= 1;
            unsigned int low = 3;
            res = 1;
            while (val > low)
            {
                low <<= 1;
                res <<= 1;
            }
        }
        retval = res;
        return (res == (val>>1)) ? 1 : 0;
    }
    )
    {
        if (texFileName.
length() == 0) {
 
        }
        MString path = cgfxFindFile(texFileName);
 
        
        
        
        {
            path = cgfxFindFile(effectFile.
path() + texFileName);
        }
        return path;
    }
    
    bool allocateAndReadTexture(
        cgfxAttrDef::cgfxAttrType   attrType,
        GLuint&                     textureId
    )
    {
        if ( 0 == gGLFT )
        
        GLuint val;
        gGLFT->
glGenTextures(1, &val);
        textureId = val;
        nv_dds::CDDSImage image;
        {
            switch (attrType)
            {
                case cgfxAttrDef::kAttrTypeEnvTexture:
                case cgfxAttrDef::kAttrTypeCubeTexture:
                case cgfxAttrDef::kAttrTypeNormalizationTexture:
                    
                    image.load(path.
asChar(),
false);
                    break;
                default:
                    
                    
                    
                    
                    
                    
                    image.load(
                        cgfxProfile::getTexCoordOrientation() == cgfxProfile::TEXCOORD_OPENGL);
                    break;
            }
        }
        
        
        
        
        
        
        
        
        
        
        
        static unsigned char whitePixel[ 4] = { 255, 255, 255, 255};
        bool imageLoaded = false;
                        
        switch (attrType)
        {
            case cgfxAttrDef::kAttrTypeColor1DTexture:
                gGLFT->
glBindTexture(GL_TEXTURE_1D,textureId);
                if( image.is_valid())
                {
                    
                        GL_TEXTURE_1D, GL_GENERATE_MIPMAP_SGIS, image.get_num_mipmaps() == 0);
                    image.upload_texture1D();
                    imageLoaded = true;
                }
                else
                {
                    
                        GL_TEXTURE_1D, 0, GL_RGBA, 1, 0,
                        GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
                }
                break;
            case cgfxAttrDef::kAttrTypeColor2DTexture:
            case cgfxAttrDef::kAttrTypeNormalTexture:
            case cgfxAttrDef::kAttrTypeBumpTexture:
#if !defined(WIN32) && !defined(LINUX)
            case cgfxAttrDef::kAttrTypeColor2DRectTexture:
#endif
                gGLFT->glBindTexture(GL_TEXTURE_2D,textureId);
                if( image.is_valid())
                {
                    
                    gGLFT->glTexParameteri(
                        GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, image.get_num_mipmaps() == 0);
                    image.upload_texture2D();
                    imageLoaded = true;
                }
                else
                {
                    
                    
                    
                    
                    {
                        unsigned int width, height;
                        {
                            
                            
                            if (cgfxProfile::getTexCoordOrientation() ==
                                cgfxProfile::TEXCOORD_DIRECTX)
                            {
                            }
                            {
                                
                                
                                
                                if (width > 2 && height > 2)
                                {
                                    unsigned int p2Width, p2Height;
                                    bool widthPowerOfTwo  = textureInitPowerOfTwo(width,  p2Width);
                                    bool heightPowerOfTwo = textureInitPowerOfTwo(height, p2Height);
                                    if(!widthPowerOfTwo || !heightPowerOfTwo)
                                    {
                                        width = p2Width;
                                        height = p2Height;
                                        img.
resize( p2Width, p2Height, 
false );
                                    }
                                }
                                gGLFT->glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, true);
                                    GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                                    GL_RGBA, GL_UNSIGNED_BYTE, img.
pixels());
                                imageLoaded = true;
                            }
                        }
                    }
                }
                if (!imageLoaded) {
                    
                    gGLFT->glTexImage2D(
                        GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
                        GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
                }
                break;
            case cgfxAttrDef::kAttrTypeEnvTexture:
            case cgfxAttrDef::kAttrTypeCubeTexture:
            case cgfxAttrDef::kAttrTypeNormalizationTexture:
                {
                    gGLFT->glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, textureId);
                    if( image.is_valid()) {
                        gGLFT->glTexParameteri(
                            GL_TEXTURE_CUBE_MAP_ARB, GL_GENERATE_MIPMAP_SGIS,
                            image.get_num_mipmaps() == 0);
                        
                        for (int n = 0; n < 6; ++n)
                        {
                            
                            GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+n;
                            image.upload_texture2D(image.is_cubemap() ? n : 0, target);
                        }
                        imageLoaded = true;
                    }
                    else {
                        
                        
                        for (int n = 0; n < 6; ++n)
                        {
                            
                            GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+n;
                            gGLFT->glTexImage2D(
                                target, 0, GL_RGBA, 1, 1, 0,
                                GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
                        }
                    }
                    break;
                }
            case cgfxAttrDef::kAttrTypeColor3DTexture:
                gGLFT->glBindTexture(GL_TEXTURE_3D,textureId);
                if( image.is_valid())
                {
                    image.upload_texture3D();
                    imageLoaded = true;
                }
                else {
                    
                        GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0,
                        GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
                                 
                }
                break;
#if defined(WIN32) || defined(LINUX)
                
                
            case cgfxAttrDef::kAttrTypeColor2DRectTexture:
                gGLFT->glBindTexture(GL_TEXTURE_RECTANGLE_NV, textureId);
                if( image.is_valid())
                {
                    
                    image.upload_textureRectangle();
                    imageLoaded = true;
                }
                else
                {
                    
                    gGLFT->glTexImage2D(
                        GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA, 1, 1, 0,
                        GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
                }
                break;
#endif
            default:
                assert(false);
        }
        return imageLoaded;
    }
    
    
    
    
    struct EntryKey 
    {
        EntryKey(
            const std::string&        textureFilePath,
            const std::string&        shaderFxFile,
            const std::string&        attrName,
            cgfxAttrDef::cgfxAttrType attrType
        )
            : fTextureFilePath(textureFilePath),
              fShaderFxFile(shaderFxFile),
              fAttrName(attrName),
              fAttrType(attrType)
        {}
        
        EntryKey(const EntryKey& rhs)
            : fTextureFilePath(rhs.fTextureFilePath),
              fShaderFxFile(rhs.fShaderFxFile),
              fAttrName(rhs. fAttrName),
              fAttrType(rhs.fAttrType)
        {}
        const std::string                 fTextureFilePath;
        const std::string                 fShaderFxFile;
        const std::string                 fAttrName;
        const cgfxAttrDef::cgfxAttrType   fAttrType;
    private:
        
        const EntryKey& operator=(const EntryKey& rhs);
    };
    struct EntryKeyLessThan
    {
        bool operator()(const EntryKey& lhs, const EntryKey& rhs) const
        {
            if (lhs.fTextureFilePath < rhs.fTextureFilePath) {
                return true;
            }
            if (lhs.fTextureFilePath > rhs.fTextureFilePath) {
                return false;
            }
            if (lhs.fShaderFxFile < rhs.fShaderFxFile) {
                return true;
            }
            if (lhs.fShaderFxFile > rhs.fShaderFxFile) {
                return false;
            }
            if (lhs.fAttrName < rhs.fAttrName) {
                return true;
            }
            if (lhs.fAttrName > rhs.fAttrName) {
                return false;
            }
            if (lhs.fAttrType < rhs.fAttrType) {
                return true;
            }
            return false;
        }
    };
}
class cgfxTextureCache::Imp : public cgfxTextureCache 
{
public:
    static Imp* sTheTextureCache;
    Imp();
    ~Imp();
    
    
    
    
    
    virtual cgfxRCPtr<cgfxTextureCacheEntry> getTexture(
        cgfxAttrDef::cgfxAttrType   attrType
    );
    virtual void dump() const
    {
        fprintf(stderr, "*** Dumping texture cache ***\n");
        const Map::const_iterator end = fEntries.end();
        for (Map::const_iterator it = fEntries.begin(); it != end; ++it) {
            fprintf(stderr, "   entry = 0x%p, refCount = %d\n",
                    it->second.operator->(),
                    it->second->getRefCount());
            fprintf(stderr, "   tex file = \"%s\"\n",
                    it->first.fTextureFilePath.c_str());
            fprintf(stderr, "   fx  file = \"%s\"\n",
                    it->first.fShaderFxFile.c_str());
            fprintf(stderr, "   attrName = %s, attrType = %s\n\n",
                    it->first.fAttrName.c_str(),
                    cgfxAttrDef::typeName(it->first.fAttrType));
        }
    }
    
    static void flushEntry(const EntryKey& key)
    {
        sTheTextureCache->fEntries.erase(key);
    }
    
private:
    typedef std::map<EntryKey, cgfxRCPtr<cgfxTextureCacheEntry>, EntryKeyLessThan> Map;
    Map fEntries;
};
cgfxTextureCache::Imp* cgfxTextureCache::Imp::sTheTextureCache = 0;
cgfxTextureCache::Imp::Imp()
{}
cgfxTextureCache::Imp::~Imp()
{}
cgfxRCPtr<cgfxTextureCacheEntry> cgfxTextureCache::Imp::getTexture(
    cgfxAttrDef::cgfxAttrType   attrType
)
{
        computeTextureFilePath(texFileName, shaderFxFile).
asChar();
    
    
    
    EntryKey key(textureFilePath.
asChar(), shaderFxFile.
asChar(), attrName.
asChar(), attrType);
    
    const Map::const_iterator entryIt = fEntries.find(key);
    if (entryIt != fEntries.end()) {
        return entryIt->second;
    }
    GLuint textureId;
    bool valid = allocateAndReadTexture(
        textureFilePath, textureNode, attrType, textureId);
    
    cgfxRCPtr<cgfxTextureCacheEntry> entry(
        new cgfxTextureCacheEntry(
            key.fTextureFilePath, key.fShaderFxFile, key.fAttrName, key.fAttrType,
            textureId, valid));
    fEntries.insert(std::make_pair(key,entry));
    return entry;
}
cgfxTextureCacheEntry::~cgfxTextureCacheEntry()
{
    glDeleteTextures(1, &fTextureId);
    fTextureId = 0;
}
void cgfxTextureCacheEntry::markAsStaled()
{
    fStaled = true;
    
    
    addRef();
    
    
    
    
    
    cgfxTextureCache::Imp::flushEntry(
        EntryKey(fTextureFilePath, fShaderFxFile, fAttrName, fAttrType));
    release();
}
    
void cgfxTextureCacheEntry::addRef()
{
    ++fRefCount;
};
void cgfxTextureCacheEntry::release()
{
    -- fRefCount;
    
    if (fRefCount == 1) {
        
        
        
        
        
        
        cgfxTextureCache::Imp::flushEntry(
            EntryKey(fTextureFilePath, fShaderFxFile, fAttrName, fAttrType));
    }
    else if (fRefCount == 0) {
        delete this;
    }
}
void cgfxTextureCache::initialize()
{
    Imp::sTheTextureCache = new cgfxTextureCache::Imp;
}
void cgfxTextureCache::uninitialize()
{
    delete Imp::sTheTextureCache;
    Imp::sTheTextureCache = 0;
}
cgfxTextureCache& cgfxTextureCache::instance()
{
    return *Imp::sTheTextureCache;
}
cgfxTextureCache::cgfxTextureCache()
{}
cgfxTextureCache::~cgfxTextureCache()
{}