// Generated by IcedCoffeeScript 1.3.3f
(function() {
  var RAttributeSchema, RModelSchema, RRegularPropertySchema, debug, types, _,
    __slice = [].slice,
    __hasProp = {}.hasOwnProperty;

  debug = require('debug')('reactive');

  types = require('./types');

  _ = require('underscore');

  RAttributeSchema = (function() {

    function RAttributeSchema(modelSchema, key, options) {
      var _ref, _ref1;
      this.modelSchema = modelSchema;
      this.key = key;
      this.type = types.resolve((_ref = options.type) != null ? _ref : 'any');
      this.computed = (_ref1 = options.computed) != null ? _ref1 : false;
      this["default"] = options["default"];
      this.computeFunc = this.modelSchema.modelClass.prototype["compute " + this.key];
      if (!!this.computeFunc !== !!this.computed) {
        if (this.computed) {
          throw new Error("Missing compute func for computed property " + this);
        }
      }
    }

    RAttributeSchema.prototype.toString = function() {
      return "" + this.modelSchema + ".attributes." + this.key;
    };

    RAttributeSchema.prototype.preSet = function(instance, value) {
      if (this.computed) {
        throw new Error("Cannot assign to a computed property " + this);
      }
      return this.type.coerce(value);
    };

    RAttributeSchema.prototype.initializeInstance = function(instance) {
      var _this = this;
      instance.attributes[this.key] = this._defaultValue();
      if (this.computeFunc) {
        return instance.pleasedo("compute " + this.key, function() {
          var newValue, oldValue;
          newValue = _this.computeFunc.call(instance);
          oldValue = instance.attributes[_this.key];
          if (newValue !== oldValue) {
            instance.attributes[_this.key] = newValue;
            return instance._changed(_this.key);
          }
        });
      }
    };

    RAttributeSchema.prototype._defaultValue = function() {
      switch (typeof this["default"]) {
        case 'function':
          return this["default"]();
        case 'undefined':
          return this.type.defaultValue();
        default:
          return this["default"];
      }
    };

    return RAttributeSchema;

  })();

  RRegularPropertySchema = (function() {

    function RRegularPropertySchema(modelSchema, key) {
      this.modelSchema = modelSchema;
      this.key = key;
      this.getter = null;
      this.setter = null;
    }

    RRegularPropertySchema.prototype.define = function(modelClass) {
      var descriptor;
      descriptor = {};
      if (this.getter) descriptor.get = this.getter;
      if (this.setter) descriptor.set = this.setter;
      return Object.defineProperty(modelClass.prototype, this.key, descriptor);
    };

    return RRegularPropertySchema;

  })();

  RModelSchema = (function() {

    function RModelSchema(universe, originalModelClass) {
      this.universe = universe;
      this.modelClass = this._createSingletonClass(originalModelClass);
      this.attributes = {};
      this.autoBlocks = [];
      this._handleMagicKeys(this.modelClass);
    }

    RModelSchema.prototype.toString = function() {
      return "" + this.modelClass.name + ".schemaObj";
    };

    RModelSchema.prototype.mixin = function() {
      var mixinClass, mixinClasses, _i, _len, _results;
      mixinClasses = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      debug("Extending model schema " + this.modelClass.name + " with mixin " + mixinClasses[0].name);
      _results = [];
      for (_i = 0, _len = mixinClasses.length; _i < _len; _i++) {
        mixinClass = mixinClasses[_i];
        this._extendModel(mixinClass);
        _results.push(this._handleMagicKeys(mixinClass));
      }
      return _results;
    };

    RModelSchema.prototype.create = function(options) {
      var result;
      result = new this.modelClass(options);
      debug("Created " + result);
      return result;
    };

    RModelSchema.prototype._createSingletonClass = function(modelClass) {
      var k, singletonClass, v;
      global.REACTIVE_CLASS_CREATION_HACK = modelClass;
      singletonClass = eval("(function(modelClass) { return function " + modelClass.name + "() { modelClass.apply(this, arguments); }; })(global.REACTIVE_CLASS_CREATION_HACK);");
      delete global.REACTIVE_CLASS_CREATION_HACK;
      singletonClass.isSingletonClass = true;
      for (k in modelClass) {
        if (!__hasProp.call(modelClass, k)) continue;
        v = modelClass[k];
        singletonClass[k] = v;
      }
      singletonClass.prototype = {
        constructor: singletonClass
      };
      singletonClass.prototype.__proto__ = modelClass.prototype;
      singletonClass.schemaObj = this;
      singletonClass.prototype.universe = this.universe;
      return singletonClass;
    };

    RModelSchema.prototype._extendModel = function(mixinClass) {
      var k, v, _ref, _results;
      for (k in mixinClass) {
        if (!__hasProp.call(mixinClass, k)) continue;
        v = mixinClass[k];
        if (this.modelClass.hasOwnProperty(k)) {
          throw new Error("Key " + (JSON.stringify(k)) + " is already defined on model " + this.modelClass.name + ", cannot redefine in mixin " + mixinClass.name);
        }
        this.modelClass[k] = v;
      }
      _ref = mixinClass.prototype;
      _results = [];
      for (k in _ref) {
        v = _ref[k];
        if (!(!(k === 'schema'))) continue;
        if (this.modelClass.prototype.hasOwnProperty(k)) {
          throw new Error("Prototype key " + (JSON.stringify(k)) + " is already defined on model " + this.modelClass.name + ", cannot redefine in mixin " + mixinClass.name);
        }
        _results.push(this.modelClass.prototype[k] = v);
      }
      return _results;
    };

    RModelSchema.prototype._handleMagicKeys = function(mixinClass) {
      var $, baseClass, c, data, key, mixinClasses, mixins, options, prop, propSchema, propSchemas, prototype, value, _i, _len, _ref, _ref1, _results;
      prototype = mixinClass.prototype;
      data = _.extend({}, prototype.schema);
      if (data._mixins) {
        mixins = data._mixins;
        delete data._mixins;
        debug("Found mixin metadata in " + mixinClass.name);
        for (_i = 0, _len = mixins.length; _i < _len; _i++) {
          _ref = mixins[_i], baseClass = _ref[0], mixinClasses = _ref[1];
          if (!Array.isArray(mixinClasses)) mixinClasses = [mixinClasses];
          debug(("  baseClass = " + baseClass.name + ", mixinClasses = ") + ((function() {
            var _j, _len1, _results;
            _results = [];
            for (_j = 0, _len1 = mixinClasses.length; _j < _len1; _j++) {
              c = mixinClasses[_j];
              _results.push(c.name);
            }
            return _results;
          })()).join(", "));
          (_ref1 = this.universe.modelSchema(baseClass)).mixin.apply(_ref1, mixinClasses);
        }
      }
      for (key in data) {
        options = data[key];
        this.attributes[key] = this._createAttribute(key, options);
      }
      propSchemas = {};
      for (key in prototype) {
        if ($ = key.match(/^automatically (.*)$/)) {
          value = prototype[key];
          this.autoBlocks.push([$[1], value]);
        } else if ($ = key.match(/^get (.*)$/)) {
          prop = $[1];
          (propSchemas[prop] || (propSchemas[prop] = new RRegularPropertySchema(this, prop))).getter = prototype[key];
        } else if ($ = key.match(/^set (.*)$/)) {
          prop = $[1];
          (propSchemas[prop] || (propSchemas[prop] = new RRegularPropertySchema(this, prop))).setter = prototype[key];
        }
      }
      _results = [];
      for (key in propSchemas) {
        if (!__hasProp.call(propSchemas, key)) continue;
        propSchema = propSchemas[key];
        _results.push(propSchema.define(this.modelClass));
      }
      return _results;
    };

    RModelSchema.prototype.initializeInstance = function(instance) {
      var attrSchema, func, key, name, _i, _len, _ref, _ref1, _ref2, _results;
      _ref = this.attributes;
      for (key in _ref) {
        if (!__hasProp.call(_ref, key)) continue;
        attrSchema = _ref[key];
        attrSchema.initializeInstance(instance);
      }
      _ref1 = this.autoBlocks;
      _results = [];
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        _ref2 = _ref1[_i], name = _ref2[0], func = _ref2[1];
        _results.push(instance.pleasedo(name, func.bind(instance)));
      }
      return _results;
    };

    RModelSchema.prototype._createAttribute = function(key, options) {
      debug("Defining attribute " + this.modelClass.name + "." + key);
      Object.defineProperty(this.modelClass.prototype, key, {
        enumerable: true,
        get: function() {
          return this.get(key);
        },
        set: function(value) {
          return this.set(key, value);
        }
      });
      return new RAttributeSchema(this, key, options);
    };

    return RModelSchema;

  })();

  module.exports = RModelSchema;

}).call(this);
