;(function () {
    "use strict";

    var glowscript = { version: "2.2" }

    // GlowScript uses lots of javascript properties (Object.defineProperty) with getters and setters
    // This is an attempt to create a more declarative syntax for declaring an object with lots of properties

    // This file also exports a function Export() which is used by all other glowscript modules to export their
    // symbols.  At present the symbols go both into window and window.glowscript objects.

    /* Example:
    function myClass() {}
    property.declare( myClass.prototype, {
    // By default, properties are writable, but are forced to have the same type they have initially
    // Each one gets a corresponding data property (e.g. __name) that should be used with great care
    x:0, y:0, z:0,
    name: "Me",

    // Properties starting with __ are non-enumerable and not typechecked
    __hidden = 57,

    // Derived properties can be read-only or read-write
    length: { get: function() { return this.x+this.y+this.z } },
    x_plus_one: { get: function() { return this.x+1 }, set: function(value) { this.x = value-1 } ),

    // onchanged lets a property work as normal (including typechecking) but trigger a function after being modified
    title: { value: "Mr.", onchanged: function() { console.log("Now you are " + this.title + " " + this.name + "!") } },

    // You can make a property read only by setting readonly to true.  It can be changed via __whatever
    salary: { value: 0, readonly: true },

    // You can also turn off typechecking by setting type to null, or force a particular type check by setting it
    anything: { value: null, type: null },
    number: { value: 0, type: Number },
    short: { value: "", type: function(x) { if (typeof x !== "string" || x.length>8) throw new Error("Not a string"); return x; } }
    } )
    */

    var property = {
        declare: function (proto, propertyMap) {
            $.each(propertyMap, function (name, definition) {
                if (definition === null || (definition.value===undefined && !definition.get))
                    definition = { value: definition }
                definition.name = name
                var internal = definition.internal || name.substr(0, 2) === "__"
                if (definition.enumerable === undefined)
                    definition.enumerable = !internal
                if (definition.type === undefined && !definition.get && !internal) {
                    var tp = typeof definition.value
                    if (tp === "number")
                        definition.type = property.check_number
                    else if (tp === "string")
                        definition.type = property.check_string
                    else if (definition.value instanceof attributeVector)
                        definition.type = property.check_attributeVector
                    else if (definition.value instanceof attributeVectorPos)
                        definition.type = property.check_attributeVectorPos
                    else if (definition.value instanceof attributeVectorAxis)
                        definition.type = property.check_attributeVectorAxis
                    else if (definition.value instanceof attributeVectorSize)
                        definition.type = property.check_attributeVectorSize
                    else if (definition.value instanceof vec)
                        definition.type = property.check_vec
                }

                if ((definition.readonly && definition.set) ||
                    (definition.onchanged && definition.set) ||
                    (definition.value !== undefined && definition.get))
                    throw new Error("Erroneous property definition '" + name + "'.")

                function readOnlyError() { throw new Error("Property '" + name + "' is read-only.") }

                //console.log("property", proto.constructor.name, name, definition, proto)

                if (definition.get) {
                    Object.defineProperty(proto, name, { enumerable: definition.enumerable, get: definition.get, set: definition.set || readOnlyError })
                } else {
                    // If no typechecking, we just define a plain jane data descriptor
                    if (!definition.type && !definition.onchanged)
                        Object.defineProperty(proto, name, { enumerable: definition.enumerable, writable: !definition.readonly, value: definition.value })
                    else{ 
                        var internal = "__" + name
                        var prop = {
                            enumerable: definition.enumerable,
                            get: function () { return this[internal] }
                        }

                        if (definition.set)
                            prop.set = definition.set
                        else if (definition.onchanged && definition.type)
                            prop.set = function (val) { var old = this[internal]; this[internal] = definition.type.call(this, val, definition); definition.onchanged.call(this, old) }
                        else if (definition.onchanged)
                            prop.set = function (val) { var old = this[internal]; this[internal] = val; definition.onchanged.call(this, old) }
                        else if (definition.type)
                            prop.set = function (val) { this[internal] = definition.type.call(this, val, definition) }
                        /*else
                        prop.set = function (val) { this[internal] = val; }*/

                        Object.defineProperty(proto, internal, { enumerable: false, writable: true, value: definition.value })
                        Object.defineProperty(proto, name, prop)
                    }
                }
            })
        },
        nullable_attributeVector: function nullable_attributeVector(v, def) {
            if (v === null) return null
            return property.check_attributeVector.call(this, v, def)
        },
        check_attributeVector: function check_attributeVector(v, def) {
            if (!(v instanceof vec)) throw new Error("Property '" + def.name + "' must be a vector.");
            return new attributeVector( this, v.x, v.y, v.z )
        },
        check_attributeVectorPos: function check_attributeVectorPos(v, def) {
            if (!(v instanceof vec)) throw new Error("Property '" + def.name + "' must be a vector.");
            return new attributeVectorPos( this, v.x, v.y, v.z )
        },
        check_attributeVectorAxis: function check_attributeVectorAxis(v, def) {
            if (!(v instanceof vec)) throw new Error("Property '" + def.name + "' must be a vector.");
            return new attributeVectorAxis( this, v.x, v.y, v.z )
        },
        check_attributeVectorSize: function check_attributeVectorSize(v, def) {
            if (!(v instanceof vec)) throw new Error("Property '" + def.name + "' must be a vector.");
            return new attributeVectorSize( this, v.x, v.y, v.z )
        },
        check_vec: function check_vec(v, def) { if (!v instanceof vec) throw new Error("Property '" + def.name + "' must be a vector."); return v; },
        check_number: function check_number(v, def) { return v; },
        check_string: function check_string(v, def) { return v; },
    }

    var global = window
    function Export( exports ) {
        for(var id in exports) {
            glowscript[id] = exports[id]
            global[id] = exports[id]
        }
    }

    var module_exports = {
        glowscript: glowscript,
        property: property,
        Export: Export
    }
    Export(module_exports)
})()