/*
 * NodeImageTexture.cpp
 *
 * Copyright (C) 1999 Stephen F. White
 * 
 * Modified by Aaron Cram - Now uses DevIL to load textures
 *
 * 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 2 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 (see the file "COPYING" for details); if 
 * not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
 * Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include "stdafx.h"

#include "swt.h"
#include "DuneApp.h"
#include "resource.h"

#include "NodeImageTexture.h"
#include "Scene.h"
#include "Proto.h"
#include "FieldValue.h"
#include "MFString.h"
#include "SFBool.h"
#include "SFInt32.h"
#include "Field.h"
#include "URL.h"
#ifdef HAVE_LIBDEVIL
# include <IL/ilu.h>
#else
# include "Image.h"
#endif
#include "Util.h"
#include "Texture.h"

ProtoImageTexture::ProtoImageTexture(Scene *scene)
  : Proto(scene, "ImageTexture")
{
    url.set(
          addExposedField(MFSTRING, "url", new MFString(), FF_URL | 
                          FF_WONDERLAND_ART, NULL));
    repeatS.set(
          addField(SFBOOL, "repeatS", new SFBool(true)));
    repeatT.set(
          addField(SFBOOL, "repeatT", new SFBool(true)));
    if (TheApp->getCoverMode()) {
        // non standard Covise/COVER extensions
        environment.set(
          addField(SFBOOL, "environment", new SFBool(false)));
        setFieldFlags(environment, FF_COVER_ONLY);
        blendMode.set(
          addField(SFINT32, "blendMode", new SFInt32(0), 
                   new SFInt32(0), new SFInt32(5)));
        setFieldFlags(blendMode, FF_COVER_ONLY);
        filterMode.set(
          addField(SFINT32, "filterMode", new SFInt32(0), 
                   new SFInt32(0), new SFInt32(5)));
        setFieldFlags(filterMode, FF_COVER_ONLY);
        anisotropy.set(
          addField(SFINT32, "anisotropy", new SFInt32(1), 
                   new SFInt32(0), new SFInt32(2)));
        setFieldFlags(anisotropy, FF_COVER_ONLY);
    }
#ifdef HAVE_TEXTUREIMAGE_MODE
    mode.set(
          addField(SFINT32, "mode", new SFInt32(0), 
                   new SFInt32(0), new SFInt32(3)));
#endif
    alphaChannel.set(
          addField(SFSTRING, "alphaChannel", new SFString("AUTO")));
    setFieldFlags(alphaChannel, FF_KAMBI_ONLY);

    textureProperties.set(
        addField(SFNODE, "textureProperties", new SFNode(), 
                 X3D_TEXTURE_PROPERTIES));
    setFieldFlags(textureProperties, FF_X3D_ONLY);
}

Node *
ProtoImageTexture::create(Scene *scene)
{ 
    return new NodeImageTexture(scene, this); 
}

NodeImageTexture::NodeImageTexture(Scene *scene, Proto *def)
  : Node(scene, def)
{
    _image = NULL;
    _textureWidth = 0;
    _textureHeight = 0;
    _imageStatus = IMG_STATUS_UNLOADED;
    _textureName = 0;
    _components = 0;
    _glColorMode = GL_RGB;          // default: no transparency

    _scaleRequired = false;
    _isTransparent = false;

#ifdef HAVE_LIBDEVIL
    static bool il_needs_init = true;
    if (il_needs_init) {
        ilInit();
        iluInit();
        il_needs_init = false;
        ilEnable(IL_ORIGIN_SET);
        ilOriginFunc(IL_ORIGIN_LOWER_LEFT);
    }

    ilGenImages(1, &_imageName);
    ilBindImage(_imageName);
#endif
}

NodeImageTexture::NodeImageTexture(const NodeImageTexture &node)
  : Node(node)
{
    _image = NULL;
    _textureName = 0;  // must load its own texture though
    _textureWidth = node._textureWidth;
    _textureHeight = node._textureHeight;
    _components=node._components;
    _glColorMode = node._glColorMode ;
    _imageStatus = node._imageStatus;
    _scaleRequired = node._scaleRequired;

#ifdef HAVE_LIBDEVIL
    ilGenImages(1, &_imageName);
    ilBindImage(_imageName);

    // copy image data, if needed
    if (node._imageStatus == IMG_STATUS_LOADED) {
        if (ilCopyImage(node._imageName)) {
            _image = ilGetData();
            if (_image) {
                _imageStatus = IMG_STATUS_LOADED;
            } else {
                _imageStatus = IMG_STATUS_ERROR;
                reportLoadError(NULL, NULL);
            }
        } else {
            _imageStatus = IMG_STATUS_ERROR;
            reportLoadError(NULL, NULL);
        }
    }
#else
    int size = _textureWidth * _textureHeight * _components;
    // copy image data, if any
    if (node._image) {
        _image = new unsigned char[size];
        memcpy(_image, node._image, size);
    }
#endif
}

NodeImageTexture::~NodeImageTexture()
{
    if (_textureName != 0) glDeleteTextures(1, &_textureName);
#ifdef HAVE_LIBDEVIL
    ilDeleteImages(1, &_imageName);
#else
    delete [] _image;
    _image = NULL;
#endif
}

void
NodeImageTexture::update()
{
    if (_textureName != 0) glDeleteTextures(1, &_textureName);
#ifdef HAVE_LIBDEVIL
    ilDeleteImages(1, &_imageName);
#else
    delete [] _image;
    _image = NULL;
#endif
    _imageStatus = IMG_STATUS_UNLOADED;
}

void
NodeImageTexture::setField(int field, FieldValue *value)
{
    if (field == url_Field()) {
        _imageStatus = IMG_STATUS_UNLOADED;
    }
    Node::setField(field, value);
}

void
NodeImageTexture::load()
{
    MFString *urls = url();
    int width, height;
    unsigned char *data = NULL;
    bool errorflag=true;
    char *lastCheckedPath = NULL;

    if (urls->getSize() == 0)
        return;

    for (int i = 0; i < urls->getSize(); i++) {
        MyString path;
        URL url(_scene->getURL(), urls->getValue(i));
        if (urls->getValue(i).length() == 0) continue;
        _scene->Download(url, &path);
#ifdef HAVE_LIBDEVIL
        errorflag=true;
        if (ilLoadImage((char *)(const char *)path)) {
            width = ilGetInteger(IL_IMAGE_WIDTH);
            height = ilGetInteger(IL_IMAGE_HEIGHT);
            _components = ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL);
            if (_components == 1) {
                _glColorMode=GL_LUMINANCE;
                ilConvertImage(IL_LUMINANCE, IL_UNSIGNED_BYTE);
            } else if (_components == 2) {
                _glColorMode=GL_LUMINANCE_ALPHA;
                ilConvertImage(IL_LUMINANCE_ALPHA, IL_UNSIGNED_BYTE);
            } else if (_components == 3) {
                _glColorMode=GL_RGB;
                ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);
            } else if (_components == 4) {
                _glColorMode=GL_RGBA;
                ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);
            } else {
                reportLoadError((char *)(const char *)path, 
                                "Invalid image type: wrong number of components");
                _imageStatus = IMG_STATUS_ERROR;
                return;
            }
            errorflag=false;
            break;
        } else
            lastCheckedPath = mystrdup(path);

#else
        Image image;
        if (image.Open(path)) {
            width = image.GetWidth();
            height = image.GetHeight();
            _components = image.GetComponents();
            if (_components==4)
               _glColorMode=GL_RGBA;
            else if (_components==3)
               _glColorMode = GL_RGB;
            else if (_components==2)
               _glColorMode = GL_LUMINANCE_ALPHA;
            else
               _glColorMode = GL_LUMINANCE;
            data = new unsigned char [width * height * _components];
            image.Read(data);
            break;
        }
#endif        
    }

#ifdef HAVE_LIBDEVIL
    if (errorflag) {
        _imageStatus = IMG_STATUS_ERROR;
        reportLoadError(lastCheckedPath, NULL);
        return;
    }
#else    
    if (data == NULL) { 
        _imageStatus = IMG_STATUS_ERROR;
        return;
    }
#endif        
        
    _textureWidth = 1;
    _textureHeight = 1;

    while (_textureWidth < width)
       _textureWidth <<= 1;

    while (_textureHeight < height)
       _textureHeight <<= 1;

#ifdef HAVE_LIBDEVIL
    if (width == _textureWidth && height == _textureHeight) {
        _scaleRequired = false;
    } else {
        _scaleRequired = true;
        iluImageParameter(ILU_FILTER, ILU_BILINEAR);
        if (!iluScale(_textureWidth, _textureHeight, _components)) {
            _imageStatus = IMG_STATUS_ERROR;
            reportLoadError(NULL, NULL);
            return;
        }
    }

    _image = ilGetData();
    if (_image) {
        _imageStatus = IMG_STATUS_LOADED;
    } else {
        _imageStatus = IMG_STATUS_ERROR;
        reportLoadError(NULL, NULL);
        return;
    }
#else
    delete [] _image;

    if (width == _textureWidth && height == _textureHeight) {
        _scaleRequired = false;
        _image = data;
    } else {
        _scaleRequired = true;
        _image = new unsigned char[_textureWidth * _textureHeight * 
                                   _components];
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glPixelStorei(GL_PACK_ALIGNMENT, 1);
        gluScaleImage(_glColorMode, width, height, GL_UNSIGNED_BYTE, data,
                      _textureWidth, _textureHeight, GL_UNSIGNED_BYTE, _image);
        delete [] data;
    }
    _imageStatus = IMG_STATUS_LOADED;
#endif
    if ((_components == 2) || (_components == 4)) {
        int size = _textureWidth * _textureHeight * _components;
        for (int i = 0; i < size; i += _components) {
            if (_image[i + _components - 1] < 0xff) {
                _isTransparent = true;
                break;
            }
        }
    }
    if (TheApp->isAnaglyphStereo()) {
        // change colors into grayscale values
        if ((_components == 3) || (_components == 4)) {
            int size = _textureWidth * _textureHeight * _components;
            for (int i = 0; i < size; i += _components) {
                int gray = (_image[i + 0] + _image[i + 1] + _image[i + 2]) / 3;
                _image[i + 0] = gray;
                _image[i + 1] = gray;
                _image[i + 2] = gray;
            }
        }
    }
}

#ifdef HAVE_LIBDEVIL
// If a NULL pointer is passed in, this function will report any errors
// that have happened inside the DevIL image library.  Otherwise, it will
// report whatever string is passed in.
void
NodeImageTexture::reportLoadError(char* filename, const char *error_msg)
{
    // If the user hits the cancel button, all subsequent errors will be ignored.
    // This way, if the VRML file contains 100 broken images, the user won't have
    // to hit the OK button 100 times.
    // FIXME:  This won't be reset until the program is restarted...
    static int mbReturn = 0;
    if (mbReturn == IDCANCEL) return;

    char errorstring[256];
    swLoadString(IDS_IMAGE_TEXTURE_ERROR + swGetLang(), errorstring, 255);
    char buf[4096];
    if (error_msg) {                
         if (filename)
             mysnprintf(buf, 4095, "%s: %s\n", filename, error_msg);
         else
             mysnprintf(buf, 4095, "%s\n", error_msg);
    } else {
        ILenum Error;
        while ((Error = ilGetError()) != IL_NO_ERROR) {
            if (filename)
                 mysnprintf(buf, 4095, "%s: ilError #%d: %s\n", filename, Error, iluErrorString(Error));
            else
                 mysnprintf(buf, 4095, "ilError #%d: %s\n", Error, iluErrorString(Error));
            mbReturn = swMessageBox(TheApp->mainWnd(), buf, 
                                    errorstring, SW_MB_OKCANCEL, SW_MB_ERROR);
        }
    }
}
#endif

void
NodeImageTexture::bind()
{
    if (_imageStatus == IMG_STATUS_UNLOADED) {
        load();
        if (_textureName != 0) glDeleteTextures(1, &_textureName);
        _textureName = 0;
    }
    if (_imageStatus == IMG_STATUS_LOADED) {
        if (_textureName == 0) {
            glGenTextures(1, &_textureName);
            glBindTexture(GL_TEXTURE_2D, _textureName);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            glTexImage2D(GL_TEXTURE_2D, 0, _components, 
                         _textureWidth, _textureHeight, 0,
                         _glColorMode, GL_UNSIGNED_BYTE, _image);
            GLenum error=glGetError();
            if (error!=0)
               {
               /* what's wrong here with "invalid value" ? */
               fprintf(stderr,"GL Error: %s\n",gluErrorString(error));
               }

        } else {
            glBindTexture(GL_TEXTURE_2D, _textureName);
        }
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 
                        repeatS()->getValue() ? GL_REPEAT : GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 
                        repeatT()->getValue() ? GL_REPEAT : GL_CLAMP);
        glEnable(GL_TEXTURE_2D);

        if (TheApp->getCoverMode()) {
            // non standard Covise/COVER extensions
            if (environment()->getValue()) {
                glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
                glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
//                glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
//                glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
                glEnable(GL_TEXTURE_GEN_S);
                glEnable(GL_TEXTURE_GEN_T);
            }
        }

#ifdef HAVE_TEXTUREIMAGE_MODE
        switch (mode()->getValue()) {
          case 1:
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
            break;
          case 2:
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
            break;
          case 3:
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
            glEnable(GL_TEXTURE_GEN_S);
            glEnable(GL_TEXTURE_GEN_T);
            break;
        }
#endif
    }
}

void
NodeImageTexture::unbind()
{
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
}

int
NodeImageTexture::isLoaded()
{
    return _imageStatus == IMG_STATUS_LOADED;
}
