/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */

var Texture = require("js/lib/rdge/texture").Texture;


///////////////////////////////////////////////////////////////////////
// Class GLMaterial
//      GL representation of a material.
///////////////////////////////////////////////////////////////////////
var Material = function GLMaterial( world ) {
    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////
	this._name = "GLMaterial";
	this._shaderName = "undefined";

	this._time = 0.0;
	this._dTime = 0.01;

	// keep a reference to the owning GLWorld
	this._world = null;
    if(world) {
        this._world = world;
    }

	this._glTextures = [];		// indexed by uniform name

	// vertex deformation variables
	this._hasVertexDeformation = false;
	this._vertexDeformationRange = [0, 0, 1, 1];	// (xMin, yMin, xMax, yMax)
	this._vertexDeformationTolerance = 0.1;

	// RDGE variables
	this._shader = null;
	this._materialNode = null;

	// vertex deformation variables
	this._hasVertexDeformation = false;
	this._vertexDeformationRange = [0, 0, 1, 1];	// (xMin, yMin, xMax, yMax)
	this._vertexDeformationTolerance = 0.02;

    ///////////////////////////////////////////////////////////////////////
    // Property Accessors
    ///////////////////////////////////////////////////////////////////////

	this.setName = function(n) {
        this._name = n;
    };

	this.getName = function() {
        return this._name;
    };

	this.setShaderName = function(n) {
        this._shaderName = n;
    };

	this.getShaderName = function() {
        return this._shaderName;
    };

	this.setWorld = function(world) {
        this._world = world;
    };

	this.getWorld = function() {
        return this._world;
    };

	this.getShader = function() {
        return this._shader;
    };

	this.getMaterialNode = function() {
        return this._materialNode;
    };

	// a material can be animated or not. default is not.  
	// Any material needing continuous rendering should override this method
	this.isAnimated	= function() {
        return false;
    };

	this.getTechniqueName	= function() {
		return 'default'
	};

	// the vertex shader can apply deformations requiring refinement in
	// certain areas.
	this.hasVertexDeformation = function()	{
        return this._hasVertexDeformation;
    };

	this.getVertexDeformationRange = function()	{
        return this._vertexDeformationRange.slice();
    };

	this.getVertexDeformationTolerance = function()	{
        return this._vertexDeformationTolerance;
    };

    ///////////////////////////////////////////////////////////////////////
    // Common Material Methods
    ///////////////////////////////////////////////////////////////////////
	this.getProperty = function( propName ) {
		return this._propValues[propName];
	};

	this.getPropertyCount = function() {
		return this._propNames.length;
	};

	this.getPropertyAtIndex = function( index ) {
		var rtnArr = [];
		if ((index < 0) || (index >= this.getPropertyCount())) {
			throw new Error( "property index " + index + " is out of range for material" );
        }

		return [ this._propNames[index],  this._propLabels[index],  this._propTypes[index],  this._propValues[index] ];
	};

	this.getAllProperties = function( propNames,  propValues,  propTypes,  propLabels) {
		// clear all the input arrays if there is junk in them
		propNames.length	= 0;
		propValues.length	= 0;
		propTypes.length	= 0;
		propLabels.length	= 0;

		var nProps = this._propNames.length;
		for (var i=0;  i<nProps;  i++) {
			propNames[i]	= this._propNames[i];
			propValues[i]	= this._propValues[this._propNames[i]];
			propTypes[i]	= this._propTypes[i];
			propLabels[i]	= this._propLabels[i];
		}
	};

	this.hasProperty = function( prop )
	{
		var propNames = [],  dummy = [];
		this.getAllProperties( propNames, dummy, dummy, dummy )
		for (var i=0;  i<propNames.length;  i++)
		{
			if (prop === propNames[i])  return true;
		}
	};

	this.getPropertyType = function( prop )
	{
		var n = this.getPropertyCount();
		for (var i=0;  i<n;  i++)
		{
			if (prop === this._propNames[i])  return this._propTypes[i];
		}
	};

    this.dup = function ()
	{
        // get the current values;
        var propNames = [], propValues = [], propTypes = [], propLabels = [];
        this.getAllProperties(propNames, propValues, propTypes, propLabels);
        
        // allocate a new material
		var MaterialLibrary = require("js/models/materials-model").MaterialsModel;
        var newMat = MaterialLibrary.createMaterialByShaderName( this.getShaderName() );

		// copy over the current values;
        var n = propNames.length;
        for (var i = 0; i < n; i++)
            newMat.setProperty(propNames[i], propValues[i]);

        return newMat;
    };

	this.validateProperty = function( prop, value ) {
		var rtnVal = false;
		try
		{
			//if (!this._propValues[prop])  return false;

			// find the index of the property
			var n = this._propNames.length;
			var valType =  typeof value;
			for (var i=0;  i<n;  i++) {
				if (this._propNames[i] == prop) {

					switch (this._propTypes[i])
					{
						case "color":
							rtnVal = ((valType == "object") && (value.length >= 4));
							break;

						case "vector2d":
							rtnVal = ((valType == "object") && (value.length >= 2));
							break;
							
						case "vector3d":
							rtnVal = ((valType == "object") && (value.length >= 3));
							break;

						case "angle":
						case "float":
							rtnVal = (valType == "number");
							break;

						case "file":
							rtnVal = ((valType == "string") || !value);
							break;
					}

					break;
				}
			}
		}
		catch(e)  {
			console.log( "setting invalid material property: " + prop + ", value: " + value );
		}
		
//		if (!rtnVal && (prop != 'color')) {
//			console.log( "invalid material property: " + prop + " : " + value );
//        }

		return rtnVal;
	};

	this.setProperty = function( prop,  value )
	{
		var ok = this.validateProperty( prop, value );
		if (!ok && (prop != 'color')) {
			//console.log( "invalid property in Material:" + prop + " : " + value );
			return;
		}

		// get the technique if the shader is instantiated
		var technique;
		var material = this._materialNode;
		if (material)  technique = material.shaderProgram[this.getTechniqueName()];

		switch (this.getPropertyType(prop))
		{
			case "angle":
			case "float":
				this._propValues[prop] = value;
				if (technique)  technique[prop].set( [value] );
				break;

			case "file":
				this._propValues[prop] = value.slice();
				if (technique)
				{
					var glTex = new Texture( this.getWorld(),  value );
					this._glTextures[prop] = glTex;
					glTex.render();
					var tex = glTex.getTexture();
					if (tex)  technique[prop].set( tex );
				}
				break;

			case "color":
			case "vector2d":
			case "vector3d":
				this._propValues[prop] = value.slice();
				if (technique)  technique[prop].set( value );
				break;
		}
	};

	this.setShaderValues = function()
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram[this.getTechniqueName()];
			if (technique)
			{
				var n = this.getPropertyCount();
				for (var i=0;  i<n;  i++)
				{
					var prop = this._propNames[i],
						value = this._propValues[prop];

					switch (this._propTypes[i])
					{
						case "angle":
						case "float":
							technique[prop].set( [value] );
							break;

						case "file":
							var glTex = this._glTextures[prop];
							if (glTex)
							{
								var tex = glTex.getTexture();
								if (tex)  technique[prop].set( tex );
							}
							else
								this.setProperty( prop, value );
							break;

						case "color":
						case "vector2d":
						case "vector3d":
							technique[prop].set( value );
							break;
					}
				}
			}
		}
	};

	// JSON export
	this.exportJSON = function()
	{
		var jObj =
		{
			'material'		: this.getShaderName(),
			'name'			: this.getName(),
			'dTime'         : this._dTime
		};

		var n = this.getPropertyCount();
		for (var i=0;  i<n;  i++)
		{
			var	prop  = this._propNames[i],
				value = this._propValues[prop];

			jObj[prop] = value;
		}

		if (this.customExport)
			jObj = this.customExport( jObj );

		return jObj;
	};
	
    this.importJSON = function (jObj) {
        if (this.getShaderName() != jObj.material) throw new Error("ill-formed material");
        this.setName(jObj.name);

        try
		{
			for (var prop in jObj)
			{
				if ((prop != 'material') && (prop != 'name'))
				{
					var value = jObj[prop];
					this.setProperty( prop, value );
				}
			}
        }
        catch (e) {
            throw new Error("could not import material: " + jObj);
        }
    };

    ///////////////////////////////////////////////////////////////////////
    
    ///////////////////////////////////////////////////////////////////////
    // Methods
    ///////////////////////////////////////////////////////////////////////
	this.init = function( world ) {
		throw new Error( "Material.init() must be overridden by subclass" );
	};

	this.update = function( time ) {
		// animated materials should implement the update method
	};

	this.fitToPrimitive = function( prim )  {
		// some materials need to preserve an aspect ratio - or someting else.
	};

	this.registerTexture = function( texture ) {
		// the world needs to know about the texture map
		var world = this.getWorld();
		if (!world) {
			console.log( "**** world not defined for registering texture map: " + texture.lookUpName );
        } else {
			world.textureToLoad( texture );
        }
	};

	this.loadTexture = function( texMapName, wrap, mips ) {
		var tex;
		var world = this.getWorld();
		if (!world) {
			console.log( "world not defined for material with texture map" );
        } else {
			var renderer = world.getRenderer();
			tex = renderer.getTextureByName(texMapName, wrap, mips );
			this.registerTexture( tex );
		}

		return tex;
	};

};

if (typeof exports === "object") {
    exports.Material = Material;
}