
Object.prototype.hasOwnProperty = function(property) {
    return typeof(this[property]) !== 'undefined'
};

function loadBodyFromRUBE(bodyJso, world) {
    //console.log(bodyJso);
    
    if ( ! bodyJso.hasOwnProperty('type') ) {
        console.log("Body does not have a 'type' property");
        return null;
    }    
    
    var bd = new b2BodyDef();
    if ( bodyJso.type == 2 )
        bd.type = b2_dynamicBody;
    else if ( bodyJso.type == 1 )
        bd.type = b2_kinematicBody;
    if ( bodyJso.hasOwnProperty('angle') )
        bd.angle = bodyJso.angle;
    if ( bodyJso.hasOwnProperty('angularVelocity') )
        bd.angularVelocity = bodyJso.angularVelocity;
    if ( bodyJso.hasOwnProperty('active') )
        bd.awake = bodyJso.active;        
    if ( bodyJso.hasOwnProperty('fixedRotation') )
        bd.fixedRotation = bodyJso.fixedRotation;
    if ( bodyJso.hasOwnProperty('linearVelocity') && bodyJso.linearVelocity instanceof Object )
        bd.linearVelocity.SetV( bodyJso.linearVelocity );
    if ( bodyJso.hasOwnProperty('position') && bodyJso.position instanceof Object )
        bd.position.SetV( bodyJso.position );
    if ( bodyJso.hasOwnProperty('awake') )
        bd.awake = bodyJso.awake;
    else
        bd.awake = false;
    var body = world.CreateBody(bd);
    if ( bodyJso.hasOwnProperty('fixture') ) {
        for (k = 0; k < bodyJso['fixture'].length; k++) {
            var fixtureJso = bodyJso['fixture'][k];
            loadFixtureFromRUBE(body, fixtureJso);
        }
    }
    if ( bodyJso.hasOwnProperty('name') )
        body.name = bodyJso.name;
    if ( bodyJso.hasOwnProperty('customProperties') )
        body.customProperties = bodyJso.customProperties;
    return body;
}

function loadFixtureFromRUBE(body, fixtureJso) {    
    //console.log(fixtureJso);
    var fd = new b2FixtureDef();
    if (fixtureJso.hasOwnProperty('friction'))
        fd.friction = fixtureJso.friction;
    if (fixtureJso.hasOwnProperty('density'))
        fd.density = fixtureJso.density;
    if (fixtureJso.hasOwnProperty('restitution'))
        fd.restitution = fixtureJso.restitution;
    if (fixtureJso.hasOwnProperty('sensor'))
        fd.isSensor = fixtureJso.sensor;
    if ( fixtureJso.hasOwnProperty('filter-categoryBits') )
        fd.filter.categoryBits = fixtureJso['filter-categoryBits'];
    if ( fixtureJso.hasOwnProperty('filter-maskBits') )
        fd.filter.maskBits = fixtureJso['filter-maskBits'];
    if ( fixtureJso.hasOwnProperty('filter-groupIndex') )
        fd.filter.groupIndex = fixtureJso['filter-groupIndex'];
    if (fixtureJso.hasOwnProperty('circle')) {
        fd.shape = new b2CircleShape();
        fd.shape.m_radius = fixtureJso.circle.radius;
        if ( fixtureJso.circle.center )
            fd.shape.m_p.SetV(fixtureJso.circle.center);
        var fixture = body.CreateFixture(fd);        
        if ( fixtureJso.name )
            fixture.name = fixtureJso.name;
    }
    else if (fixtureJso.hasOwnProperty('polygon')) {
        fd.shape = new b2PolygonShape();
        var verts = [];
        for (v = 0; v < fixtureJso.polygon.vertices.x.length; v++) 
           verts.push( new b2Vec2( fixtureJso.polygon.vertices.x[v], fixtureJso.polygon.vertices.y[v] ) );
        fd.shape.SetAsArray(verts, verts.length);
        var fixture = body.CreateFixture(fd);        
        if ( fixture && fixtureJso.name )
            fixture.name = fixtureJso.name;
    }
    else if (fixtureJso.hasOwnProperty('chain')) {
        fd.shape = new b2PolygonShape();
        var lastVertex = new b2Vec2();
        for (v = 0; v < fixtureJso.chain.vertices.x.length; v++) {
            var thisVertex = new b2Vec2( fixtureJso.chain.vertices.x[v], fixtureJso.chain.vertices.y[v] );
            if ( v > 0 ) {
                fd.shape.SetAsEdge( lastVertex, thisVertex );
                var fixture = body.CreateFixture(fd);        
                if ( fixtureJso.name )
                    fixture.name = fixtureJso.name;
            }
            lastVertex = thisVertex;
        }
    }
    else {
        console.log("Could not find shape type for fixture");
    }
}

function getVectorValue(val) {
    if ( val instanceof Object )
        return val;
    else
        return { x:0, y:0 };
}

function loadJointCommonProperties(jd, jointJso, loadedBodies) {    
    jd.bodyA = loadedBodies[jointJso.bodyA];
    jd.bodyB = loadedBodies[jointJso.bodyB];
    jd.localAnchorA.SetV( getVectorValue(jointJso.anchorA) );
    jd.localAnchorB.SetV( getVectorValue(jointJso.anchorB) );
    if ( jointJso.collideConnected )
        jd.collideConnected = jointJso.collideConnected;
}

function loadJointFromRUBE(jointJso, world, loadedBodies)
{
    if ( ! jointJso.hasOwnProperty('type') ) {
        console.log("Joint does not have a 'type' property");
        return null;
    }    
    if ( jointJso.bodyA >= loadedBodies.length ) {
        console.log("Index for bodyA is invalid: " + jointJso.bodyA );
        return null;
    }    
    if ( jointJso.bodyB >= loadedBodies.length ) {
        console.log("Index for bodyB is invalid: " + jointJso.bodyB );
        return null;
    }
    
    var joint = null;
    if ( jointJso.type == "revolute" ) {
        var jd = new b2RevoluteJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);
        if ( jointJso.hasOwnProperty('refAngle') )
            jd.referenceAngle = jointJso.refAngle;
        if ( jointJso.hasOwnProperty('lowerLimit') )
            jd.lowerAngle = jointJso.lowerLimit;
        if ( jointJso.hasOwnProperty('upperLimit') )
            jd.upperAngle = jointJso.upperLimit;
        if ( jointJso.hasOwnProperty('maxMotorTorque') )
            jd.maxMotorTorque = jointJso.maxMotorTorque;
        if ( jointJso.hasOwnProperty('motorSpeed') )
            jd.motorSpeed = jointJso.motorSpeed;
        if ( jointJso.hasOwnProperty('enableLimit') )
            jd.enableLimit = jointJso.enableLimit;
        if ( jointJso.hasOwnProperty('enableMotor') )
            jd.enableMotor = jointJso.enableMotor;
        joint = world.CreateJoint(jd);
    }
    else if ( jointJso.type == "distance" || jointJso.type == "rope" ) {
        if ( jointJso.type == "rope" )
            console.log("Replacing unsupported rope joint with distance joint!");
        var jd = new b2DistanceJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);
        if ( jointJso.hasOwnProperty('length') )
            jd.length = jointJso.length;
        if ( jointJso.hasOwnProperty('dampingRatio') )
            jd.dampingRatio = jointJso.dampingRatio;
        if ( jointJso.hasOwnProperty('frequency') )
            jd.frequencyHz = jointJso.frequency;
        joint = world.CreateJoint(jd);
    }
    else if ( jointJso.type == "prismatic" ) {
        var jd = new b2PrismaticJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);        
        if ( jointJso.hasOwnProperty('localAxisA') )
            jd.localAxisA.SetV( getVectorValue(jointJso.localAxisA) );         
        if ( jointJso.hasOwnProperty('refAngle') )
            jd.referenceAngle = jointJso.refAngle;
        if ( jointJso.hasOwnProperty('enableLimit') )
            jd.enableLimit = jointJso.enableLimit;
        if ( jointJso.hasOwnProperty('lowerLimit') )
            jd.lowerTranslation = jointJso.lowerLimit;
        if ( jointJso.hasOwnProperty('upperLimit') )
            jd.upperTranslation = jointJso.upperLimit;
        if ( jointJso.hasOwnProperty('enableMotor') )
            jd.enableMotor = jointJso.enableMotor;
        if ( jointJso.hasOwnProperty('maxMotorForce') )
            jd.maxMotorForce = jointJso.maxMotorForce;
        if ( jointJso.hasOwnProperty('motorSpeed') )
            jd.motorSpeed = jointJso.motorSpeed;            
        joint = world.CreateJoint(jd);
    }
    else if ( jointJso.type == "wheel" ) {
        //Make a fake wheel joint using a line joint and a distance joint.
        //Return the line joint because it has the linear motor controls.
        //Use ApplyTorque on the bodies to spin the wheel...
        
        var jd = new b2DistanceJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);
        jd.length = 0.0;
        if ( jointJso.hasOwnProperty('springDampingRatio') )
            jd.dampingRatio = jointJso.springDampingRatio;
        if ( jointJso.hasOwnProperty('springFrequency') )
            jd.frequencyHz = jointJso.springFrequency;
        world.CreateJoint(jd);
        
        jd = new b2LineJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);
        if ( jointJso.hasOwnProperty('localAxisA') )
            jd.localAxisA.SetV( getVectorValue(jointJso.localAxisA) );
            
        joint = world.CreateJoint(jd);
    }
    else if ( jointJso.type == "friction" ) {
        var jd = new b2FrictionJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);
        if ( jointJso.hasOwnProperty('maxForce') )
            jd.maxForce = jointJso.maxForce;
        if ( jointJso.hasOwnProperty('maxTorque') )
            jd.maxTorque = jointJso.maxTorque;
        joint = world.CreateJoint(jd);
    }
    else if ( jointJso.type == "weld" ) {
        var jd = new b2WeldJointDef();
        loadJointCommonProperties(jd, jointJso, loadedBodies);
        if ( jointJso.hasOwnProperty('referenceAngle') )
            jd.referenceAngle = jointJso.referenceAngle;
        joint = world.CreateJoint(jd);
    }
    else {
        console.log("Unsupported joint type: " + jointJso.type);
        console.log(jointJso);
    }
    if ( joint && jointJso.name )
        joint.name = jointJso.name;
    return joint;
}

function makeClone(obj) {
  var newObj = (obj instanceof Array) ? [] : {};
  for (var i in obj) {
    if (obj[i] && typeof obj[i] == "object") 
      newObj[i] = makeClone(obj[i]);
    else
        newObj[i] = obj[i];
  }
  return newObj;
};

function loadImageFromRUBE(imageJso, world, loadedBodies)
{
    var image = makeClone(imageJso);
    
    if ( image.hasOwnProperty('body') && image.body >= 0 )
        image.body = loadedBodies[image.body];//change index to the actual body
    else
        image.body = null;
                
    if ( ! image.hasOwnProperty('aspectScale') )
        image.aspectScale = 1;
        
    image.center = new b2Vec2();
    image.center.SetV( getVectorValue(imageJso.center) );
    
    return image;
}



//mainly just a convenience for the testbed - uses global 'world' variable
function loadSceneFromRUBE(worldJso) {
    return loadSceneIntoWorld(worldJso, world);
}

//load the scene into an already existing world variable
function loadSceneIntoWorld(worldJso, world) {
    var success = true;
    
    var loadedBodies = [];
    if ( worldJso.hasOwnProperty('body') ) {
        for (var i = 0; i < worldJso.body.length; i++) {
            var bodyJso = worldJso.body[i];
            var body = loadBodyFromRUBE(bodyJso, world);
            if ( body )
                loadedBodies.push( body );
            else
                success = false;
        }
    }
    
    var loadedJoints = [];
    if ( worldJso.hasOwnProperty('joint') ) {
        for (var i = 0; i < worldJso.joint.length; i++) {
            var jointJso = worldJso.joint[i];
            var joint = loadJointFromRUBE(jointJso, world, loadedBodies);
            if ( joint )
                loadedJoints.push( joint );
            //else
            //    success = false;
        }
    }
    
    var loadedImages = [];
    if ( worldJso.hasOwnProperty('image') ) {
        for (var i = 0; i < worldJso.image.length; i++) {
            var imageJso = worldJso.image[i];
            var image = loadImageFromRUBE(imageJso, world, loadedBodies);
            if ( image )
                loadedImages.push( image );
            else
                success = false;
        }        
        world.images = loadedImages;
    }
    
    return success;
}

//create a world variable and return it if loading succeeds
function loadWorldFromRUBE(worldJso) {
    var gravity = new b2Vec2(0,0);
    if ( worldJso.hasOwnProperty('gravity') && worldJso.gravity instanceof Object )
        gravity.SetV( worldJso.gravity );
    var world = new b2World( gravity );
    if ( ! loadSceneIntoWorld(worldJso, world) )
        return false;
    return world;
}

function getNamedBodies(world, name) {
    var bodies = [];
    for (b = world.m_bodyList; b; b = b.m_next) {
        if ( b.name == name )
            bodies.push(b);
    }
    return bodies;
}

function getNamedFixtures(world, name) {
    var fixtures = [];
    for (b = world.m_bodyList; b; b = b.m_next) {
        for (f = b.m_fixtureList; f; f = f.m_next) {
            if ( f.name == name )
                fixtures.push(f);
        }
    }
    return fixtures;
}

function getNamedJoints(world, name) {
    var joints = [];
    for (j = world.m_jointList; j; j = j.m_next) {
        if ( j.name == name )
            joints.push(j);
    }
    return joints;
}

function getNamedImages(world, name) {
    var images = [];
    for (i = 0; i < world.images.length; i++) {
        if ( world.images[i].name == name )
            images.push(world.images[i].name);
    }
    return images;
}

//custom properties
function getBodiesByCustomProperty(world, propertyType, propertyName, valueToMatch) {
    var bodies = [];
    for (b = world.m_bodyList; b; b = b.m_next) {
        if ( ! b.hasOwnProperty('customProperties') )
            continue;
        for (var i = 0; i < b.customProperties.length; i++) {
            if ( ! b.customProperties[i].hasOwnProperty("name") )
                continue;
            if ( ! b.customProperties[i].hasOwnProperty(propertyType) )
                continue;
            if ( b.customProperties[i].name == propertyName &&
                 b.customProperties[i][propertyType] == valueToMatch)
                bodies.push(b);
        }        
    }
    return bodies;
}

function hasCustomProperty(item, propertyType, propertyName) {
    if ( !item.hasOwnProperty('customProperties') )
        return false;
    for (var i = 0; i < item.customProperties.length; i++) {
        if ( ! item.customProperties[i].hasOwnProperty("name") )
            continue;
        if ( ! item.customProperties[i].hasOwnProperty(propertyType) )
            continue;
        return true;
    }
    return false;
}

function getCustomProperty(item, propertyType, propertyName, defaultValue) {
    if ( !item.hasOwnProperty('customProperties') )
        return defaultValue;
    for (var i = 0; i < item.customProperties.length; i++) {
        if ( ! item.customProperties[i].hasOwnProperty("name") )
            continue;
        if ( ! item.customProperties[i].hasOwnProperty(propertyType) )
            continue;
        if ( item.customProperties[i].name == propertyName )
            return item.customProperties[i][propertyType];
    }
    return defaultValue;
}











