// Copyright (C) 2007-2012  CEA/DEN, EDF R&D, OPEN CASCADE
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
//
// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
//

// File   : Material_Model.cxx
// Author : Margarita KARPUNINA, Open CASCADE S.A.S. (margarita.karpunina@opencascade.com)
//

#include "Material_Model.h"
#include "GEOM_VTKPropertyMaterial.hxx"
#include "Material_ResourceMgr.h"

/*!
  \brief Constructor

  Create new SALOME material model with default properties.
*/
Material_Model::Material_Model()
{
  myReflection  = ReflectionList(4);
  init(); // set default properties
}

/*!
  \brief Destructor
*/
Material_Model::~Material_Model()
{
}

/*!
  \brief Initialize material data from the given properties list
  \param props material properties
  \sa toProperties()
*/
void Material_Model::fromProperties( const QString& props )
{
  // reset to default values
  init();

  // parse material properties
  QStringList propList = props.split( ":", QString::SkipEmptyParts );
  foreach ( QString prop, propList ) 
  {
    QStringList pdata = prop.split( "=" );
    if ( pdata.count() < 2 ) continue;
    QString key   = pdata[0].trimmed().toLower();
    QString data  = pdata[1].trimmed().toLower();
    bool dblOk, boolOk;
    double dblValue  = data.toDouble( &dblOk );
    bool   boolValue = (bool)( data.toInt( &boolOk ) );
    QColor colorValue;
    
    if      ( key == "ambientcolor" && Qtx::stringToColor( data, colorValue ) ) {
      setColor( Ambient, colorValue );
    }
    else if ( key == "diffusecolor" && Qtx::stringToColor( data, colorValue ) ) {
      setColor( Diffuse, colorValue );
    }
    else if ( key == "specularcolor" && Qtx::stringToColor( data, colorValue ) ) {
      setColor( Specular, colorValue );
    }
    else if ( key == "emissivecolor" && Qtx::stringToColor( data, colorValue ) ) {
      setColor( Emissive, colorValue );
    }
    else if ( key == "frontambientcoefficient" && dblOk ) {
      setReflection( Ambient, dblValue );
    }
    else if ( key == "backambientcoefficient" && dblOk ) {
      setReflection( Ambient, dblValue, false );
    }
    else if ( key == "frontdiffusecoefficient" && dblOk ) {
      setReflection( Diffuse, dblValue );
    }
    else if ( key == "backdiffusecoefficient" && dblOk ) {
      setReflection( Diffuse, dblValue, false );
    }
    else if ( key == "frontspecularcoefficient" && dblOk ) {
      setReflection( Specular, dblValue );
    }
    else if ( key == "backspecularcoefficient" && dblOk ) {
      setReflection( Specular, dblValue, false );
    }
    else if ( key == "frontemissivecoefficient" && dblOk ) {
      setReflection( Emissive, dblValue );
    }
    else if ( key == "backemissivecoefficient" && dblOk ) {
      setReflection( Emissive, dblValue, false );
    }
    else if ( key == "frontshininess" && dblOk ) {
      setShininess( dblValue );
    }
    else if ( key == "backshininess" && dblOk ) {
      setShininess( dblValue, false );
    }
    else if ( key == "transparency" && dblOk ) {
      setTransparency( dblValue );
    }
    else if ( key == "physical" && boolOk ) {
      setPhysical( boolValue );
    }
    else if ( key == "ambient" && boolOk ) {
      setReflection( Ambient, boolValue );
    }
    else if ( key == "diffuse" && boolOk ) {
      setReflection( Diffuse, boolValue );
    }
    else if ( key == "specular" && boolOk ) {
      setReflection( Specular, boolValue );
    }
    else if ( key == "emissive" && boolOk ) {
      setReflection( Emissive, boolValue );
    }
  }
}

/*!
  \brief Get material properties string representation
  \return string representing of material properties
  \sa fromProperties()
*/
QString Material_Model::toProperties()
{
  QStringList props;
  QString fmt = "%1=%2";

  // physical
  props << fmt.arg( "Physical" ).arg( isPhysical() );

  // shininess
  props << fmt.arg( "FrontShininess" ).arg( QString::number ( shininess( true ), 'g', 4 ) ) << fmt.arg( "BackShininess" ).arg( QString::number ( shininess( false ), 'g', 4 ) );

  //transparency
  props << fmt.arg( "Transparency" ).arg( transparency() );

  // ambient reflection
  props << fmt.arg( "Ambient" ).arg( hasReflection( Ambient ) );
  if ( color( Ambient ).isValid() )
    props << fmt.arg( "AmbientColor" ).arg( Qtx::colorToString( color( Ambient ) ) );
  props << fmt.arg( "FrontAmbientCoefficient" ).arg( QString::number ( reflection( Ambient, true ), 'g', 4 ) ) << fmt.arg( "BackAmbientCoefficient" ).arg( QString::number ( reflection( Ambient, false ), 'g', 4 ) );

  // diffuse reflection
  props << fmt.arg( "Diffuse" ).arg( hasReflection( Diffuse ) );
  if ( color( Diffuse ).isValid() )
    props << fmt.arg( "DiffuseColor" ).arg( Qtx::colorToString( color( Diffuse ) ) );
  props << fmt.arg( "FrontDiffuseCoefficient" ).arg( QString::number ( reflection( Diffuse, true ), 'g', 4 ) ) << fmt.arg( "BackDiffuseCoefficient" ).arg( QString::number ( reflection( Diffuse, false ), 'g', 4 ) );

  // specular reflection
  props << fmt.arg( "Specular" ).arg( hasReflection( Specular ) );
  if ( color( Specular ).isValid() )
    props << fmt.arg( "SpecularColor" ).arg( Qtx::colorToString( color( Specular ) ) );
  props << fmt.arg( "FrontSpecularCoefficient" ).arg( QString::number ( reflection( Specular, true ), 'g', 4 ) ) << fmt.arg( "BackSpecularCoefficient" ).arg( QString::number ( reflection( Specular, false ), 'g', 4 ) );

  // emissive reflection
  props << fmt.arg( "Emissive" ).arg( hasReflection( Emissive ) );
  if ( color( Emissive ).isValid() )
    props << fmt.arg( "EmissiveColor" ).arg( Qtx::colorToString( color( Emissive ) ) );
  props << fmt.arg( "FrontEmissiveCoefficient" ).arg( QString::number ( reflection( Emissive, true ), 'g', 4 ) ) << fmt.arg( "BackEmissiveCoefficient" ).arg( QString::number ( reflection( Emissive, false ), 'g', 4 ) );

  return props.join( ":" );
}

/*!
  \brief Initialize material model from the resources

  This function can be used to retrieve material properties from the resource file.

  \param material material name (if not specified, model is initialized by default material)
  \param resMgr resource manager (if not specified, new resources manager is created)
  \sa toResources()
*/
void Material_Model::fromResources( const QString& material, QtxResourceMgr* resMgr )
{
  static QString common = "[common]";
  
  // reset to default values
  init();

  // material name is not specified: use default values
  if ( material.isEmpty() ) return;

  bool ownResourcesMgr = resMgr == 0;
  
  if ( ownResourcesMgr )
    resMgr = new Material_ResourceMgr();

  // read common section
  if ( material != common && resMgr->hasSection( common ) )
    fromResources( common, resMgr );

  // physical
  if ( resMgr->hasValue( material, "physical" ) ) {
    setPhysical( resMgr->booleanValue( material, "physical" ) );
  }

  // shininess
  if ( resMgr->hasValue( material, "front_shininess" ) ) {
    setShininess( resMgr->doubleValue( material, "front_shininess" ) );
  }
  if ( resMgr->hasValue( material, "back_shininess" ) ) {
    setShininess( resMgr->doubleValue( material, "back_shininess" ), false );
  }

  // transparency
  if ( resMgr->hasValue( material, "transparency" ) ) {
    setTransparency( resMgr->doubleValue( material, "transparency" ) );
  }

  // ambient reflection
  if ( resMgr->hasValue( material, "ambient-color" ) ) {
    setColor( Ambient, resMgr->colorValue( material, "ambient-color" ) );
  }
  if ( resMgr->hasValue( material, "front_ambient-coefficient" ) ) {
    setReflection( Ambient, resMgr->doubleValue( material, "front_ambient-coefficient" ) );
  }
  if ( resMgr->hasValue( material, "back_ambient-coefficient" ) ) {
    setReflection( Ambient, resMgr->doubleValue( material, "back_ambient-coefficient" ), false );
  }
  if ( resMgr->hasValue( material, "ambient" ) ) {
    setReflection( Ambient, resMgr->booleanValue( material, "ambient" ) );
  }

  // diffuse reflection
  if ( resMgr->hasValue( material, "diffuse-color" ) ) {
    setColor( Diffuse, resMgr->colorValue( material, "diffuse-color" ) );
  }
  if ( resMgr->hasValue( material, "front_diffuse-coefficient" ) ) {
    setReflection( Diffuse, resMgr->doubleValue( material, "front_diffuse-coefficient" ) );
  }
  if ( resMgr->hasValue( material, "back_diffuse-coefficient" ) ) {
    setReflection( Diffuse, resMgr->doubleValue( material, "back_diffuse-coefficient" ), false );
  }
  if ( resMgr->hasValue( material, "diffuse" ) ) {
    setReflection( Diffuse, resMgr->booleanValue( material, "diffuse" ) );
  }

  // specular reflection
  if ( resMgr->hasValue( material, "specular-color" ) ) {
    setColor( Specular, resMgr->colorValue( material, "specular-color" ) );
  }
  if ( resMgr->hasValue( material, "front_specular-coefficient" ) ) {
    setReflection( Specular, resMgr->doubleValue( material, "front_specular-coefficient" ) );
  }
  if ( resMgr->hasValue( material, "back_specular-coefficient" ) ) {
    setReflection( Specular, resMgr->doubleValue( material, "back_specular-coefficient" ), false );
  }
  if ( resMgr->hasValue( material, "specular" ) ) {
    setReflection( Specular, resMgr->booleanValue( material, "specular" ) );
  }

  // emissive reflection
  if ( resMgr->hasValue( material, "emissive-color" ) ) {
    setColor( Emissive, resMgr->colorValue( material, "emissive-color" ) );
  }
  if ( resMgr->hasValue( material, "front_emissive-coefficient" ) ) {
    setReflection( Emissive, resMgr->doubleValue( material, "front_emissive-coefficient" ) );
  }
  if ( resMgr->hasValue( material, "back_emissive-coefficient" ) ) {
    setReflection( Emissive, resMgr->doubleValue( material, "back_emissive-coefficient" ), false );
  }
  if ( resMgr->hasValue( material, "emissive" ) ) {
    setReflection( Emissive, resMgr->booleanValue( material, "emissive" ) );
  }

  if ( ownResourcesMgr )
    delete resMgr;
}

/*!
  \brief Save material properties to the resource file.
  \param material material name
  \param resMgr resource manager
  \sa fromResources()
*/
void Material_Model::toResources( const QString& material, QtxResourceMgr* resMgr )
{
  if ( resMgr && !material.isEmpty() ) {
    // remove resources section (to clean-up all previous properties)
    resMgr->remove( material );

    // physical
    resMgr->setValue( material, "physical", isPhysical() );

    // shininess
    resMgr->setValue( material, "front_shininess", shininess( true) );
    resMgr->setValue( material, "back_shininess", shininess( false ) );

    // transparency
    resMgr->setValue( material, "transparency", transparency() );

    // ambient reflection
    if ( color( Ambient ).isValid() )
      resMgr->setValue( material, "ambient-color", color( Ambient ) );
    resMgr->setValue( material, "front_ambient-coefficient", reflection( Ambient ) );
    resMgr->setValue( material, "back_ambient-coefficient", reflection( Ambient, false ) );
    resMgr->setValue( material, "ambient", hasReflection( Ambient ) );

    // diffuse reflection
    if ( color( Diffuse ).isValid() )
      resMgr->setValue( material, "diffuse-color", color( Diffuse ) );
    resMgr->setValue( material, "front_diffuse-coefficient", reflection( Diffuse ) );
    resMgr->setValue( material, "back_diffuse-coefficient", reflection( Diffuse, false ) );
    resMgr->setValue( material, "diffuse", hasReflection( Diffuse ) );

    // Specular reflection
    if ( color( Specular ).isValid() )
      resMgr->setValue( material, "specular-color", color( Specular ) );
    resMgr->setValue( material, "front_specular-coefficient", reflection( Specular ) );
    resMgr->setValue( material, "back_specular-coefficient", reflection( Specular, false ) );
    resMgr->setValue( material, "specular", hasReflection( Specular ) );

    // Emissive reflection
    if ( color( Emissive ).isValid() )
      resMgr->setValue( material, "emissive-color", color( Emissive ) );
    resMgr->setValue( material, "front_emissive-coefficient", reflection( Emissive ) );
    resMgr->setValue( material, "back_emissive-coefficient", reflection( Emissive, false ) );
    resMgr->setValue( material, "emissive", hasReflection( Emissive ) );
  }
}

/*!
  \brief Get material type
  \return \c true if material is physical or \c false otherwise
  \sa setPhysical()
*/
bool Material_Model::isPhysical() const
{
  return myIsPhysical;
}

/*!
  \brief Set material type
  \param value \c true if material is physical or \c false otherwise
  \sa isPhysical()
*/
void Material_Model::setPhysical( bool value )
{
  myIsPhysical = value;
}

/*!
  \brief Check if given reflection is enabled
  \param type reflection type
  \return \c true if specified reflection type is enabled or \c false otherwise
  \sa setReflection(ReflectionType, bool)
*/
bool Material_Model::hasReflection( ReflectionType type ) const
{
  bool value = false;
  if ( type >= 0 && type < 4 )
    value = myReflection[ type ].enabled;
  return value;
}

/*!
  \brief Enable/disable given reflection type
  \param type reflection type
  \param value boolean flag
  \sa hasReflection()
*/
void Material_Model::setReflection( ReflectionType type, bool value )
{
  if ( type >= 0 && type < 4 )
    myReflection[ type ].enabled = value;
}

/*!
  \brief Get color value for the given reflection type
  \param type reflection type
  \return color associated with the specified reflection type
  \sa setColor()
*/
QColor Material_Model::color( ReflectionType type ) const
{
  QColor value;
  if ( type >= 0 && type < 4 )
    value = myReflection[ type ].color;
  return value;
}

/*!
  \brief Set color value for the given reflection type
  \param type reflection type
  \param value color to be used for specified reflection type
  \sa color()
*/
void Material_Model::setColor( ReflectionType type, const QColor& value )
{
  if ( type >= 0 && type < 4 )
    myReflection[ type ].color = value;
}

/*!
  \brief Get coefficient value for the given reflection type
  \param type reflection type
  \param theIsFront boolean flag for choosing side
  \return coefficient value for the specified reflection type
  \sa setReflection(ReflectionType, double, bool = true)
*/
double Material_Model::reflection( ReflectionType type, bool theIsFront ) const
{
  double value = 0.0;
  if ( type >= 0 && type < 4 )
    if ( theIsFront )
      value = myReflection[ type ].front_coef;
    else
      value = myReflection[ type ].back_coef;
  return value;
}

/*!
  \brief Set coefficient value for the given reflection type
  \param type reflection type
  \param value coefficient to be used by the given reflection type
  \param theIsFront boolean flag for choosing side
  \sa reflection( bool = true)
*/
void Material_Model::setReflection( ReflectionType type, double value, bool theIsFront )
{
  if ( type >= 0 && type < 4 )
    if ( theIsFront )
      myReflection[ type ].front_coef = value;
    else
      myReflection[ type ].back_coef = value;
}

/*!
  \brief Get shininess value
  \param theIsFront boolean flag for choosing side
  \return shininess value of the material
  \sa setShininess( double, bool = true )
*/
double Material_Model::shininess( bool theIsFront ) const
{
  if ( theIsFront )
    return myFrontShininess;
  else
    return myBackShininess;
}

/*!
  \brief Set shininess value
  \param value new shininess value
  \param theIsFront boolean flag for choosing side
  \sa shininess( bool = true )
*/
void Material_Model::setShininess( double value, bool theIsFront )
{
  if ( theIsFront )
    myFrontShininess = value;
  else
    myBackShininess = value;
}

/*!
  \brief Get transparency value
  \return transparency value of the material
  \sa setTransparency()
*/
double Material_Model::transparency() const
{
  return myTransparency;
}

/*!
  \brief Set transparency value
  \param value new transparency value
  \sa transparency()
*/
void Material_Model::setTransparency( double value )
{
  myTransparency = value;
}

/*!
  \brief Initialize material model with default values
*/
void Material_Model::init()
{  
  QColor c;

  // non-physical by default
  setPhysical( false );
  // shininess
  setShininess( 0.039 );
  setShininess( 0.039, false );
  // transparency
  setTransparency( 0.0 );

  // ambient reflection (enabled by default)
  Qtx::stringToColor( "#333333", c );
  setColor( Ambient, c );
  setReflection( Ambient, 0.3, true );
  setReflection( Ambient, 0.3, false );
  setReflection( Ambient, true );

  // diffuse reflection (enabled by default)
  Qtx::stringToColor( "#000000", c );
  setColor( Diffuse, c );
  setReflection( Diffuse, 0.65 );
  setReflection( Diffuse, 0.65, false );
  setReflection( Diffuse, true );

  // specular reflection (enabled by default)
  Qtx::stringToColor( "#ffffff", c );
  setColor( Specular, c );
  setReflection( Specular, 0.0 );
  setReflection( Specular, 0.0, false );
  setReflection( Specular, true );

  // emissive reflection (disabled by default)
  Qtx::stringToColor( "#000000", c );
  setColor( Emissive, c );
  setReflection( Emissive, 0.0 );
  setReflection( Emissive, 0.0, false );
  setReflection( Emissive, false );
}

/*!
  \brief Construct OCCT material aspect from material model
  \param theIsFront boolean flag for choosing side
  \return material aspect object with corresponding properties
*/
Graphic3d_MaterialAspect Material_Model::getMaterialOCCAspect( bool theIsFront )
{
  // Get material aspect from the current model
  Graphic3d_MaterialAspect aspect;

  QColor c;
  
  // ambient reflection
  if ( color( Ambient ).isValid() ) {
    c = color( Ambient );
    aspect.SetAmbientColor( Quantity_Color( c.redF(), c.greenF(), c.blueF(), Quantity_TOC_RGB ) );
  }
  aspect.SetAmbient( reflection( Ambient, theIsFront ));
  if ( hasReflection( Ambient ) )
    aspect.SetReflectionModeOn( Graphic3d_TOR_AMBIENT );
  else
    aspect.SetReflectionModeOff( Graphic3d_TOR_AMBIENT );
  
  // diffuse reflection
  if ( color( Diffuse ).isValid() ) {
    c = color( Diffuse );
    aspect.SetDiffuseColor( Quantity_Color( c.redF(), c.greenF(), c.blueF(), Quantity_TOC_RGB ) );
  }
  aspect.SetDiffuse( reflection( Diffuse, theIsFront ));
  if ( hasReflection( Diffuse ) )
    aspect.SetReflectionModeOn( Graphic3d_TOR_DIFFUSE );
  else
    aspect.SetReflectionModeOff( Graphic3d_TOR_DIFFUSE );

  // specular reflection
  if ( color( Specular ).isValid() ) {
    c = color( Specular );
    aspect.SetSpecularColor( Quantity_Color( c.redF(), c.greenF(), c.blueF(), Quantity_TOC_RGB ) );
  }
  aspect.SetSpecular( reflection( Specular, theIsFront ));
  if ( hasReflection( Specular ) )
    aspect.SetReflectionModeOn( Graphic3d_TOR_SPECULAR );
  else
    aspect.SetReflectionModeOff( Graphic3d_TOR_SPECULAR );

  // emissive reflection
  if ( color( Emissive ).isValid() ) {
    c = color( Emissive );
    aspect.SetEmissiveColor( Quantity_Color( c.redF(), c.greenF(), c.blueF(), Quantity_TOC_RGB ) );
  }
  aspect.SetEmissive( reflection( Emissive, theIsFront ));
  if ( hasReflection( Emissive ) )
    aspect.SetReflectionModeOn( Graphic3d_TOR_EMISSION );
  else
    aspect.SetReflectionModeOff( Graphic3d_TOR_EMISSION );
  
  // shininess
  aspect.SetShininess( shininess( theIsFront ) );

  // transparency
  aspect.SetTransparency( transparency() );

  // material type
  aspect.SetMaterialType( isPhysical() ? Graphic3d_MATERIAL_PHYSIC : Graphic3d_MATERIAL_ASPECT );

  return aspect;
}

/*!
  \brief Construct VTK property from material model
  \param theIsFront boolean flag for choosing side
  \return VTK property with correspondent material properties
*/
GEOM_VTKPropertyMaterial* Material_Model::getMaterialVTKProperty( bool theIsFront )
{
  // NOTE: In VTK it's impossible to switch on/off specific reflection type
  // NOTE: In VTK emissive reflection type is not supported
  // NOTE: In VTK shininess is specified via SpecularPower attribute
  // NOTE: In VTK transparency is specified via Opacity attribute

  // Get material properties from the current model
  GEOM_VTKPropertyMaterial* prop = GEOM_VTKPropertyMaterial::New();

  QColor c;

  // ambient reflection
  if ( color( Ambient ).isValid() && hasReflection( Ambient ) ) {
    c = color( Ambient );
    prop->SetAmbientColor( c.redF(), c.greenF(), c.blueF() );
    prop->SetAmbient( reflection( Ambient, theIsFront ) );
  }

  // diffuse reflection
  if ( color( Diffuse ).isValid() && hasReflection( Diffuse ) ) {
    c = color( Diffuse );
    prop->SetDiffuseColor( c.redF(), c.greenF(), c.blueF() );
    prop->SetDiffuse( reflection( Diffuse, theIsFront ) );
  }

  // specular reflection
  if ( color( Specular ).isValid() && hasReflection( Specular ) ) {
    c = color( Specular );
    prop->SetSpecularColor( c.redF(), c.greenF(), c.blueF() );
    prop->SetSpecular( reflection( Specular, theIsFront ) );
  }

  // shininess
  prop->SetSpecularPower( shininess( theIsFront )*100.0 );

  // transparency
  prop->SetOpacity( 1 - transparency() );

  // material type
  prop->SetPhysical( isPhysical() );

  return prop;
}

