/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------

Copyright (c) 2006-2022, assimp team



All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.

* Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other
  materials provided with the distribution.

* Neither the name of the assimp team, nor the names of its
  contributors may be used to endorse or promote products
  derived from this software without specific prior
  written permission of the assimp team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/

/** @file Implementation of the material oart of the LWO importer class */

#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER

// internal headers
#include "LWOLoader.h"
#include <assimp/ByteSwapper.h>

using namespace Assimp;

// ------------------------------------------------------------------------------------------------
template <class T>
T lerp(const T &one, const T &two, float val) {
    return one + (two - one) * val;
}

// ------------------------------------------------------------------------------------------------
// Convert a lightwave mapping mode to our's
inline aiTextureMapMode GetMapMode(LWO::Texture::Wrap in) {
    switch (in) {
        case LWO::Texture::REPEAT:
            return aiTextureMapMode_Wrap;

        case LWO::Texture::MIRROR:
            return aiTextureMapMode_Mirror;

        case LWO::Texture::RESET:
            ASSIMP_LOG_WARN("LWO2: Unsupported texture map mode: RESET");

            // fall though here
        case LWO::Texture::EDGE:
            return aiTextureMapMode_Clamp;
    }
    return (aiTextureMapMode)0;
}

// ------------------------------------------------------------------------------------------------
bool LWOImporter::HandleTextures(aiMaterial *pcMat, const TextureList &in, aiTextureType type) {
    ai_assert(nullptr != pcMat);

    unsigned int cur = 0, temp = 0;
    aiString s;
    bool ret = false;

    for (const auto &texture : in) {
        if (!texture.enabled || !texture.bCanUse)
            continue;
        ret = true;

        // Convert lightwave's mapping modes to ours. We let them
        // as they are, the GenUVcoords step will compute UV
        // channels if they're not there.

        aiTextureMapping mapping = aiTextureMapping_OTHER;
        switch (texture.mapMode) {
            case LWO::Texture::Planar:
                mapping = aiTextureMapping_PLANE;
                break;
            case LWO::Texture::Cylindrical:
                mapping = aiTextureMapping_CYLINDER;
                break;
            case LWO::Texture::Spherical:
                mapping = aiTextureMapping_SPHERE;
                break;
            case LWO::Texture::Cubic:
                mapping = aiTextureMapping_BOX;
                break;
            case LWO::Texture::FrontProjection:
                ASSIMP_LOG_ERROR("LWO2: Unsupported texture mapping: FrontProjection");
                mapping = aiTextureMapping_OTHER;
                break;
            case LWO::Texture::UV: {
                if (UINT_MAX == texture.mRealUVIndex) {
                    // We have no UV index for this texture, so we can't display it
                    continue;
                }

                // add the UV source index
                temp = texture.mRealUVIndex;
                pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_UVWSRC(type, cur));

                mapping = aiTextureMapping_UV;
            } break;
            default:
                ai_assert(false);
        };

        if (mapping != aiTextureMapping_UV) {
            // Setup the main axis
            aiVector3D v;
            switch (texture.majorAxis) {
                case Texture::AXIS_X:
                    v = aiVector3D(1.0, 0.0, 0.0);
                    break;
                case Texture::AXIS_Y:
                    v = aiVector3D(0.0, 1.0, 0.0);
                    break;
                default: // case Texture::AXIS_Z:
                    v = aiVector3D(0.0, 0.0, 1.0);
                    break;
            }

            pcMat->AddProperty(&v, 1, AI_MATKEY_TEXMAP_AXIS(type, cur));

            // Setup UV scalings for cylindric and spherical projections
            if (mapping == aiTextureMapping_CYLINDER || mapping == aiTextureMapping_SPHERE) {
                aiUVTransform trafo;
                trafo.mScaling.x = texture.wrapAmountW;
                trafo.mScaling.y = texture.wrapAmountH;

                static_assert(sizeof(aiUVTransform) / sizeof(ai_real) == 5, "sizeof(aiUVTransform)/sizeof(ai_real) == 5");
                pcMat->AddProperty(&trafo, 1, AI_MATKEY_UVTRANSFORM(type, cur));
            }
            ASSIMP_LOG_VERBOSE_DEBUG("LWO2: Setting up non-UV mapping");
        }

        // The older LWOB format does not use indirect references to clips.
        // The file name of a texture is directly specified in the tex chunk.
        if (mIsLWO2 || mIsLWO3) {
            // find the corresponding clip (take the last one if multiple
            // share the same index)
            ClipList::iterator end = mClips.end(), candidate = end;
            temp = texture.mClipIdx;
            for (ClipList::iterator clip = mClips.begin(); clip != end; ++clip) {
                if ((*clip).idx == temp) {
                    candidate = clip;
                }
            }
            if (candidate == end) {
                ASSIMP_LOG_ERROR("LWO2: Clip index is out of bounds");
                temp = 0;

                // fixme: apparently some LWO files shipping with Doom3 don't
                // have clips at all ... check whether that's true or whether
                // it's a bug in the loader.

                s.Set("$texture.png");

                //continue;
            } else {
                if (Clip::UNSUPPORTED == (*candidate).type) {
                    ASSIMP_LOG_ERROR("LWO2: Clip type is not supported");
                    continue;
                }
                AdjustTexturePath((*candidate).path);
                s.Set((*candidate).path);

                // Additional image settings
                int flags = 0;
                if ((*candidate).negate) {
                    flags |= aiTextureFlags_Invert;
                }
                pcMat->AddProperty(&flags, 1, AI_MATKEY_TEXFLAGS(type, cur));
            }
        } else {
            std::string ss = texture.mFileName;
            if (!ss.length()) {
                ASSIMP_LOG_WARN("LWOB: Empty file name");
                continue;
            }
            AdjustTexturePath(ss);
            s.Set(ss);
        }
        pcMat->AddProperty(&s, AI_MATKEY_TEXTURE(type, cur));

        // add the blend factor
        pcMat->AddProperty<float>(&texture.mStrength, 1, AI_MATKEY_TEXBLEND(type, cur));

        // add the blend operation
        switch (texture.blendType) {
            case LWO::Texture::Normal:
            case LWO::Texture::Multiply:
                temp = (unsigned int)aiTextureOp_Multiply;
                break;

            case LWO::Texture::Subtractive:
            case LWO::Texture::Difference:
                temp = (unsigned int)aiTextureOp_Subtract;
                break;

            case LWO::Texture::Divide:
                temp = (unsigned int)aiTextureOp_Divide;
                break;

            case LWO::Texture::Additive:
                temp = (unsigned int)aiTextureOp_Add;
                break;

            default:
                temp = (unsigned int)aiTextureOp_Multiply;
                ASSIMP_LOG_WARN("LWO2: Unsupported texture blend mode: alpha or displacement");
        }
        // Setup texture operation
        pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_TEXOP(type, cur));

        // setup the mapping mode
        int mapping_ = static_cast<int>(mapping);
        pcMat->AddProperty<int>(&mapping_, 1, AI_MATKEY_MAPPING(type, cur));

        // add the u-wrapping
        temp = (unsigned int)GetMapMode(texture.wrapModeWidth);
        pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_MAPPINGMODE_U(type, cur));

        // add the v-wrapping
        temp = (unsigned int)GetMapMode(texture.wrapModeHeight);
        pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_MAPPINGMODE_V(type, cur));

        ++cur;
    }
    return ret;
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat) {
    // copy the name of the surface
    aiString st;
    st.Set(surf.mName);
    pcMat->AddProperty(&st, AI_MATKEY_NAME);

    const int i = surf.bDoubleSided ? 1 : 0;
    pcMat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED);

    // add the refraction index and the bump intensity
    pcMat->AddProperty(&surf.mIOR, 1, AI_MATKEY_REFRACTI);
    pcMat->AddProperty(&surf.mBumpIntensity, 1, AI_MATKEY_BUMPSCALING);

    aiShadingMode m;
    if (surf.mSpecularValue && surf.mGlossiness) {
        float fGloss;
        if (mIsLWO2 || mIsLWO3) {
            fGloss = std::pow(surf.mGlossiness * ai_real(10.0) + ai_real(2.0), ai_real(2.0));
        } else {
            if (16.0 >= surf.mGlossiness)
                fGloss = 6.0;
            else if (64.0 >= surf.mGlossiness)
                fGloss = 20.0;
            else if (256.0 >= surf.mGlossiness)
                fGloss = 50.0;
            else
                fGloss = 80.0;
        }

        pcMat->AddProperty(&surf.mSpecularValue, 1, AI_MATKEY_SHININESS_STRENGTH);
        pcMat->AddProperty(&fGloss, 1, AI_MATKEY_SHININESS);
        m = aiShadingMode_Phong;
    } else
        m = aiShadingMode_Gouraud;

    // specular color
    aiColor3D clr = lerp(aiColor3D(1.0, 1.0, 1.0), surf.mColor, surf.mColorHighlights);
    pcMat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
    pcMat->AddProperty(&surf.mSpecularValue, 1, AI_MATKEY_SHININESS_STRENGTH);

    // emissive color
    // luminosity is not really the same but it affects the surface in a similar way. Some scaling looks good.
    clr.g = clr.b = clr.r = surf.mLuminosity * ai_real(0.8);
    pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_EMISSIVE);

    // opacity ... either additive or default-blended, please
    if (0.0 != surf.mAdditiveTransparency) {
        const int add = aiBlendMode_Additive;
        pcMat->AddProperty(&surf.mAdditiveTransparency, 1, AI_MATKEY_OPACITY);
        pcMat->AddProperty(&add, 1, AI_MATKEY_BLEND_FUNC);
    } else if (10e10f != surf.mTransparency) {
        const int def = aiBlendMode_Default;
        const float f = 1.0f - surf.mTransparency;
        pcMat->AddProperty(&f, 1, AI_MATKEY_OPACITY);
        pcMat->AddProperty(&def, 1, AI_MATKEY_BLEND_FUNC);
    }

    // ADD TEXTURES to the material
    // TODO: find out how we can handle COLOR textures correctly...
    bool b = HandleTextures(pcMat, surf.mColorTextures, aiTextureType_DIFFUSE);
    b = (b || HandleTextures(pcMat, surf.mDiffuseTextures, aiTextureType_DIFFUSE));
    HandleTextures(pcMat, surf.mSpecularTextures, aiTextureType_SPECULAR);
    HandleTextures(pcMat, surf.mGlossinessTextures, aiTextureType_SHININESS);
    HandleTextures(pcMat, surf.mBumpTextures, aiTextureType_HEIGHT);
    HandleTextures(pcMat, surf.mOpacityTextures, aiTextureType_OPACITY);
    HandleTextures(pcMat, surf.mReflectionTextures, aiTextureType_REFLECTION);

    // Now we need to know which shader to use .. iterate through the shader list of
    // the surface and  search for a name which we know ...
    for (const auto &shader : surf.mShaders) {
        if (shader.functionName == "LW_SuperCelShader" || shader.functionName == "AH_CelShader") {
            ASSIMP_LOG_INFO("LWO2: Mapping LW_SuperCelShader/AH_CelShader to aiShadingMode_Toon");

            m = aiShadingMode_Toon;
            break;
        } else if (shader.functionName == "LW_RealFresnel" || shader.functionName == "LW_FastFresnel") {
            ASSIMP_LOG_INFO("LWO2: Mapping LW_RealFresnel/LW_FastFresnel to aiShadingMode_Fresnel");

            m = aiShadingMode_Fresnel;
            break;
        } else {
            ASSIMP_LOG_WARN("LWO2: Unknown surface shader: ", shader.functionName);
        }
    }
    if (surf.mMaximumSmoothAngle <= 0.0)
        m = aiShadingMode_Flat;
    int m_ = static_cast<int>(m);
    pcMat->AddProperty(&m_, 1, AI_MATKEY_SHADING_MODEL);

    // (the diffuse value is just a scaling factor)
    // If a diffuse texture is set, we set this value to 1.0
    clr = (b && false ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor);
    clr.r *= surf.mDiffuseValue;
    clr.g *= surf.mDiffuseValue;
    clr.b *= surf.mDiffuseValue;
    pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
}

// ------------------------------------------------------------------------------------------------
char LWOImporter::FindUVChannels(LWO::TextureList &list,
        LWO::Layer & /*layer*/, LWO::UVChannel &uv, unsigned int next) {
    char ret = 0;
    for (auto &texture : list) {

        // Ignore textures with non-UV mappings for the moment.
        if (!texture.enabled || !texture.bCanUse || texture.mapMode != LWO::Texture::UV) {
            continue;
        }

        if (texture.mUVChannelIndex == uv.name) {
            ret = 1;

            // got it.
            if (texture.mRealUVIndex == UINT_MAX || texture.mRealUVIndex == next) {
                texture.mRealUVIndex = next;
            } else {
                // channel mismatch. need to duplicate the material.
                ASSIMP_LOG_WARN("LWO: Channel mismatch, would need to duplicate surface [design bug]");

                // TODO
            }
        }
    }
    return ret;
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::FindUVChannels(LWO::Surface &surf,
        LWO::SortedRep &sorted, LWO::Layer &layer,
        unsigned int out[AI_MAX_NUMBER_OF_TEXTURECOORDS]) {
    unsigned int next = 0, extra = 0, num_extra = 0;

    // Check whether we have an UV entry != 0 for one of the faces in 'sorted'
    for (unsigned int i = 0; i < layer.mUVChannels.size(); ++i) {
        LWO::UVChannel &uv = layer.mUVChannels[i];

        for (LWO::SortedRep::const_iterator it = sorted.begin(); it != sorted.end(); ++it) {

            LWO::Face &face = layer.mFaces[*it];

            for (unsigned int n = 0; n < face.mNumIndices; ++n) {
                unsigned int idx = face.mIndices[n];

                if (uv.abAssigned[idx] && ((aiVector2D *)&uv.rawData[0])[idx] != aiVector2D()) {

                    if (extra >= AI_MAX_NUMBER_OF_TEXTURECOORDS) {

                        ASSIMP_LOG_ERROR("LWO: Maximum number of UV channels for "
                                         "this mesh reached. Skipping channel \'" +
                                         uv.name + "\'");

                    } else {
                        // Search through all textures assigned to 'surf' and look for this UV channel
                        char had = 0;
                        had |= FindUVChannels(surf.mColorTextures, layer, uv, next);
                        had |= FindUVChannels(surf.mDiffuseTextures, layer, uv, next);
                        had |= FindUVChannels(surf.mSpecularTextures, layer, uv, next);
                        had |= FindUVChannels(surf.mGlossinessTextures, layer, uv, next);
                        had |= FindUVChannels(surf.mOpacityTextures, layer, uv, next);
                        had |= FindUVChannels(surf.mBumpTextures, layer, uv, next);
                        had |= FindUVChannels(surf.mReflectionTextures, layer, uv, next);

                        // We have a texture referencing this UV channel so we have to take special care
                        // and are willing to drop unreferenced channels in favour of it.
                        if (had != 0) {
                            if (num_extra) {

                                for (unsigned int a = next; a < std::min(extra, AI_MAX_NUMBER_OF_TEXTURECOORDS - 1u); ++a) {
                                    out[a + 1] = out[a];
                                }
                            }
                            ++extra;
                            out[next++] = i;
                        }
                        // Bah ... seems not to be used at all. Push to end if enough space is available.
                        else {
                            out[extra++] = i;
                            ++num_extra;
                        }
                    }
                    it = sorted.end() - 1;
                    break;
                }
            }
        }
    }
    if (extra < AI_MAX_NUMBER_OF_TEXTURECOORDS) {
        out[extra] = UINT_MAX;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::FindVCChannels(const LWO::Surface &surf, LWO::SortedRep &sorted, const LWO::Layer &layer,
        unsigned int out[AI_MAX_NUMBER_OF_COLOR_SETS]) {
    unsigned int next = 0;

    // Check whether we have an vc entry != 0 for one of the faces in 'sorted'
    for (unsigned int i = 0; i < layer.mVColorChannels.size(); ++i) {
        const LWO::VColorChannel &vc = layer.mVColorChannels[i];

        if (surf.mVCMap == vc.name) {
            // The vertex color map is explicitly requested by the surface so we need to take special care of it
            for (unsigned int a = 0; a < std::min(next, AI_MAX_NUMBER_OF_COLOR_SETS - 1u); ++a) {
                out[a + 1] = out[a];
            }
            out[0] = i;
            ++next;
        } else {

            for (LWO::SortedRep::iterator it = sorted.begin(); it != sorted.end(); ++it) {
                const LWO::Face &face = layer.mFaces[*it];

                for (unsigned int n = 0; n < face.mNumIndices; ++n) {
                    unsigned int idx = face.mIndices[n];

                    if (vc.abAssigned[idx] && ((aiColor4D *)&vc.rawData[0])[idx] != aiColor4D(0.0, 0.0, 0.0, 1.0)) {
                        if (next >= AI_MAX_NUMBER_OF_COLOR_SETS) {

                            ASSIMP_LOG_ERROR("LWO: Maximum number of vertex color channels for "
                                             "this mesh reached. Skipping channel \'" +
                                             vc.name + "\'");

                        } else {
                            out[next++] = i;
                        }
                        it = sorted.end() - 1;
                        break;
                    }
                }
            }
        }
    }
    if (next != AI_MAX_NUMBER_OF_COLOR_SETS) {
        out[next] = UINT_MAX;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2ImageMap(unsigned int size, LWO::Texture &tex) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;
    while (true) {
        if (mFileBuffer + 6 >= end) break;
        LE_NCONST IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer);

        if (mFileBuffer + head.length > end)
            throw DeadlyImportError("LWO2: Invalid SURF.BLOCK chunk length");

        uint8_t *const next = mFileBuffer + head.length;
        switch (head.type) {
            case AI_LWO_PROJ:
                tex.mapMode = (Texture::MappingMode)GetU2();
                break;
            case AI_LWO_WRAP:
                tex.wrapModeWidth = (Texture::Wrap)GetU2();
                tex.wrapModeHeight = (Texture::Wrap)GetU2();
                break;
            case AI_LWO_AXIS:
                tex.majorAxis = (Texture::Axes)GetU2();
                break;
            case AI_LWO_IMAG:
                tex.mClipIdx = GetU2();
                break;
            case AI_LWO_VMAP:
                GetS0(tex.mUVChannelIndex, head.length);
                break;
            case AI_LWO_WRPH:
                tex.wrapAmountH = GetF4();
                break;
            case AI_LWO_WRPW:
                tex.wrapAmountW = GetF4();
                break;
        }
        mFileBuffer = next;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2Procedural(unsigned int /*size*/, LWO::Texture &tex) {
    // --- not supported at the moment
    ASSIMP_LOG_ERROR("LWO2: Found procedural texture, this is not supported");
    tex.bCanUse = false;
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2Gradient(unsigned int /*size*/, LWO::Texture &tex) {
    // --- not supported at the moment
    ASSIMP_LOG_ERROR("LWO2: Found gradient texture, this is not supported");
    tex.bCanUse = false;
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2TextureHeader(unsigned int size, LWO::Texture &tex) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    // get the ordinal string
    GetS0(tex.ordinal, size);

    // we could crash later if this is an empty string ...
    if (!tex.ordinal.length()) {
        ASSIMP_LOG_ERROR("LWO2: Ill-formed SURF.BLOK ordinal string");
        tex.ordinal = "\x00";
    }
    while (true) {
        if (mFileBuffer + 6 >= end) break;
        const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer);

        if (mFileBuffer + head.length > end)
            throw DeadlyImportError("LWO2: Invalid texture header chunk length");

        uint8_t *const next = mFileBuffer + head.length;
        switch (head.type) {
            case AI_LWO_CHAN:
                tex.type = GetU4();
                break;
            case AI_LWO_ENAB:
                tex.enabled = GetU2() ? true : false;
                break;
            case AI_LWO_OPAC:
                tex.blendType = (Texture::BlendType)GetU2();
                tex.mStrength = GetF4();
                break;
        }
        mFileBuffer = next;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2TextureBlock(LE_NCONST IFF::SubChunkHeader *head, unsigned int size) {
    ai_assert(!mSurfaces->empty());
    LWO::Surface &surf = mSurfaces->back();
    LWO::Texture tex;

    // load the texture header
    LoadLWO2TextureHeader(head->length, tex);
    size -= head->length + 6;

    // now get the exact type of the texture
    switch (head->type) {
        case AI_LWO_PROC:
            LoadLWO2Procedural(size, tex);
            break;
        case AI_LWO_GRAD:
            LoadLWO2Gradient(size, tex);
            break;
        case AI_LWO_IMAP:
            LoadLWO2ImageMap(size, tex);
    }

    // get the destination channel
    TextureList *listRef = nullptr;
    switch (tex.type) {
        case AI_LWO_COLR:
            listRef = &surf.mColorTextures;
            break;
        case AI_LWO_DIFF:
            listRef = &surf.mDiffuseTextures;
            break;
        case AI_LWO_SPEC:
            listRef = &surf.mSpecularTextures;
            break;
        case AI_LWO_GLOS:
            listRef = &surf.mGlossinessTextures;
            break;
        case AI_LWO_BUMP:
            listRef = &surf.mBumpTextures;
            break;
        case AI_LWO_TRAN:
            listRef = &surf.mOpacityTextures;
            break;
        case AI_LWO_REFL:
            listRef = &surf.mReflectionTextures;
            break;
        default:
            ASSIMP_LOG_WARN("LWO2: Encountered unknown texture type");
            return;
    }

    // now attach the texture to the parent surface - sort by ordinal string
    for (TextureList::iterator it = listRef->begin(); it != listRef->end(); ++it) {
        if (::strcmp(tex.ordinal.c_str(), (*it).ordinal.c_str()) < 0) {
            listRef->insert(it, tex);
            return;
        }
    }
    listRef->push_back(tex);
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader * /*head*/, unsigned int size) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    ai_assert(!mSurfaces->empty());
    LWO::Surface &surf = mSurfaces->back();
    LWO::Shader shader;

    // get the ordinal string
    GetS0(shader.ordinal, size);

    // we could crash later if this is an empty string ...
    if (!shader.ordinal.length()) {
        ASSIMP_LOG_ERROR("LWO2: Ill-formed SURF.BLOK ordinal string");
        shader.ordinal = "\x00";
    }

    // read the header
    while (true) {
        if (mFileBuffer + 6 >= end) break;
        const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer);

        if (mFileBuffer + head.length > end)
            throw DeadlyImportError("LWO2: Invalid shader header chunk length");

        uint8_t *const next = mFileBuffer + head.length;
        switch (head.type) {
            case AI_LWO_ENAB:
                shader.enabled = GetU2() ? true : false;
                break;

            case AI_LWO_FUNC:
                GetS0(shader.functionName, head.length);
        }
        mFileBuffer = next;
    }

    // now attach the shader to the parent surface - sort by ordinal string
    for (ShaderList::iterator it = surf.mShaders.begin(); it != surf.mShaders.end(); ++it) {
        if (::strcmp(shader.ordinal.c_str(), (*it).ordinal.c_str()) < 0) {
            surf.mShaders.insert(it, shader);
            return;
        }
    }
    surf.mShaders.push_back(shader);
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadNodalBlocks(unsigned int size) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    while (true) {
        if (mFileBuffer + 8 >= end)
            break;

        IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer);
        int bufOffset = 0;
        if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form
            mFileBuffer -= 8;
            head = IFF::LoadForm(mFileBuffer);
            bufOffset = 4;
        }

        if (mFileBuffer + head.length > end) {
            throw DeadlyImportError("LWO3: cannot read length; LoadNodalBlocks");
        }
        uint8_t *const next = mFileBuffer + head.length;
        mFileBuffer += bufOffset;
        switch (head.type) {
        case AI_LWO_NNDS:
            LoadNodes(head.length);
            break;
        }

        mFileBuffer = next;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadNodes(unsigned int size) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    while (true) {
        if (mFileBuffer + 8 >= end)
            break;

        IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer);
        int bufOffset = 0;
        if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form
            mFileBuffer -= 8;
            head = IFF::LoadForm(mFileBuffer);
            bufOffset = 4;
        }

        if (mFileBuffer + head.length > end) {
            throw DeadlyImportError("LWO3: cannot read length; LoadNodes");
        }

        uint8_t *const next = mFileBuffer + head.length;
        mFileBuffer += bufOffset;
        switch (head.type) {
        case AI_LWO_NTAG:
            LoadNodeTag(head.length);
            break;
        }

        mFileBuffer = next;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadNodeTag(unsigned int size) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    while (true) {
        if (mFileBuffer + 8 >= end)
            break;

        IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer);
        int bufOffset = 0;
        if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form
            mFileBuffer -= 8;
            head = IFF::LoadForm(mFileBuffer);
            bufOffset = 4;
        }

        if (mFileBuffer + head.length > end) {
            throw DeadlyImportError("LWO3: cannot read length; LoadNodeTag");
        }

        uint8_t *const next = mFileBuffer + head.length;
        mFileBuffer += bufOffset;

        switch (head.type) {
        case AI_LWO_NDTA:
            LoadNodeData(head.length);
            break;
        }

        mFileBuffer = next;
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadNodeData(unsigned int size) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    LWO::Surface &surf = mSurfaces->back();

    while (true) {
        if (mFileBuffer + 8 >= end)
            break;

        IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer);
        int bufOffset = 0;
        if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form
            mFileBuffer -= 8;
            head = IFF::LoadForm(mFileBuffer);
            bufOffset = 4;
        }

        if (mFileBuffer + head.length > end) {
            throw DeadlyImportError("LWO3: INVALID LENGTH; LoadNodeData");
        }

        uint8_t *const next = mFileBuffer + head.length;
        mFileBuffer += bufOffset;
        switch (head.type) {
            case AI_LWO_VERS:
            case AI_LWO_ENUM:
            case AI_LWO_IBGC:
            case AI_LWO_IOPC:
            case AI_LWO_IIMG:
            case AI_LWO_TXTR:
            case AI_LWO_IFAL:
            case AI_LWO_ISCL:
            case AI_LWO_IPOS:
            case AI_LWO_IROT:
            case AI_LWO_IBMP:
            case AI_LWO_IUTD:
            case AI_LWO_IVTD:

            case AI_LWO_IPIX:
            case AI_LWO_IMIP:
            case AI_LWO_IMOD:
            case AI_LWO_AMOD:
            case AI_LWO_IINV:
            case AI_LWO_INCR:
            case AI_LWO_IAXS:
            case AI_LWO_IFOT:
            case AI_LWO_ITIM:
            case AI_LWO_IWRL:
            case AI_LWO_IUTI:
            case AI_LWO_IUVI:
            case AI_LWO_IINX:
            case AI_LWO_IINY:
            case AI_LWO_IINZ:
            case AI_LWO_IREF:
            case AI_LWO_IMST:
            case AI_LWO_IMAP:
            case AI_LWO_IUTL:
            case AI_LWO_IVTL:
            case AI_LWO_VPVL:
            case AI_LWO_VPRM:
                mFileBuffer = next;
                break;
            case AI_LWO_ENTR:
                std::string attrName;

                while (true) {
                    if (mFileBuffer + 8 >= next)
                        break;

                    IFF::ChunkHeader head1 = IFF::LoadChunk(mFileBuffer);
                    int bufOffset1 = 0;
                    if (head1.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form
                        mFileBuffer -= 8;
                        head1 = IFF::LoadForm(mFileBuffer);
                        bufOffset1 = 4;
                    }

                    if (mFileBuffer + head1.length > end) {
                        throw DeadlyImportError("LWO3: cannot read length;");
                    }
                    uint8_t *const next1 = mFileBuffer + head1.length;
                    mFileBuffer += bufOffset1;

                    switch (head1.type) {
                        case AI_LWO_FLAG:
                        case AI_LWO_TAG:
                            mFileBuffer = next1;
                            break;
                        case AI_LWO_NAME:
                            GetS0(attrName, head1.length);
                            break;
                        case AI_LWO_VALU:
                            mFileBuffer += 8;

                            std::string valueType;
                            GetS0(valueType, 8);

                            if (valueType == "int") {
                                static_cast<void>(GetU4());
                            } else if (valueType == "double") {
                                static_cast<void>(GetU8());
                            } else if (valueType == "vparam") {
                                mFileBuffer += 24;

                                float value = GetF8();
                                if (attrName == "Diffuse") {
                                    surf.mDiffuseValue = value;
                                } else if (attrName == "Specular") {
                                    surf.mSpecularValue = value;
                                } else if (attrName == "Transparency") {
                                    surf.mTransparency = value;
                                } else if (attrName == "Glossiness") {
                                    surf.mGlossiness = value;
                                } else if (attrName == "Luminosity") {
                                    surf.mLuminosity = value;
                                } else if (attrName == "Color Highlight") {
                                    surf.mColorHighlights = value;
                                } else if (attrName == "Refraction Index") {
                                    surf.mIOR = value;
                                } else if (attrName == "Bump Height") {
                                    surf.mBumpIntensity = value;
                                }
                            } else if (valueType == "vparam3") {
                                mFileBuffer += 24;

                                float value1, value2, value3;
                                value1 = GetF8();
                                value2 = GetF8();
                                value3 = GetF8();

                                if (attrName == "Color") {
                                    surf.mColor.r = value1;
                                    surf.mColor.g = value2;
                                    surf.mColor.b = value3;
                                }
                            }

                            mFileBuffer = next1;
                            break;
                    }
                }

                break;
        }
    }
}

// ------------------------------------------------------------------------------------------------
void LWOImporter::LoadLWO2Surface(unsigned int size) {
    LE_NCONST uint8_t *const end = mFileBuffer + size;

    mSurfaces->push_back(LWO::Surface());
    LWO::Surface &surf = mSurfaces->back();

    GetS0(surf.mName, size);

    // check whether this surface was derived from any other surface
    std::string derived;
    GetS0(derived, (unsigned int)(end - mFileBuffer));
    if (derived.length()) {
        // yes, find this surface
        for (SurfaceList::iterator it = mSurfaces->begin(), itEnd = mSurfaces->end() - 1; it != itEnd; ++it) {
            if ((*it).mName == derived) {
                // we have it ...
                surf = *it;
                derived.clear();
                break;
            }
        }
        if (derived.size()) {
            ASSIMP_LOG_WARN("LWO2: Unable to find source surface: ", derived);
        }
    }

    while (true) {
        if (mFileBuffer + 6 >= end)
            break;
        const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer);

        if (mFileBuffer + head.length > end)
            throw DeadlyImportError("LWO2: Invalid surface chunk length");

        uint8_t *const next = mFileBuffer + head.length;
        switch (head.type) {
                // diffuse color
            case AI_LWO_COLR: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, COLR, 12);
                surf.mColor.r = GetF4();
                surf.mColor.g = GetF4();
                surf.mColor.b = GetF4();
                break;
            }
                // diffuse strength ... hopefully
            case AI_LWO_DIFF: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, DIFF, 4);
                surf.mDiffuseValue = GetF4();
                break;
            }
                // specular strength ... hopefully
            case AI_LWO_SPEC: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SPEC, 4);
                surf.mSpecularValue = GetF4();
                break;
            }
                // transparency
            case AI_LWO_TRAN: {
                // transparency explicitly disabled?
                if (surf.mTransparency == 10e10f)
                    break;

                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, TRAN, 4);
                surf.mTransparency = GetF4();
                break;
            }
                // additive transparency
            case AI_LWO_ADTR: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, ADTR, 4);
                surf.mAdditiveTransparency = GetF4();
                break;
            }
                // wireframe mode
            case AI_LWO_LINE: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, LINE, 2);
                if (GetU2() & 0x1)
                    surf.mWireframe = true;
                break;
            }
                // glossiness
            case AI_LWO_GLOS: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, GLOS, 4);
                surf.mGlossiness = GetF4();
                break;
            }
                // bump intensity
            case AI_LWO_BUMP: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, BUMP, 4);
                surf.mBumpIntensity = GetF4();
                break;
            }
                // color highlights
            case AI_LWO_CLRH: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, CLRH, 4);
                surf.mColorHighlights = GetF4();
                break;
            }
                // index of refraction
            case AI_LWO_RIND: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, RIND, 4);
                surf.mIOR = GetF4();
                break;
            }
                // polygon sidedness
            case AI_LWO_SIDE: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SIDE, 2);
                surf.bDoubleSided = (3 == GetU2());
                break;
            }
                // maximum smoothing angle
            case AI_LWO_SMAN: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SMAN, 4);
                surf.mMaximumSmoothAngle = std::fabs(GetF4());
                break;
            }
                // vertex color channel to be applied to the surface
            case AI_LWO_VCOL: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, VCOL, 12);
                surf.mDiffuseValue *= GetF4(); // strength
                ReadVSizedIntLWO2(mFileBuffer); // skip envelope
                surf.mVCMapType = GetU4(); // type of the channel

                // name of the channel
                GetS0(surf.mVCMap, (unsigned int)(next - mFileBuffer));
                break;
            }
                // surface bock entry
            case AI_LWO_BLOK: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, BLOK, 4);
                IFF::SubChunkHeader head2 = IFF::LoadSubChunk(mFileBuffer);

                switch (head2.type) {
                    case AI_LWO_PROC:
                    case AI_LWO_GRAD:
                    case AI_LWO_IMAP:
                        LoadLWO2TextureBlock(&head2, head.length);
                        break;
                    case AI_LWO_SHDR:
                        LoadLWO2ShaderBlock(&head2, head.length);
                        break;

                    default:
                        ASSIMP_LOG_WARN("LWO2: Found an unsupported surface BLOK");
                };

                break;
            }
        }
        mFileBuffer = next;
    }
}

void LWOImporter::LoadLWO3Surface(unsigned int size) {
    mFileBuffer += 8;
    LE_NCONST uint8_t *const end = mFileBuffer + size - 12;

    mSurfaces->push_back(LWO::Surface());
    LWO::Surface &surf = mSurfaces->back();

    GetS0(surf.mName, size);

    // check whether this surface was derived from any other surface
    std::string derived;
    GetS0(derived, (unsigned int)(end - mFileBuffer));
    if (derived.length()) {
        // yes, find this surface
        for (SurfaceList::iterator it = mSurfaces->begin(), itEnd = mSurfaces->end() - 1; it != itEnd; ++it) {
            if ((*it).mName == derived) {
                // we have it ...
                surf = *it;
                derived.clear();
                break;
            }
        }
        if (derived.size()) {
            ASSIMP_LOG_WARN("LWO3: Unable to find source surface: ", derived);
        }
    }
    while (true) {
        if (mFileBuffer + 8 >= end)
            break;

        IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer);
        int bufOffset = 0;
        if( head.type == AI_IFF_FOURCC_FORM ) { // not chunk, it's a form
            mFileBuffer -= 8;
            head = IFF::LoadForm(mFileBuffer);
            bufOffset = 4;
        }

        if (mFileBuffer + head.length > end) {
            throw DeadlyImportError("LWO3: cannot read length; LoadLWO3Surface");
        }

        uint8_t *const next = mFileBuffer + head.length;
        mFileBuffer += bufOffset;
        switch (head.type) {
            case AI_LWO_NODS:
                LoadNodalBlocks(head.length);
                break;
                // polygon sidedness
            case AI_LWO_SIDE: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SIDE, 2);
                surf.bDoubleSided = (3 == GetU2());
                break;
            }
                // maximum smoothing angle
            case AI_LWO_SMAN: {
                AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SMAN, 4);
                surf.mMaximumSmoothAngle = std::fabs(GetF4());
                break;
            }
        }
        mFileBuffer = next;
    }
}

#endif // !! ASSIMP_BUILD_NO_X_IMPORTER
