/* <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 MaterialsModel = require("js/models/materials-model").MaterialsModel;

///////////////////////////////////////////////////////////////////////
// Class GLGeomObj
//      Super class for all geometry classes
///////////////////////////////////////////////////////////////////////
exports.GeomObj = Object.create(Object.prototype, {
    ///////////////////////////////////////////////////////////////////////
    // Constants
    ///////////////////////////////////////////////////////////////////////
    // TODO - Is there a way to make these static constants?
    GEOM_TYPE_RECTANGLE: { value : 1, writable: false },
    GEOM_TYPE_CIRCLE: { value : 2, writable: false },
    GEOM_TYPE_LINE: { value : 3, writable: false },
    GEOM_TYPE_PATH: { value : 4, writable: false },
    GEOM_TYPE_CUBIC_BEZIER: { value : 5, writable: false },
    GEOM_TYPE_BRUSH_STROKE: { value : 6, writable: false },
    GEOM_TYPE_UNDEFINED: { value : -1, writable: false },

    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////
    _matrix: { value : Matrix.I(4), writable: true },

    _next: { value : undefined, writable: true },
    _prev: { value : undefined, writable: true },
    _child: { value : undefined, writable: true },
    _parent: { value : undefined, writable: true },

    m_world: { value : null, writable: true },

    // stroke and fill colors
    _strokeColor: { value : [0, 0, 0, 0], writable: true },
    _fillColor: { value : [0, 0, 0, 0], writable: true },

    // stroke and fill materials
    _fillMaterial: { value : null, writable: true },
    _strokeMaterial: { value : null, writable: true },

    // Shapes (such as lines) that don't support fill should set this to false
    canFill: { value : true, writable: true },

    // array of primitives - used in RDGE
    _primArray: { value : [], writable: true },
    _materialNodeArray: { value : [], writable: true },
    _materialArray: { value : [], writable: true },
    _materialTypeArray: { value : [], writable: true },

    // the transform node used by RDGE
    _trNode: { value : null, writable: true },

    ///////////////////////////////////////////////////////////////////////
    // Property accessors
    ///////////////////////////////////////////////////////////////////////
    getWorld: {
        value: function() {
            return this.m_world;
        }
    },

    setWorld: {
        value: function(world) {
            this.m_world = world;
        }
    },

    getMatrix: {
        value: function() {
            return this._matrix.slice(0);
        }
    },

    setMatrix: {
        value: function(m) {
            this._matrix = m.slice(0);
        }
    },

    getNext: {
        value: function() {
            return this._next;
        }
    },

    setNext: {
        value: function(next) {
            this._next = next;
        }
    },

    getPrev: {
        value: function() {
            return this._prev;
        }
    },

    setPrev: {
        value: function(prev) {
            this._prev = prev;
        }
    },

    getChild: {
        value: function() {
            return this._child;
        }
    },

    setChild: {
        value: function(child) {
            this._child = child;
        }
    },

    getParent: {
        value: function() {
            return this._parent;
        }
    },

    setParent: {
        value: function(parent) {
            this._parent = parent;
        }
    },

    geomType: {
        value: function() {
            return this.GEOM_TYPE_UNDEFINED;
        }
    },

    getPrimitiveArray: {
        value: function() {
            return this._primArray;
        }
    },

    getMaterialNodeArray: {
        value: function() {
            return this._materialNodeArray;
        }
    },

    getMaterialArray: {
        value: function() {
            return this._materialArray;
        }
    },

    getTransformNode: {
        value: function() {
            return this._trNode;
        }
    },

    setTransformNode: {
        value: function(t) {
            this._trNode = t;
        }
    },

    setFillColor: {
        value: function(c) {
            this.setMaterialColor(c, "fill");
        }
    },

    setStrokeColor: {
        value: function(c) {
            this.setMaterialColor(c, "stroke");
        }
    },
    ///////////////////////////////////////////////////////////////////////
    // Methods
    ///////////////////////////////////////////////////////////////////////
    setMaterialColor: {
        value: function(c, type) {
            var i = 0,
                nMats = 0;
            if (c) {
                if (c.gradientMode) {
                    // Gradient support
                    if (this._materialArray && this._materialTypeArray) {
                        nMats = this._materialArray.length;
                    }

                    var stops = [],
                        colors = c.color;

                    var len = colors.length;
                    // TODO - Current shaders only support 4 color stops
                    if (len > 4) {
                        len = 4;
                    }

                    for (var n = 0; n < len; n++) {
                        var position = colors[n].position / 100;
                        var cs = colors[n].value;
                        var stop = [cs.r / 255, cs.g / 255, cs.b / 255, cs.a];
                        stops.push(stop);

                        if (nMats === this._materialTypeArray.length) {
                            for (i = 0; i < nMats; i++) {
                                if (this._materialTypeArray[i] == type) {
                                    this._materialArray[i].setProperty("color" + (n + 1), stop.slice(0));
                                    this._materialArray[i].setProperty("colorStop" + (n + 1), position);
                                }
                            }
                        }
                    }
                    if (type === "fill") {
                        this._fillColor = c;
                    } else {
                        this._strokeColor = c;
                    }
                } else {
                    if (type === "fill") {
                        this._fillColor = c.slice(0);
                    } else {
                        this._strokeColor = c.slice(0);
                    }

                    if (this._materialArray && this._materialTypeArray) {
                        nMats = this._materialArray.length;
                        if (nMats === this._materialTypeArray.length) {
                            for (i = 0; i < nMats; i++) {
                                if (this._materialTypeArray[i] == type) {
                                    this._materialArray[i].setProperty("color", c.slice(0));
                                }
                            }
                        }
                    }
                }
            } else {
                if (type === "fill") {
                    this._fillColor = null;
                } else {
                    this._strokeColor = null;
                }

                if (this._materialArray && this._materialTypeArray) {
                    nMats = this._materialArray.length;
                    if (nMats === this._materialTypeArray.length) {
                        for (i = 0; i < nMats; i++) {
                            if (this._materialTypeArray[i] == type) {
                                // TODO - Not sure how to set color to null values in shaders
                                this._materialArray[i].setProperty("color", [0, 0, 0, 0]);
                            }
                        }
                    }
                }
            }

            var world = this.getWorld();
            if (world) {
                world.restartRenderLoop();
            }
        }
    },

    makeStrokeMaterial: {
        value: function() {
            var strokeMaterial;
            if (this.getStrokeMaterial()) {
                strokeMaterial = this.getStrokeMaterial().dup();
            } else {
                strokeMaterial = MaterialsModel.exportFlatMaterial();
            }

            if (strokeMaterial) {
                strokeMaterial.init(this.getWorld());
            }

            this._materialArray.push(strokeMaterial);
            this._materialTypeArray.push("stroke");

            if (this._strokeColor) {
                this.setStrokeColor(this._strokeColor);
            }

            return strokeMaterial;
        }
    },

    makeFillMaterial: {
        value: function() {
            var fillMaterial;
            if (this.getFillMaterial()) {
                fillMaterial = this.getFillMaterial().dup();
            } else {
                fillMaterial = MaterialsModel.exportFlatMaterial();
            }

            if (fillMaterial) {
                fillMaterial.init(this.getWorld());
            }

            this._materialArray.push(fillMaterial);
            this._materialTypeArray.push("fill");

            if (this._fillColor) {
                this.setFillColor(this._fillColor);
            }

            return fillMaterial;
        }
    },

    exportMaterialsJSON: {
        value: function() {
            var jObj;
            if (this._materialArray && this._materialNodeArray && this.getWorld().isWebGL()) {
                var nMats = this._materialArray.length;
                if (nMats > 0) {
                    var arr = [];

                    for (var i = 0; i < nMats; i++) {
                        var matObj =
                        {
                            'materialNodeName':this._materialNodeArray[i].name,
                            'material':this._materialArray[i].exportJSON(),
                            'type':this._materialTypeArray[i]
                        }
                        arr.push(matObj);
                    }

                    jObj =
                    {
                        'nMaterials':nMats,
                        'materials':arr
                    };
                }
            }

            return jObj;
        }
    },

    importMaterialsJSON: {
        value: function(jObj) {
            this._materialArray = [];
            this._materialTypeArray = [];

            if (!jObj)  return;

            var nMaterials = jObj.nMaterials;
            var matArray = jObj.materials;
            for (var i = 0; i < nMaterials; i++) {
                var mat;
                var matObj = matArray[i].material;
                var shaderName = matObj.material;
                switch (shaderName) {
                    case "flat":
                    case "radialGradient":
                    case "linearGradient":
                    case "bumpMetal":
                    case "uber":
                    case "plasma":
                    case "deform":
                    case "water":
                    case "paris":
                    case "raiders":
                    case "tunnel":
                    case "reliefTunnel":
                    case "squareTunnel":
                    case "twist":
                    case "fly":
                    case "julia":
                    case "mandel":
                    case "star":
                    case "zinvert":
                    case "keleidoscope":
                    case "radialBlur":
                    case "pulse":
                        mat = MaterialsModel.getMaterialByShader(shaderName);
                        if (mat)  mat = mat.dup();
                        break;

                    default:
                        console.log("material type: " + shaderName + " is not supported");
                        break;
                }

                if (mat) {
                    mat.importJSON(matObj);
                    this._materialArray.push(mat);
                    this._materialTypeArray.push(matObj.type);
                    var type = matArray[i].type;
                    if (type == "fill")  this._fillMaterial = mat;
                    else  this._strokeMaterial = mat;
                }
            }
        }
    },

    translate: {
        value: function(v) {
            var mat = Matrix.Translation(v);
            //var mat2 = mat.multiply( this._matrix );
            //this._matrix = mat2;
            glmat4.multiply(mat, this._matrix, this._matrix);
        }
    },

    transform: {
        value: function(mat) {
            if (mat) {
                //this._matrix = mat.multiply( this._matrix );
                glmat4.multiply(mat, this._matrix, this._matrix);
            }
        }
    },

    setMatrix: {
        value: function(mat) {
            var gl = this.getWorld().getGLContext();
            if (gl) {
                gl.uniformMatrix4fv(this.getWorld().getShaderProgram().mvMatrixUniform, false, new Float32Array(mat));
            }
        }
    },

    getGLCenter: {
        value: function() {
            // get the normalized device coordinates (NDC) for
            // all position and dimensions.
            var world = this.getWorld();
            var	vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var	xNDC = 2*this._xOffset/vpw,  yNDC = -2*this._yOffset/vph;

            var aspect = world.getAspect();
            var zn = world.getZNear(),  zf = world.getZFar();
            var	t = zn * Math.tan(world.getFOV() * Math.PI / 360.0),
                b = -t,
                r = aspect*t,
                l = -r;

            // calculate the object coordinates from their NDC coordinates
            var z = -world.getViewDistance();

            // unproject to get the position of the origin in GL
            var x = -z*(r-l)/(2.0*zn)*xNDC,
                y = -z*(t-b)/(2.0*zn)*yNDC;
            z = 0.0;

            // transform by the object's transformation matrix
            var ctr = MathUtils.transformPoint( [x, y, z], this.getMatrix() );

            return ctr;
        }
    },

    preViewToGL: {
        value: function(preViewPt) {
            // get the normalized device coordinates (NDC) for
            // all position and dimensions.
            var world = this.getWorld();
            var	vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var	xNDC = 2*preViewPt[0]/vpw,  yNDC = -2*preViewPt[1]/vph;

            var aspect = world.getAspect();
            var zn = world.getZNear(),  zf = world.getZFar();
            var	t = zn * Math.tan(world.getFOV() * Math.PI / 360.0),
                b = -t,
                r = aspect*t,
                l = -r;

            // calculate the object coordinates from their NDC coordinates
            var z = -world.getViewDistance();

            // unproject to get the position of the origin in GL
            var x = -z*(r-l)/(2.0*zn)*xNDC,
                y = -z*(t-b)/(2.0*zn)*yNDC;
            z = 0.0;

            // transform by the object's transformation matrix
            var glPt = MathUtils.transformPoint( [x, y, z], this.getMatrix() );

            return glPt;
        }
    },

    buildBuffers: {
        value: function() {
            // this function must be overridden by the base class
            alert("GLGeomObj.buildBuffers must be overridden by base class");
        }
    },

    render: {
        value: function() {
            alert("GLGeomObj.render method must be overridden by sub class");
        }
    },

    collidesWithPoint: {
        value: function(x, y) {
            alert("GLGeomObj.collidesWithPoint method must be overridden by sub class");
        }
    },

    getNearPoint: {
        value: function(pt, dir) {
            // the alert is not displayed.  Objects may choose not to implement this method.
            //alert( "GLGeomObj.getNearPoint method must be overridden by sub class" );
        }
    },

    getNearVertex: {
        value: function(pt, dir) {
            // this should be overridden by objects (such as rectangles) that have corners
        }
    },

    containsPoint: {
        value: function(pt, dir) {
            // the alert is not displayed.  Objects may choose not to implement this method.
            //alert( "GLGeomObj.containsPoint method must be overridden by sub class" );
        }
    },

    getPropertyFromString: {
        value: function(prop, str) {
            var index = str.indexOf(prop);
            if (index < 0)  throw new Error("property " + prop + " not found in string: " + str);

            var rtnStr = str.substr(index + prop.length);
            index = rtnStr.indexOf("\n");
            if (index >= 0) {
                rtnStr = rtnStr.substr(0, index);
            }

            return rtnStr;
        }
    },

    // Gradient stops for rgba(255,0,0,1) at 0%; rgba(0,255,0,1) at 33%; rgba(0,0,255,1) at 100% will return
    // 255,0,0,1@0;0,255,0,1@33;0,0,255,1@100
    gradientToString: {
        value: function(colors) {
            var rtnStr = "";
            if (colors && colors.length) {
                var c = colors[0],
                    len = colors.length;

                rtnStr += String(c.value.r + "," + c.value.g + "," + c.value.b + "," + c.value.a + "@" + c.position);
                for (var i = 1; i < len; i++) {
                    c = colors[i];
                    rtnStr += ";" + String(c.value.r + "," + c.value.g + "," + c.value.b + "," + c.value.a + "@" + c.position);
                }
            }
            return rtnStr;
        }
    },

    // Given a gradientStr "255,0,0,1@0;0,255,0,1@33;0,0,255,1@100" will return:
    // colors array [{position:0, value:{r:255, g:0, b:0, a:1}},
    //               {position:33, value:{r:0, g:255, b:0, a:1}},
    //               {position:100, value:{r:0, g:0, b:255, a:1}}
    //             ]
    stringToGradient: {
        value: function(gradientStr) {
            var rtnArr = [];

            var i,
                len,
                stops,
                stop,
                c;

            stops = gradientStr.split(";");
            len = stops.length;
            for (i = 0; i < len; i++) {
                stop = stops[i].split("@");
                c = stop[0].split(",");
                rtnArr.push({ position:Number(stop[1]), value:{r:Number(c[0]), g:Number(c[1]), b:Number(c[2]), a:Number(c[3])} });
            }

            return rtnArr;
        }
    }

    /*
     this.export = function() {
     var rtnStr;
     return rtnStr;
     }
     */
});