// Generated by CoffeeScript 1.7.1
(function() {
  var Pointer, VoidPointer, utils;

  utils = require('./utils');

  Pointer = (function() {
    function Pointer(offsetType, type, options) {
      var _base, _base1, _base2, _base3;
      this.offsetType = offsetType;
      this.type = type;
      this.options = options != null ? options : {};
      if (this.type === 'void') {
        this.type = null;
      }
      if ((_base = this.options).type == null) {
        _base.type = 'local';
      }
      if ((_base1 = this.options).allowNull == null) {
        _base1.allowNull = true;
      }
      if ((_base2 = this.options).nullValue == null) {
        _base2.nullValue = 0;
      }
      if ((_base3 = this.options).lazy == null) {
        _base3.lazy = false;
      }
      if (this.options.relativeTo) {
        this.relativeToGetter = new Function('ctx', "return ctx." + this.options.relativeTo);
      }
    }

    Pointer.prototype.decode = function(stream, ctx) {
      var c, decodeValue, offset, ptr, relative, val;
      offset = this.offsetType.decode(stream, ctx);
      if (offset === this.options.nullValue && this.options.allowNull) {
        return null;
      }
      relative = (function() {
        switch (this.options.type) {
          case 'local':
            return ctx._startOffset;
          case 'immediate':
            return stream.pos - this.offsetType.size();
          case 'parent':
            return ctx.parent._startOffset;
          default:
            c = ctx;
            while (c.parent) {
              c = c.parent;
            }
            return c._startOffset || 0;
        }
      }).call(this);
      if (this.options.relativeTo) {
        relative += this.relativeToGetter(ctx);
      }
      ptr = offset + relative;
      if (this.type != null) {
        val = null;
        decodeValue = (function(_this) {
          return function() {
            var pos;
            if (val != null) {
              return val;
            }
            pos = stream.pos;
            stream.pos = ptr;
            val = _this.type.decode(stream, ctx);
            stream.pos = pos;
            return val;
          };
        })(this);
        if (this.options.lazy) {
          return new utils.PropertyDescriptor({
            get: decodeValue
          });
        }
        return decodeValue();
      } else {
        return ptr;
      }
    };

    Pointer.prototype.size = function(val, ctx) {
      var parent, type;
      parent = ctx;
      switch (this.options.type) {
        case 'local':
        case 'immediate':
          break;
        case 'parent':
          ctx = ctx.parent;
          break;
        default:
          while (ctx.parent) {
            ctx = ctx.parent;
          }
      }
      type = this.type;
      if (type == null) {
        if (!(val instanceof VoidPointer)) {
          throw new Error("Must be a VoidPointer");
        }
        type = val.type;
        val = val.value;
      }
      if (val && ctx) {
        ctx.pointerSize += type.size(val, parent);
      }
      return this.offsetType.size();
    };

    Pointer.prototype.encode = function(stream, val, ctx) {
      var parent, relative, type;
      parent = ctx;
      if (val == null) {
        this.offsetType.encode(stream, this.options.nullValue);
        return;
      }
      switch (this.options.type) {
        case 'local':
          relative = ctx.startOffset;
          break;
        case 'immediate':
          relative = stream.pos + this.offsetType.size(val, parent);
          break;
        case 'parent':
          ctx = ctx.parent;
          relative = ctx.startOffset;
          break;
        default:
          relative = 0;
          while (ctx.parent) {
            ctx = ctx.parent;
          }
      }
      if (this.options.relativeTo) {
        relative += this.relativeToGetter(parent.val);
      }
      this.offsetType.encode(stream, ctx.pointerOffset - relative);
      type = this.type;
      if (type == null) {
        if (!(val instanceof VoidPointer)) {
          throw new Error("Must be a VoidPointer");
        }
        type = val.type;
        val = val.value;
      }
      ctx.pointers.push({
        type: type,
        val: val,
        parent: parent
      });
      return ctx.pointerOffset += type.size(val, parent);
    };

    return Pointer;

  })();

  VoidPointer = (function() {
    function VoidPointer(type, value) {
      this.type = type;
      this.value = value;
    }

    return VoidPointer;

  })();

  exports.Pointer = Pointer;

  exports.VoidPointer = VoidPointer;

}).call(this);
