/*
 * Copyright (c) 2009-2015 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use 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 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * 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.
 */
package com.jme3.scene.plugins.fbx.material;

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Texture;
import com.jme3.texture.image.ColorSpace;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FbxMaterial extends FbxObject<Material> {
    
    private static final Logger logger = Logger.getLogger(FbxMaterial.class.getName());
    
    private String shadingModel; // TODO: do we care about this? lambert just has no specular?
    private final FbxMaterialProperties properties = new FbxMaterialProperties();
    
    public FbxMaterial(AssetManager assetManager, String sceneFolderName) {
        super(assetManager, sceneFolderName);
    }
    
    @Override
    public void fromElement(FbxElement element) {
        super.fromElement(element);
        if(!getSubclassName().equals("")) {
            return;
        }
        
        FbxElement shadingModelEl = element.getChildById("ShadingModel");
        if (shadingModelEl != null) { 
            shadingModel = (String) shadingModelEl.properties.get(0);
            if (!shadingModel.equals("")) {
                if (!shadingModel.equalsIgnoreCase("phong") &&
                    !shadingModel.equalsIgnoreCase("lambert")) {
                    logger.log(Level.WARNING, "FBX material uses unknown shading model: {0}. "
                                            + "Material may display incorrectly.", shadingModel);  
                }
            }
        }
        
        for (FbxElement child : element.getFbxProperties()) {
            properties.setPropertyFromElement(child);
        }
    }
    
    @Override
    public void connectObject(FbxObject object) {
        unsupportedConnectObject(object);
    }

    @Override
    public void connectObjectProperty(FbxObject object, String property) {
        if (!(object instanceof FbxTexture)) {
            unsupportedConnectObjectProperty(object, property);
        }
        
        properties.setPropertyTexture(property, (FbxTexture) object);
    }
    
    private static void multRGB(ColorRGBA color, float factor) {
        color.r *= factor;
        color.g *= factor;
        color.b *= factor;
    }
    
    @Override
    protected Material toJmeObject() {
        ColorRGBA ambient  = null;
        ColorRGBA diffuse  = null;
        ColorRGBA specular = null;
        ColorRGBA transp   = null;
        ColorRGBA emissive = null;
        float shininess    = 1f;
        boolean separateTexCoord = false;
        
        Texture diffuseMap  = null;
        Texture specularMap = null;
        Texture normalMap   = null;
        Texture transpMap   = null;
        Texture emitMap     = null;
        Texture aoMap       = null;
        
        FbxTexture fbxDiffuseMap = null;
        
        Object diffuseColor = properties.getProperty("DiffuseColor");
        if (diffuseColor != null) {
            if (diffuseColor instanceof ColorRGBA) {
                diffuse = ((ColorRGBA) diffuseColor).clone();
            } else if (diffuseColor instanceof FbxTexture) {
                FbxTexture tex = (FbxTexture) diffuseColor;
                fbxDiffuseMap = tex;
                diffuseMap = tex.getJmeObject();
                diffuseMap.getImage().setColorSpace(ColorSpace.sRGB);
            }
        }
        
        Object diffuseFactor = properties.getProperty("DiffuseFactor");
        if (diffuseFactor != null && diffuseFactor instanceof Float) {
            float factor = (Float)diffuseFactor;
            if (diffuse != null) {
                multRGB(diffuse, factor);
            } else {
                diffuse = new ColorRGBA(factor, factor, factor, 1f);
            }
        }
        
        Object specularColor = properties.getProperty("SpecularColor");
        if (specularColor != null) {
            if (specularColor instanceof ColorRGBA) {
                specular = ((ColorRGBA) specularColor).clone();
            } else if (specularColor instanceof FbxTexture) {
                FbxTexture tex = (FbxTexture) specularColor;
                specularMap = tex.getJmeObject();
                specularMap.getImage().setColorSpace(ColorSpace.sRGB);
            }
        }
        
        Object specularFactor = properties.getProperty("SpecularFactor");
        if (specularFactor != null && specularFactor instanceof Float) {
            float factor = (Float)specularFactor;
            if (specular != null) {
                multRGB(specular, factor);
            } else {
                specular = new ColorRGBA(factor, factor, factor, 1f);
            }
        }
        
        Object transparentColor = properties.getProperty("TransparentColor");
        if (transparentColor != null) {
            if (transparentColor instanceof ColorRGBA) {
                transp = ((ColorRGBA) transparentColor).clone();
            } else if (transparentColor instanceof FbxTexture) {
                FbxTexture tex = (FbxTexture) transparentColor;
                transpMap = tex.getJmeObject();
                transpMap.getImage().setColorSpace(ColorSpace.sRGB);
            }
        }
        
        Object transparencyFactor = properties.getProperty("TransparencyFactor");
        if (transparencyFactor != null && transparencyFactor instanceof Float) {
            float factor = (Float)transparencyFactor;
            if (transp != null) {
                transp.a *= factor;
            } else {
                transp = new ColorRGBA(1f, 1f, 1f, factor);
            }
        }
        
        Object emissiveColor = properties.getProperty("EmissiveColor");
        if (emissiveColor != null) {
            if (emissiveColor instanceof ColorRGBA) {
                emissive = ((ColorRGBA)emissiveColor).clone();
            } else if (emissiveColor instanceof FbxTexture) {
                FbxTexture tex = (FbxTexture) emissiveColor;
                emitMap = tex.getJmeObject();
                emitMap.getImage().setColorSpace(ColorSpace.sRGB);
            }
        }
        
        Object emissiveFactor = properties.getProperty("EmissiveFactor");
        if (emissiveFactor != null && emissiveFactor instanceof Float) {
            float factor = (Float)emissiveFactor;
            if (emissive != null) { 
                multRGB(emissive, factor);
            } else {
                emissive = new ColorRGBA(factor, factor, factor, 1f);
            }
        }
        
        Object ambientColor = properties.getProperty("AmbientColor");
        if (ambientColor != null && ambientColor instanceof ColorRGBA) {
            ambient = ((ColorRGBA)ambientColor).clone();
        }
        
        Object ambientFactor = properties.getProperty("AmbientFactor");
        if (ambientFactor != null && ambientFactor instanceof Float) { 
            float factor = (Float)ambientFactor;
            if (ambient != null) {
                multRGB(ambient, factor);
            } else {
                ambient = new ColorRGBA(factor, factor, factor, 1f);
            }
        }
        
        Object shininessFactor = properties.getProperty("Shininess");
        if (shininessFactor != null) {
            if (shininessFactor instanceof Float) {
                shininess = (Float) shininessFactor;
            } else if (shininessFactor instanceof FbxTexture) {
                // TODO: support shininess textures
            }
        }
        
        Object bumpNormal = properties.getProperty("NormalMap");
        if (bumpNormal != null) {
            if (bumpNormal instanceof FbxTexture) {
                // TODO: check all meshes that use this material have tangents
                //       otherwise shading errors occur
                FbxTexture tex = (FbxTexture) bumpNormal;
                normalMap = tex.getJmeObject();
                normalMap.getImage().setColorSpace(ColorSpace.Linear);
            }
        }
        
        Object aoColor = properties.getProperty("DiffuseColor2");
        if (aoColor != null) {
            if (aoColor instanceof FbxTexture) {
                FbxTexture tex = (FbxTexture) aoColor;
                if (tex.getUvSet() != null && fbxDiffuseMap != null) {
                    if (!tex.getUvSet().equals(fbxDiffuseMap.getUvSet())) {
                        separateTexCoord = true;
                    }
                }
                aoMap = tex.getJmeObject();
                aoMap.getImage().setColorSpace(ColorSpace.sRGB);
            }
        }
        
        // TODO: how to disable transparency from diffuse map?? Need "UseAlpha" again..

        assert ambient == null  || ambient.a  == 1f;
        assert diffuse == null  || diffuse.a  == 1f;
        assert specular == null || specular.a == 1f;
        assert emissive == null || emissive.a == 1f;
        assert transp == null   || (transp.r == 1f && transp.g == 1f && transp.b == 1f);
        
        // If shininess is less than 1.0, the lighting shader won't be able
        // to handle it. Gotta disable specularity then.
        if (shininess < 1f) {
            shininess = 1f;
            specular = ColorRGBA.Black;
        }
        
        // Try to guess if we need to enable alpha blending.
        // FBX does not specify this explicitly.
        boolean useAlphaBlend = false;
        
        if (diffuseMap != null && diffuseMap == transpMap) {
            // jME3 already uses alpha from diffuseMap
            // (if alpha blend is enabled)
            useAlphaBlend = true;
            transpMap = null; 
        } else if (diffuseMap != null && transpMap != null && diffuseMap != transpMap) {
            // TODO: potential bug here. Alpha from diffuse may 
            // leak unintentionally. 
            useAlphaBlend = true;
        } else if (transpMap != null) {
            // We have alpha map but no diffuse map, OK.
            useAlphaBlend = true;
        }
        
        if (transp != null && transp.a != 1f) {
            // Consolidate transp into diffuse
            // (jME3 doesn't use a separate alpha color)
            
            // TODO: potential bug here. Alpha from diffuse may 
            // leak unintentionally. 
            useAlphaBlend = true;
            if (diffuse != null) {
                diffuse.a = transp.a;
            } else {
                diffuse = transp;
            }
        }
        
        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        mat.setName(name);
        
        // TODO: load this from FBX material.
        mat.setReceivesShadows(true);
        
        if (useAlphaBlend) {
            // No idea if this is a transparent or translucent model, gotta guess..
            mat.setTransparent(true);
            mat.setFloat("AlphaDiscardThreshold", 0.01f);
            mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
        }
        
        mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
        
        // Set colors.
        if (ambient != null || diffuse != null || specular != null) {
            // If either of those is set, we have to set them all.
            // NOTE: default specular is black, unless it is set explicitly.
            mat.setBoolean("UseMaterialColors", true);
            mat.setColor("Ambient",  /*ambient  != null ? ambient  :*/ ColorRGBA.White);
            mat.setColor("Diffuse",  diffuse  != null ? diffuse  : ColorRGBA.White);
            mat.setColor("Specular", specular != null ? specular : ColorRGBA.Black);
        }
        
        if (emissive != null) { 
            mat.setColor("GlowColor", emissive);
        }
        
        // Set shininess.
        if (shininess > 1f) {
            // Convert shininess from 
            // Phong (FBX shading model) to Blinn (jME3 shading model).
            float blinnShininess = (shininess * 5.1f) + 1f;
            mat.setFloat("Shininess", blinnShininess);
        }
        
        // Set textures.
        if (diffuseMap != null) {
            mat.setTexture("DiffuseMap", diffuseMap);
        }
        if (specularMap != null) {
            mat.setTexture("SpecularMap", specularMap);
        }
        if (normalMap != null) {
            mat.setTexture("NormalMap", normalMap);
        }
        if (transpMap != null) {
//            mat.setTexture("AlphaMap", transpMap);
        }
        if (emitMap != null) {
            mat.setTexture("GlowMap", emitMap);
        }
        if (aoMap != null) {
            mat.setTexture("LightMap", aoMap);
            if (separateTexCoord) {
                mat.setBoolean("SeparateTexCoord", true);
            }
        }
        
        return mat;
    }
}
