// ReaderWriter for pvr images

#if defined(_MSC_VER)
#include <windows.h>
#endif

#include <osg/Image>
#include <osg/Notify>

#include <osg/Geode>

#include <osg/GL>

#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(_MSC_VER)
typedef UINT64     uint64_t;
typedef INT64      int64_t;
typedef UINT32     uint32_t;
typedef INT32      int32_t;
typedef UINT16     uint16_t;
typedef UINT8      uint8_t;
#else
#if defined __sun || defined __hpux
#include <inttypes.h>
#else
#include <stdint.h>
#endif
#endif

using namespace osg;

#define PVR_TEXTURE_FLAG_TYPE_MASK    0xff

static char gPVRTexIdentifier[5] = "PVR!";

enum
{
  kPVRTextureFlagTypePVRTC_2 = 12,
  kPVRTextureFlagTypePVRTC_4,
  kPVRTextureFlagTypeOGLPVRTC_2 = 24,
  kPVRTextureFlagTypeOGLPVRTC_4,
  kPVRTextureFlagTypeETC = 54
};

typedef struct _PVRTexHeader
{
  uint32_t headerLength;
  uint32_t height;
  uint32_t width;
  uint32_t numMipmaps;
  uint32_t flags;
  uint32_t dataLength;
  uint32_t bpp;
  uint32_t bitmaskRed;
  uint32_t bitmaskGreen;
  uint32_t bitmaskBlue;
  uint32_t bitmaskAlpha;
  uint32_t pvrTag;
  uint32_t numSurfs;

  typedef unsigned char * BytePtr;

  bool needsBytesSwapped()
  {
    union {
      int testWord;
      char testByte[sizeof(int)];
    }endianTest;
    endianTest.testWord = 1;
    if( endianTest.testByte[0] == 1 )
      return false;
    else
      return true;
  }

  template <class T>
  inline void swapBytes(  T &s )
  {
    if( sizeof( T ) == 1 )
      return;

    T d = s;
    BytePtr sptr = (BytePtr)&s;
    BytePtr dptr = &(((BytePtr)&d)[sizeof(T)-1]);

    for( unsigned int i = 0; i < sizeof(T); i++ )
      *(sptr++) = *(dptr--);
  }

  void swapBytes()
  {
    swapBytes(headerLength);
    swapBytes(height);
    swapBytes(width);
    swapBytes(numMipmaps);
    swapBytes(flags);
    swapBytes(dataLength);
    swapBytes(bpp);
    swapBytes(bitmaskRed);
    swapBytes(bitmaskGreen);
    swapBytes(bitmaskBlue);
    swapBytes(bitmaskAlpha);
    swapBytes(pvrTag);
    swapBytes(numSurfs);
  }

} PVRTexHeader;


class ReaderWriterPVR : public osgDB::ReaderWriter
{
public:

    ReaderWriterPVR()
    {
        supportsExtension("pvr","PVR image format");
    }

    virtual const char* className() const { return "PVR Image Reader/Writer"; }


    ReadResult readPVRStream(std::istream& fin) const
    {
        PVRTexHeader header;

        fin.read((char*)&header, sizeof(PVRTexHeader));
        if(!fin.good()){
            osg::notify(osg::WARN) << "Failed to read pvr header." << std::endl;
            return ReadResult::ERROR_IN_READING_FILE;
        }

        if(header.needsBytesSwapped())
            header.swapBytes();

        if(gPVRTexIdentifier[0] != static_cast<char>((header.pvrTag >>  0) & 0xff) ||
           gPVRTexIdentifier[1] != static_cast<char>((header.pvrTag >>  8) & 0xff) ||
           gPVRTexIdentifier[2] != static_cast<char>((header.pvrTag >> 16) & 0xff) ||
           gPVRTexIdentifier[3] != static_cast<char>((header.pvrTag >> 24) & 0xff))
            {
                osg::notify(osg::WARN) << "Failed to verify pvr header: " << ((header.pvrTag >>  0) & 0xff) << ", " << ((header.pvrTag >>  8) & 0xff) << ", " << ((header.pvrTag >>  16) & 0xff) << ", " << ((header.pvrTag >>  24) & 0xff) << std::endl;
                return ReadResult::FILE_NOT_HANDLED;
            }


        uint32_t formatFlags = header.flags & PVR_TEXTURE_FLAG_TYPE_MASK;
        GLenum internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
        uint32_t width, height;

        if(formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypePVRTC_2 ||
           formatFlags == kPVRTextureFlagTypeOGLPVRTC_4 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_2 ||
           formatFlags == kPVRTextureFlagTypeETC){
            if(formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_4)
                internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
            else if(formatFlags == kPVRTextureFlagTypePVRTC_2 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_2)
                internalFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
            else if(formatFlags == kPVRTextureFlagTypeETC)
                internalFormat = GL_ETC1_RGB8_OES;

            width = header.width;
            height = header.height;

            osg::ref_ptr<osg::Image> image = new osg::Image;
            if (!image) return ReadResult::INSUFFICIENT_MEMORY_TO_LOAD;

            unsigned char *imageData = new unsigned char[header.dataLength];
            if (!imageData) return ReadResult::INSUFFICIENT_MEMORY_TO_LOAD;

            fin.read((char*)imageData, header.dataLength);
            if(!fin.good())
            {
                delete [] imageData;
                return ReadResult::ERROR_IN_READING_FILE;
            }

            image->setImage(header.width, header.height, 1,
                            internalFormat,    internalFormat,
                            GL_UNSIGNED_BYTE,
                            imageData,
                            osg::Image::USE_NEW_DELETE);

            uint32_t dataOffset = 0;
            uint32_t blockSize = 0, widthBlocks = 0, heightBlocks = 0;
            uint32_t bpp = 4;

            osg::Image::MipmapDataType mipmapdata;

            // Calculate the data size for each texture level and respect the minimum number of blocks
            while(dataOffset < header.dataLength){
                if(formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypeOGLPVRTC_4){
                    blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
                    widthBlocks = width / 4;
                    heightBlocks = height / 4;
                    bpp = 4;
                }else if(formatFlags == kPVRTextureFlagTypeETC){
                    blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
                    widthBlocks = width / 4;
                    heightBlocks = height / 4;
                    bpp = 4;
                }else{
                    blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
                    widthBlocks = width / 8;
                    heightBlocks = height / 4;
                    bpp = 2;
                }

                // Clamp to minimum number of blocks
                if(widthBlocks < 2)
                    widthBlocks = 2;
                if(heightBlocks < 2)
                    heightBlocks = 2;

                if(dataOffset > 0)
                    mipmapdata.push_back(dataOffset);

                dataOffset += widthBlocks * heightBlocks * ((blockSize  * bpp) / 8);

                width = osg::maximum(width >> 1, (uint32_t)1);
                height = osg::maximum(height >> 1, (uint32_t)1);
            }

            if(!mipmapdata.empty())
                image->setMipmapLevels(mipmapdata);

            return image.get();
        }

        osg::notify(osg::WARN) << "Failed to read pvr data." << std::endl;
        return ReadResult::FILE_NOT_HANDLED;
    }

    virtual ReadResult readObject(std::istream& fin,const osgDB::ReaderWriter::Options* options =NULL) const
    {
        return readImage(fin, options);
    }

    virtual ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options =NULL) const
    {
        return readImage(file, options);
    }

    virtual ReadResult readImage(std::istream& fin,const osgDB::ReaderWriter::Options* =NULL) const
    {
        return readPVRStream(fin);
    }

    virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const
    {
        std::string ext = osgDB::getLowerCaseFileExtension(file);
        if(!acceptsExtension(ext))
            return ReadResult::FILE_NOT_HANDLED;

        std::string fileName = osgDB::findDataFile(file, options);
        if(fileName.empty())
            return ReadResult::FILE_NOT_FOUND;

        std::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary);
        if(!istream) return ReadResult::FILE_NOT_HANDLED;
        ReadResult rr = readPVRStream(istream);
        if(rr.validImage()) rr.getImage()->setFileName(file);
        return rr;
    }

};

// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(pvr, ReaderWriterPVR)
