//   Copyright (c) Chris Houser, Sep 2008 - Jan 2009. All rights reserved.
//   The use and distribution terms for this software are covered by the
//   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
//   which can be found in the file epl-v10.html at the root of this distribution.
//   By using this software in any fashion, you are agreeing to be bound by
//   the terms of this license.
//   You must not remove this notice, or any other, from this software.

// Runtime support for code generated by tojs.clj

clojure = {
  JS: {
    global: this,
    merge: function( t, s ) {
      for( var i in s ) {
        t[ i ] = s[ i ];
      }
      return t;
    },
    Class: {
      classname: "clojure.JS.Class",
      hashCode: function() { return clojure.lang.Util.hash( this.classname ); },
      isAssignableFrom: function(base) { return base.classset[this.classname];},
      getSuperclass: function() { return this.extend || null; },
      getInterfaces: function() { return this.implement || null; }
    }
  },
  lang: {
    Namespace: function( n, m ) {
      this.name = n; // FIXME: this pollutes the namespace
      clojure.JS.merge( this, m || {} );
    }
  }
};

clojure.JS.Class.constructor = clojure.JS.Class;
clojure.JS.Class.classset = { Class: true };

if( ! clojure.JS.global["java"] ) {
  java = { lang: { String: {}, Character: {}, Class: {}, Number: {},
                   Boolean: {} },
           math: { BigDecimal: {} },
           util: { Collection: {}, Map: {}, Set: {}, regex: { Pattern: {} } } };
}

clojure = {
  JS: {
    merge: clojure.JS.merge,
    global: clojure.JS.global,
    Class: clojure.JS.Class,
    objectType: typeof {},
    functionType: typeof function(){},
    stringType: typeof "",
    numberType: typeof 0,
    variadic: function( arity, f ) {
      f.arity = arity;
      f.isVariadic = true;
      return f;
    },
    resolveVar: function( sym, ctxns ) {
      return ctxns[ sym ] || clojure[ sym ] || clojure.JS.global[ sym ];
    },
    def: function( ns, name, init ) {
      var v = new clojure.lang.Var( ns, name );
      ns["_var_" + name] = v;
      v.push( init );
      return v;
    },
    variadic_sentinel: {},
    rest_args: function( varflag, args, i ) {
      if( varflag === clojure.JS.variadic_sentinel )
        return args[ args.length - 1 ];
      return new clojure.lang.ArraySeq( null, args, i );
    },
    invoke: function( obj, methodname, args ) {
      return obj[ methodname ].apply( obj, args );
    },
    getOrRun: function( obj, prop ) {
      var val = obj[ prop ];
      if( typeof val === clojure.JS.functionType
          || (typeof val === clojure.JS.objectType && "apply" in val) )
      {
        return val.apply( obj, [] );
      }
      return val;
    },
    lit_list: function( a ) {
      if( a.length > 0 )
        return new clojure.lang.ArraySeq( null, a, 0 );
      return clojure.lang.PersistentList.EMPTY;
    },
    lit_vector: function( a ) {
      return clojure.lang.LazilyPersistentVector.createOwning( a );
    },
    implement: function( cls, name, extend, implement ) {
      clojure.JS.merge( cls, clojure.JS.Class );
      cls.classname = name;
      cls.extend = extend;
      cls.implement = implement;
      cls.classset = {};
      cls.classset[ name ] = true;
      if( implement ) {
        for( var i = 0; i < implement.length; ++i ) {
          if( ! implement[ i ] )
            throw "Can't implement null: " + name;
          clojure.JS.merge( cls.classset, implement[ i ].classset );
        }
      }
    },
    definterface: function( pkg, name, implement ) {
      var cls = pkg[ name ] = {};
      clojure.JS.implement( cls, name, null, implement );
      return cls;
    },
    defclass: function( pkg, name, opts ) {
      var cls = pkg[ name ] = opts.init || function() {};
      clojure.JS.implement( cls, name, opts.extend, opts.implement );
      if( 'extend' in opts ) {
        cls.prototype = new opts.extend;
        cls.prototype.constructor = cls;
        clojure.JS.merge( cls.classset, opts.extend.classset );
      }
      if( opts.statics ) { clojure.JS.merge( cls, opts.statics ); }
      if( opts.methods ) { clojure.JS.merge( cls.prototype, opts.methods ); }
      return cls;
    },
    instanceq: function( c, o ){
      if( o === null )
        return false;
      if( typeof o === clojure.JS.functionType && ! ("constructor" in o) )
        return false; // a Java class?
      if( o.constructor === c )
        return true;
      if( ! o.constructor.classset )
        return false; // builtin class that doesn't match?
      return o.constructor.classset[ c.classname ];
    },
    relayPrintMethod: function( jsclass, javaclass, ctor ) {
      var m = clojure.core.print_method;
      m.addMethod( jsclass, function(o,w) {
        return (clojure.core.get( m.methodTable, javaclass )
          .apply(null, [ctor ? ctor(o) : o, w]));
      });
    },
    bitcount: function(n){
      var rtn = 0;
      for( ; n; n >>= 1) {
        rtn += n & 1;
      }
      return rtn;
    },
    ObjSeq: {
      create: function( obj ) {
        var i, pairs = [];
        for( i in obj ) {
          pairs.push( [i, obj[i]] );
        }
        return clojure.lang.ArraySeq.create( pairs );
      }
    }
  },
  lang: {
    Namespace: clojure.lang.Namespace,
    Numbers: {
      isZero: function(x) { return x === 0; },
      isPos: function(x) { return x > 0; },
      isNeg: function(x) { return x < 0; },
      minus: function(x,y) { return y === undefined ? -x : x - y; },
      inc: function(x) { return x + 1; },
      dec: function(x) { return x - 1; },
      add: function(x,y) { return x + y; },
      multiply: function(x,y) { return x * y; },
      divide: function(x,y) { return x / y; },
      quotient: function(x,y) { return parseInt(x / y); },
      remainder: function(x,y) { return x % y; },
      equiv: function(x,y) { return x == y; },
      lt: function(x,y) { return x < y; },
      lte: function(x,y) { return x <= y; },
      gt: function(x,y) { return x > y; },
      gte: function(x,y) { return x >= y; },
      compare: function(x,y) { return (x<y) ? -1 :( (y<x) ? 1 : 0 ); },
      unchecked_inc: function(x) { return x + 1; }
    },
    Util: {
      hash: function(o){
        switch( o ) {
          case null:     return 0;
          case Array:    return 0x7A837A71;
          case Boolean:  return 0x7A837A72;
          case Function: return 0x7A837A73;
          case Number:   return 0x7A837A74;
          case Object:   return 0x7A837A75;
          case RegExp:   return 0x7A837A76;
          case String:   return 0x7A837A77;
          case java.lang.Boolean:
          case java.lang.Character:
          case java.lang.Class:
          case java.lang.Double:
          case java.lang.Integer:
          case java.lang.Number:
          case java.lang.String:
          case java.math.BigDecimal:
          case java.util.Collection:
          case java.util.Map:
          case java.util.Set:
          case java.util.regex.Pattern:
                         return 0x7A830001;
        }
        var i = 0, ret = o.length;
        switch( typeof o ) {
          case clojure.JS.stringType:
            // lousy hand-made string hash
            for( ; i < o.length; ++i ) {
              ret ^= o.charCodeAt(i) << ((i % 4) * 8);
            }
            return ret;
          case clojure.JS.numberType:
            return o & 0xffffffff;
        }
        switch( o.constructor ) {
          case Array:
            for( ; i < o.length; ++i ) {
              ret = clojure.lang.Util.hashCombine(
                  ret, clojure.lang.Util.hash( o[i] ) );
            }
            return ret;
        }
        if( o.hashCode )
          return o.hashCode();
        return 0x11111111;
      },
      hashCombine: function(seed, hash){
        return seed ^ (hash + 0x9e3779b9 + (seed << 6) + (seed >> 2));
      },
      equal: function(x,y) { return x == y; },
      equiv: function(x,y) {
        if( x == y )
          return true;
        if( x !== null ) {
          if( typeof x == clojure.JS.numberType )
            return clojure.lang.Numbers.equiv(x,y);
          else if( x.equiv && y.equiv )
            return x.equiv( y );
          else if( x.equals )
            return x.equals( y );
        }
        return false;
      },
      isInteger: function(x) { return typeof x == clojure.JS.numberType; }
    },
    RT: {
      EMPTY_ARRAY: [],
      conj: function( coll, x ) {
        if( coll === null )
          return new clojure.lang.PersistentList( null, x );
        return coll.cons( x );
      },
      seqToArray: function(s) {
        var i = 0, ret = new Array( clojure.count( s ) );
        for( ; s !== null; ++i, s = s.rest() )
          ret[ i ] = s.first();
        return ret;
      },
      intCast: function(i) {
        return parseInt(i);
      },
      makeStringBuilder: function(s) {
        return new clojure.JS.StringBuilder( s===undefined ? "" : s );
      },
      className: function(c) {
        if( "classname" in c )
          return c.classname;
        if( "name" in c )
          return c.name;
        var s = "" + c,
            m = /^\[JavaClass (.*)]$/.exec(s);
        if( m )
          return m[1];
        return s;
      },
      simpleClassName: function(c) {
        // FIXME: should generate simple name
        return clojure.lang.RT.className(c);
      }
    }
  }
};

clojure.core = new clojure.lang.Namespace("clojure.core",{
  in_ns: function(s) {
    var i, nsparts = s.getName().split('.'),
        base = clojure.JS.global;
    for( i = 0; i < nsparts.length; ++i ) {
      if( ! base[nsparts[i]] ) {
        base[nsparts[i]] = new clojure.lang.Namespace(nsparts[i]);
      }
      base = base[nsparts[i]];
    }
  },
  refer: function(s) {},
  load: function(s) {},
  seq: function(coll){
    if( coll === null ) return null;
    else if( coll.seq ) return coll.seq();
    //else if( coll.constructor === String )
    //  return clojure.lang.StringSeq.create(coll);
    else if( typeof coll.length == clojure.JS.numberType )
      return clojure.lang.ArraySeq.create(coll);
    else if( typeof coll === clojure.JS.objectType )
      return clojure.JS.ObjSeq.create(coll);
    throw ("Don't know how to create ISeq from: " +
        (typeof coll) + " " + coll.constructor.name);
  },
  apply: function( f ) {
    var i, newargs = [], s, eagercount,
        oldargs = arguments, arglen = oldargs.length;
    if( f.isVariadic ) {
      // lazy
      eagercount = Math.min( f.arity, arglen - 2 );
      for( i = 0; i < eagercount; ++i ) {
        newargs.push( oldargs[ i + 1 ] );
      }
      if( eagercount == f.arity ) {
        if( arglen - eagercount < 3 ) {
          newargs.push( clojure.core.seq( oldargs[ arglen - 1 ] ) );
        }
        else {
          newargs.push( clojure.core.concat(
                  new clojure.lang.ArraySeq(
                      null, oldargs, eagercount + 1, arglen - 1 ),
                  oldargs[ arglen - 1 ] ) );
        }
      }
      else {
        s = clojure.core.seq( oldargs[ arglen - 1 ] );
        for( ; s && newargs.length < f.arity; s = s.rest() ) {
          newargs.push( s.first() );
        }
        if( s )
          newargs.push( s );
      }
      return f.apply( clojure.JS.variadic_sentinel, newargs );
    }
    else {
      // non-lazy
      for( i = 1; i < arglen - 1; ++i ) {
        newargs.push( oldargs[ i ] );
      }
      for( s = oldargs[ arglen - 1]; s; s = s.rest()) {
        newargs.push( s.first() );
      }
      return f.apply( null, newargs );
    }
  },
  first: function(x) {
    if( x === null ) return null;
    if( x.first ) return x.first();
    var seq = clojure.core.seq( x );
    if( seq === null ) return null;
    return seq.first();
  },
  rest: function(x) {
    if( x === null ) return null;
    if( x.rest ) return x.rest();
    var seq = clojure.core.seq( x );
    if( seq === null ) return null;
    return seq.rest();
  },
  second: function(x) { return clojure.first(clojure.rest(x)); },
  cons: function( x, coll ) {
    var y = clojure.core.seq( coll );
    if( y === null )
      return new clojure.lang.PersistentList( null, x );
    return y.cons( x );
  },
  instance_QMARK_: function( c, o ) {
    return clojure.JS.instanceq( c, o );
  },
  class_QMARK_: function(o) { return clojure.JS.instanceq(clojure.JS.Class,o);},
  number_QMARK_: function(o) { return clojure.JS.instanceq( Number, o ); },
  string_QMARK_: function(o) { return clojure.JS.instanceq( String, o ); },
  integer_QMARK_: function(o) { return parseInt( o ) === o; },
  find: function(coll, key) {
    if( coll == null )
      return null;
    else if( coll.containsKey ) {
      if( coll.containsKey( key ) )
        return new clojure.lang.MapEntry( key, coll.get( key ) );
      return null;
    }
    return coll.entryAt( key );
  },
  get: function(coll, key, notFound ) {
    var usenull = notFound === undefined;
    if( coll === null )
      return usenull ? null : notFound;
    if( coll.valAt )
      return coll.valAt( key, notFound );
    if( coll.containsKey )
      return (usenull || coll.containsKey( key )) ? coll.get( key ) : notFound;
    if( coll.contains )
      return (usenull || coll.contains( key )) ? coll.get( key ) : notFound;
    return (usenull || key in coll) ? coll[ key ] : notFound;
  },
  nth: function(coll, n, notFound) {
    var usenull = (notFound === undefined), seq, i;
    if( coll === null )
      return usenull ? null : notFound;
    if( coll.nth )
      return usenull || n < coll.count() ? coll.nth(n) : notFound;
    if( coll.get )
      return usenull || n < coll.size() ? coll.get(n) : notFound;
    if( coll.seq ) {
      for( seq = coll.seq(), i = 0; i <= n && seq; seq = seq.rest() ) {
        if( i == n )
          return seq.first();
      }
      if( usenull )
        throw "IndexOutOfBoundsException";
      return notFound;
    }
    return usenull || n < coll.length ? coll[n] : notFound;
  },
  contains_QMARK_: function(coll, key) {
    if( coll === null )
      return false;
    if( coll.containsKey )
      return coll.containsKey( key ) ? true : false;
    if( coll.contains )
      return coll.contains( key ) ? true : false;
    return key in coll;
  },
  hash_map: function() {
    return clojure.lang.PersistentHashMap.create( arguments );
  },
  hash_set: function() {
    return clojure.lang.PersistentHashSet.create( arguments );
  },
  to_array: function(coll){
    if( coll === null )
      return clojure.lang.RT.EMPTY_ARRAY;
    if( coll.toArray )
      return coll.toArray();
    if( typeof coll === clojure.JS.stringType ) {
      var i = 0, rtn = new Array( coll.length );
      for( ; i < coll.length; ++i ) {
        rtn[i] = coll[i];
      }
      return rtn;
    }
    if( coll.constructor === Array ) {
      return coll.slice(0);
    }
    throw "Unable to convert: " + coll.constructor.classname + " to Array";
  },
  keyword: function(a,b) {
    if( b === undefined )
      return clojure.lang.Keyword.intern( null, a );
    return clojure.lang.Keyword.intern( a, b );
  },
  symbol: function(a,b) {
    if( b === undefined )
      return clojure.lang.Symbol.intern( null, a );
    return clojure.lang.Symbol.intern( a, b );
  },
  assoc: function( coll, key, val ) {
    if( coll === null )
      return new clojure.lang.PersistentArrayMap([key, val]);
    return coll.assoc( key, val );
  },
  count: function(x) {
    if( x === null ) return 0;
    if( x.count ) return x.count();
    if( x.length != undefined ) return x.length;
    throw ("count not supported on: " + (typeof x) + " " + x.constructor);
  },
  class_: function(o) {
    if( o === null || o === undefined )
      return null;
    if( typeof o === clojure.JS.functionType && ! ("constructor" in o) )
      return java.lang.Class;
    return o.constructor || typeof o;
  },
  import_: function() {
    // do nothing
  },
  identical_QMARK_: function( a, b ) {
    return a === b;
  },
  keys: function(coll) {
    return clojure.lang.APersistentMap.KeySeq.create(clojure.core.seq(coll));
  },
  vals: function(coll) {
    return clojure.lang.APersistentMap.ValSeq.create(clojure.core.seq(coll));
  }
});

clojure.JS.definterface( clojure.JS, "Collection" );

clojure.JS.defclass( clojure.JS, "StringBuilder", {
  init: function( x ) { this.a = [ x ]; },
  methods: {
    append: function( x ) { this.a.push( x ); return this; },
    toString: function() { return this.a.join(''); }
  }
});

clojure.JS.defclass( clojure.JS, "String", {
  init: function(s) {
    this.s = s;
    this.length = s.length;
  },
  methods: {
    charAt: function(x) { return this.s.charAt(x); },
    toString: function() { return this.s; }
  }
});

clojure.JS.definterface( clojure.lang, "IObj" );
clojure.JS.definterface( clojure.lang, "IMeta" );

clojure.JS.defclass( clojure.lang, "Obj", {
  implement: [ clojure.lang.IObj ],
  init: function(_meta) { this._meta = _meta; },
  methods: {
    meta: function() { return this._meta; }
  }
});

clojure.JS.definterface( clojure.lang, "IReduce" );

clojure.JS.definterface( clojure.lang, "IPersistentCollection" );

clojure.JS.definterface( clojure.lang, "ISeq",
    [clojure.lang.IPersistentCollection] );

clojure.JS.definterface( clojure.lang, "IndexedSeq",
    [clojure.lang.ISeq] );

clojure.JS.defclass( clojure.lang, "ASeq", {
  implement: [clojure.lang.ISeq],
  methods: {
    equals: function( obj ) {
      var s = this.seq(), ms = obj.seq();
      for( ; s !== null; s = s.rest(), ms = ms.rest() ) {
        if( ms === null || !clojure.lang.Util.equal( s.first(), ms.first() ))
          return false;
      }
      if( ms !== null )
        return false;
      return true;
    },
    hashCode: function() { throw "not yet implemented"; },
    count: function() {
      var i = 1, s = this.rest();
      for( ; s; s = s.rest() )
        i += 1;
      return i;
    },
    seq: function(){ return this; },
    cons: function(o){ return new clojure.lang.Cons( null, o, this ); },
    toArray: function(){ return clojure.lang.RT.seqToArray( this.seq() ); },
    containsAll: function(c){ throw "not yet implemented"; },
    size: function(){ return this.count(); },
    isEmpty: function(){ return this.count() == 0; },
    contains: function(c){ throw "not yet implemented"; }
  }
});

clojure.JS.defclass( clojure.lang, "Cons", {
  extend: clojure.lang.ASeq,
  init: function( _meta, _first, _rest ) {
    this._meta = _meta;
    this._first = _first;
    this._rest = _rest;
  },
  methods: {
    first: function(){ return this._first; },
    rest: function(){ return this._rest; },
    count: function(){ return 1 + clojure.count( this._rest ); },
    seq: function(){ return this; },
    withMeta: function(_meta){
      return new clojure.lang.Cons( _meta, this._first, this._rest );
    }
  }
});

clojure.JS.defclass( clojure.lang, "ArraySeq", {
  extend: clojure.lang.ASeq,
  implement: [clojure.lang.IndexedSeq, clojure.lang.IReduce],
  init: function( _meta, a, i, len ) {
    this._meta = _meta;
    this.a = a;
    this.i = i;
    this.len = (len === undefined) ? a.length : len;
  },
  statics: {
    create: function( a ) {
      if( a && a.length )
        return new clojure.lang.ArraySeq( null, a, 0 );
      return null;
    }
  },
  methods: {
    first: function() { return this.a[this.i]; },
    rest: function() {
      if( this.i + 1 < this.len )
        return new clojure.lang.ArraySeq(
            this._meta, this.a, this.i + 1, this.len);
      return null;
    },
    count: function() { return this.len - this.i; },
    index: function() { return this.i; },
    withMeta: function( _meta ) {
      return new clojure.lang.ArraySeq( _meta, this.array, this.i, this.len );
    },
    reduce: function( fn, start ) {
      var ret = (start===undefined) ? this.a[this.i] : fn(start,this.a[this.i]),
          x = this.i + 1;
      for( ; x < this.len; ++x ) {
        ret = fn( ret, this.a[x] );
      }
      return ret;
    },
    seq: function() { return this; }
  }
});


clojure.JS.defclass( clojure.lang, "LazyCons", {
  extend: clojure.lang.ASeq,
  init: function(f,_first,_rest) {
    this.f = f;
    this._first = _first === undefined ? clojure.lang.LazyCons.sentinel :_first;
    this._rest  = _rest  === undefined ? clojure.lang.LazyCons.sentinel :_rest;
  },
  statics: {
    sentinel: {}
  },
  methods: {
    first: function() {
      if( this._first === clojure.lang.LazyCons.sentinel )
        this._first = this.f();
      return this._first;
    },
    rest: function() {
      if( this._rest === clojure.lang.LazyCons.sentinel ) {
        if( this._first === clojure.lang.LazyCons.sentinel ) {
          this.first();
        }
        this._rest = clojure.core.seq( this.f(null) );
        this.f = null;
      }
      return this._rest;
    },
    withMeta: function(_meta) {
      if( _meta == this.meta() )
        return this;
      //force before copying
      this.rest();
      return new clojure.lang.LazyCons( _meta, this._first, this._rest );
    },
    seq: function() { return this; }
  }
});


clojure.JS.defclass( clojure.lang, "Var", {
  init: function( ns, name ) {
    this.ns = ns;
    this.name = name;
    this.stack = [];
  },
  statics: {
    stack: [],
    pushThreadBindings: function( m ) {
      var vars=[], bs = m.seq(), e;
      for( ; bs; bs = bs.rest()) {
        e = bs.first();
        vars.push( e.key() );
        e.key().push( e.val() );
      }
      clojure.lang.Var.stack.push( vars );
    },
    popThreadBindings: function() {
      var i = 0, vars = clojure.lang.Var.stack.pop();
      for( ; i < vars.length; ++i ) {
        vars[i].pop();
      }
    }
  },
  methods: {
    push: function( val ) {
      this.stack.push( val );
      this.ns[ this.name ] = val;
    },
    pop: function() {
      this.stack.pop();
      this.ns[ this.name ] = this.stack[ this.stack.length - 1 ];
    },
    set: function( val ) {
      this.stack.pop();
      this.push( val );
    },
    get: function() {
      return this.ns[ this.name ];
    },
    hasRoot: function() { return this.stack.length > 0; },
    toString: function() {
      if( this.ns !== null )
        return "#=(var " + this.ns.name + "/" + this.name + ")";
      return "#<Var: " + (this.name !== null ? this.name : "--unnamed--") + ">";
    },
    hashCode: function() {
      return clojure.lang.Util.hash( this.ns + "/" + this.name );
    }
  }
});

clojure.JS.definterface( clojure.lang, "IFn" );

clojure.JS.defclass( clojure.lang, "AFn", {
  extend: clojure.lang.Obj,
  implement: [clojure.lang.IFn],
  methods: {
    apply: function( obj, args ){
      return this.invoke.apply( this, args );
    }
  }
});

clojure.JS.definterface( clojure.lang, "IPersistentStack",
    [clojure.lang.IPersistentCollection] );

clojure.JS.definterface( clojure.lang, "Sequential" );

clojure.JS.definterface( clojure.lang, "Reversible" );

clojure.JS.definterface( clojure.lang, "Named" );

clojure.JS.defclass( clojure.lang, "Keyword", {
  extend: clojure.lang.AFn,
  implement: [clojure.lang.Named],
  init: function( ns, name ) {
    this._ns = ns;
    this._name = name;
  },
  statics: {
    table: {},
    intern: function( ns, name ) {
      var key = (ns || "") + "/" + name,
          obj = clojure.lang.Keyword.table[ key ];
      if( obj )
        return obj;
      return clojure.lang.Keyword.table[ key ] =
        new clojure.lang.Keyword( ns, name );
    }
  },
  methods: {
    toString: function() {
      return ":" + (this.ns ? this.ns+"/" : "") + this._name;
    },
    compareTo: function(o) {
      if( this == o )
        return 0;
      if( this._ns === null && o._ns !== null )
        return -1;
      if( this._ns !== null ) {
        if( o._ns === null )
          return 1;
        var nsc = clojure.JS.compare(this._ns, o._ns);
        if( nsc !== 0 )
          return nsc;
      }
      return clojure.JS.compare(this._name, o._name);
    },
    getNamespace: function() { return this._ns; },
    getName: function() { return this._name; },
    hashCode: function() {
      return clojure.lang.Util.hash( this._ns + "/" + this._name );
    },
    invoke: function(coll, notFound) { return clojure.core.get( coll,this,notFound);}
  }
});

clojure.JS.defclass( clojure.lang, "Ratio", {} );

clojure.JS.defclass( clojure.lang, "Symbol", {
  extend: clojure.lang.AFn,
  implement: [clojure.lang.Named],
  init: function( ns, name ) {
    this._ns = ns;
    this._name = name;
  },
  statics: {
    table: {},
    intern: function( ns, name ) {
      var key = (ns || "") + "/" + name,
          obj = clojure.lang.Symbol.table[ key ];
      if( obj )
        return obj;
      return clojure.lang.Symbol.table[ key ] =
        new clojure.lang.Symbol( ns, name );
    }
  },
  methods: {
    toString: function() {
      return (this.ns ? this.ns+"/" : "") + this._name;
    },
    compareTo: function(o) {
      if( this == o )
        return 0;
      if( this._ns === null && o._ns !== null )
        return -1;
      if( this._ns !== null ) {
        if( o._ns === null )
          return 1;
        var nsc = clojure.JS.compare(this._ns, o._ns);
        if( nsc !== 0 )
          return nsc;
      }
      return clojure.JS.compare(this._name, o._name);
    },
    getNamespace: function() { return this._ns; },
    getName: function() { return this._name; },
    hashCode: function() {
      return clojure.lang.Util.hash( this._ns + "/" + this._name );
    },
    invoke: function(coll, notFound) {
      return clojure.core.get( coll,this,notFound);
    }
  }
});


clojure.JS.definterface( clojure.lang, "IPersistentList",
    [clojure.lang.Sequential, clojure.lang.IPersistentStack] );

clojure.JS.defclass( clojure.lang, "EmptyList", {
  extend: clojure.lang.Obj,
  implement: [clojure.lang.IPersistentList, clojure.JS.Collection],
  init: function( _meta ) { this._meta = _meta; },
  methods: {
    cons: function(o) {
      return new clojure.lang.PersistentList( this.meta(), o );
    },
    empty: function() { return this; },
    withMeta: function(m) {
      if( m != this.meta() )
        return new clojure.lang.EmptyList( m );
      return this;
    },
    peek: function() { return null; },
    pop: function() { throw "Can't pop empty list"; },
    count: function() { return 0; },
    seq: function() { return null; },
    size: function() { return 0; },
    isEmpty: function() { return true; },
    contains: function() { return false; },
    toArray: function() { return clojure.lang.RT.EMPTY_ARRAY; },
    containsAll: function( coll ) { return coll.isEmpty(); }
  }
});

clojure.JS.definterface( clojure.lang, "IMapEntry" );

clojure.JS.definterface( clojure.lang, "Associative",
    [ clojure.lang.IPersistentCollection ] );

clojure.JS.definterface( clojure.lang, "IPersistentVector",
    [ clojure.lang.Associative, clojure.lang.Sequential,
      clojure.lang.IPersistentStack, clojure.lang.Reversible ]);

clojure.JS.defclass( clojure.lang, "AMapEntry", {
  implement: [ clojure.lang.IMapEntry, clojure.lang.IPersistentVector ],
  methods: {
    empty: function(){ return null; },
    equals: function(o){
      return clojure.lang.APersistentVector.doEquals(this,o);
    },
    hashCode: function(){ throw "not implemented yet"; },
    toString: function(){
      return this.key() + " " + this.val();
      var sw = new clojure.JS.StringWriter();
      clojure.lang.RT.print( this, sw );
      return sw.toString();
    },
    length: function(){ return 2; },
    nth: function(i){
      switch(i){
        case 0: return this.key();
        case 1: return this.val();
        default: throw "Index out of bounds";
      }
    },
    asVector: function(){
      return clojure.lang.LazilyPersistentVector.createOwning(
          this.key(), this.val() );
    },
    assocN: function(i,v){ return this.asVector().assocN(i,v); },
    count: function(){ return 2; },
    seq: function(){ return this.asVector().seq(); },
    cons: function(o){ return this.asVector().cons(o); },
    containsKey: function(k){ return this.asVector().containsKey(k); },
    entryAt: function(k){ return this.asVector().entryAt(k); },
    assoc: function(k,v){ return this.asVector().assoc(k,v); },
    valAt: function(k,notFound){ return this.asVector().valAt(k,notFound); },
    peek: function(){ return this.val(); },
    pop: function(){
      return clojure.lang.LazilyPersistentVector.createOwning( this.key() );
    },
    rseq: function(){ return this.asVector().rseq(); }
  }
});

clojure.JS.defclass( clojure.lang, "MapEntry", {
  extend: clojure.lang.AMapEntry,
  init: function(k,v){
    this._key = k;
    this._val = v;
  },
  methods: {
    key: function(){ return this._key; },
    val: function(){ return this._val; },
    getKey: function(){ return this._key; },
    getValue: function(){ return this._val; }
  }
});

clojure.JS.defclass( clojure.lang, "PersistentList", {
  extend: clojure.lang.ASeq,
  implement: [clojure.lang.IPersistentList, clojure.lang.IReduce],
  init: function( _meta, _first, _rest, _count ) {
    this._meta = _meta || null;
    this._first = _first;
    this._rest = _rest || null;
    this._count = _count || 1;
  },
  statics: {
    creator: clojure.JS.variadic(0,function(){
      var args = clojure.JS.rest_args(this,arguments,0),
          ret, i;
      if( clojure.JS.instanceq( clojure.lang.ArraySeq, args ) ) {
        ret = clojure.lang.PersistentList.EMPTY;
        for( i = args.a.length - 1; i >= 0; --i ) {
          ret = ret.cons( args.a[ i ] );
        }
        return ret;
      }
      throw "Not yet implemented: clojure.lang.PersistentList.creator with non-ArraySeq";
    }),
    EMPTY: new clojure.lang.EmptyList(null)
  },
  methods: {
    first: function(){ return this._first; },
    rest: function(){
      if( this._count == 1 )
        return null;
      return this._rest;
    },
    peek: function(){ return this.first; },
    pop: function(){
      if( this._rest === null )
        return this.empty();
      return this._rest;
    },
    count: function(){ return this._count; },
    cons: function(o){
      return new clojure.lang.PersistentList(
          this._meta, o, this, this._count + 1 );
    },
    empty: function(){
      return clojure.lang.PersistentList.EMPTY.withMeta( this._meta );
    },
    withMeta: function( _meta ){
      if( _meta != this._meta )
        return new clojure.lang.PersistentList(
            this._meta, this._first, this._rest, this._count );
      return this;
    },
    reduce: function( f, start ){
      var ret = (start === undefined) ? this.first() : f( start, this.first() ),
          s = this.rest();
      for( ; s !== null; s = s.rest() )
        ret = f( ret, s.first() );
      return ret;
    }
  }
});

clojure.JS.defclass( clojure.lang, "APersistentVector", {
  extend: clojure.lang.AFn,
  implement: [clojure.lang.IPersistentVector],
  init: function( _meta ) { this._meta = _meta; },
  methods: {
    meta: function() { return this._meta; },
    peek: function() {
      if( this.count() > 0 )
        return this.nth( this.count() - 1 );
      return null;
    },
    seq: function() {
      if( this.count() > 0 )
        return new clojure.lang.APersistentVector.Seq( null, this, 0 );
      return null;
    },
    rseq: function() {
      if( this.count() > 0 )
        return new clojure.lang.APersistentVector.RSeq( null, this, this.count() - 1);
      return null;
    },
    equals: function() { throw "not implemented yet"; },
    hashCode: function() { throw "not implemented yet"; },
    get: function(i) { return this.nth(i); },
    indexOf: function( o ){
      var i = 0, len = this.count();
      for( ; i < len; ++i )
        if( clojure.lang.Util.equal( this.nth( i ), o ) )
          return i;
      return -1;
    },
    lastIndexOf: function( o ){
      for( var i = this.count() - 1; i >= 0; --i )
        if( clojure.lang.Util.equal( this.nth( i ), o ) )
          return i;
      return -1;
    },
    subList: function( fromi, toi ) {
      return clojure.lang.RT.subvec( this, fromi, toi );
    },
    invoke: function( i ) {
      if( clojure.lang.Util.isInteger(i) )
        return this.nth( parseInt( i ) );
      throw "Key must be integer";
    },
    peek: function() {
      if( this.count() > 0 )
        return this.nth( this.count() - 1 );
      return null
    },
    constainsKey: function(k){
      if( ! clojure.lang.Util.isInteger( k ) )
        return false;
      var i = parseInt(k);
      return i >= 0 && i < this.count();
    },
    entryAt: function(k){
      if( clojure.lang.Util.isInteger( k ) ) {
        var i = parseInt(k);
        if( i >= 0 && i < this.count() )
          return new clojure.lang.MapEntry( k, this.nth(i) );
      }
      return null;
    },
    assoc: function(k,v){
      if( clojure.lang.Util.isInteger( k ) ) {
        var i = parseInt(k);
        return this.assocN(i,v);
      }
      throw "Key must be integer";
    },
    valAt: function(k, notFound){
      if( clojure.lang.Util.isInteger( k ) ) {
        var i = parseInt(k);
        if( i >= 0 && i < this.count() )
          return this.nth(i);
      }
      if( notFound === undefined )
        return null;
      return notFound;
    },
    toArray: function(){ return clojure.lang.RT.seqToArray( this.seq() ); },
    containsAll: function(){ throw "not implemented yet"; },
    size: function(){ return this.count(); },
    isEmpty: function(){ return this.count() === 0; },
    contains: function(o){
      for( var s = this.seq(); s !== null; s = s.rest() ) {
        if( clojure.lang.Util.equal( s.first(), o ) )
          return true;
      }
      return false;
    },
    length: function(){ return this.count(); },
    compareTo: function(v){
      var i, c, len = this.count();
      if( len < v.count() )
        return -1;
      else if( len > v.count() )
        return 1;
      for( i = 0; i < len; ++i ) {
        c = this.nth(i).compareTo( v.nth(i) );
        if( c != 0 )
          return c;
      }
      return 0;
    }
  }
});

clojure.JS.defclass( clojure.lang.APersistentVector, "Seq", {
  extend: clojure.lang.ASeq,
  implement: [clojure.lang.IndexedSeq, clojure.lang.IReduce],
  init: function( _meta, v, i){
    this._meta = _meta;
    this.v = v;
    this.i = i;
  },
  methods: {
    seq: function(){ return this; },
    first: function(){ return this.v.nth(this.i); },
    rest: function(){
      if( this.i + 1 < this.v.count() )
        return new clojure.lang.APersistentVector.Seq(
            this._meta, this.v, this.i + 1 );
      return null;
    },
    index: function(){ return this.i; },
    count: function(){ return this.v.count() - this.i; },
    withMeta: function(_meta){
      return new clojure.lang.APersistentVector.Seq( _meta, this.v, this.i );
    },
    reduce: function( fn, start ) {
      var ret = (start === undefined) ?
                this.v.nth(this.i) : fn(start,this.v.nth(this.i)),
          x = this.i + 1;
      for( ; x < this.count(); ++x ) {
        ret = fn( ret, this.v.nth(x) );
      }
      return ret;
    }
  }
});

clojure.JS.defclass( clojure.lang.APersistentVector, "RSeq", {
  init: function( _meta, v, i){
    this._meta = _meta;
    this.v = v;
    this.i = i;
  },
  methods: {
    seq: function(){ return this; },
    first: function(){ return this.v.nth(this.i); },
    rest: function(){
      if( this.i > 0 )
        return new clojure.lang.APersistentVector.RSeq( this._meta, this.v, this.i - 1 );
      return null;
    },
    index: function(){ return this.i; },
    count: function(){ return this.i + 1; },
    withMeta: function(_meta){
      return new clojure.lang.APersistentVector.RSeq( _meta, this.v, this.i );
    }
  }
});

clojure.JS.defclass( clojure.lang, "LazilyPersistentVector", {
  extend: clojure.lang.APersistentVector,
  init: function( _meta, ary, v ) {
    this._meta = _meta;
    this.ary = ary;
    this._v = v;
  },
  statics: {
    createOwning: function(ary) {
      if(ary.length === 0)
        return clojure.lang.PersistentVector.EMPTY;
      return new clojure.lang.LazilyPersistentVector( null, ary, null );
    },
    create: function(coll) {
      return clojure.lang.LazilyPersistentVector.createOwning(coll.toArray());
    }
  },
  methods: {
    toArray: function() { return this.ary; },
    nth: function(i) { return this.ary[i]; },
    assocN: function(i,val) { return this.v().assoc(i,val); },
    count: function() { return this.ary.length; },
    cons: function(o) { return this.v().cons(o); },
    empty: function() { return clojure.lang.PersistentVector.EMPTY.withMeta(this._meta); },
    pop: function() { return this.v().pop(); },
    v: function() {
      if( this._v === null )
        this._v = clojure.lang.PersistentVector.create(this.ary);
      return this._v;
    },
    withMeta: function(meta) {
      if( meta != this._meta )
        return new clojure.lang.LazilyPersistentVector( meta, this.ary, this.v );
      return this;
    }
  }
});

clojure.JS.defclass( clojure.lang, "PersistentVector", {
  extend: clojure.lang.APersistentVector,
  init: function( _meta, cnt, shift, root, tail ) {
    clojure.lang.APersistentVector.call( this, _meta );
    this.cnt = cnt;
    this.shift = shift;
    this.root = root;
    this.tail = tail;
  },
  statics: {
    create: function( items ) {
      var i = 0, ret = clojure.lang.PersistentVector.EMPTY;
      for( ; i < items.length; ++i ) {
        ret = ret.cons( items[ i ] );
      }
      return ret;
    }
  },
  methods: {
    tailoff: function() { return this.cnt - this.tail.length; },
    nth: function( i ) {
      if( i >= 0 && i < this.cnt ) {
        if( i >= this.tailoff() ) {
          return this.tail[ i & 0x01f ];
        }
        var arr = this.root,
            level = this.shift;
        for( ; level > 0; level -= 5 ) {
          arr = arr[ (i >>> level) & 0x01f ];
        }
        return arr[ i & 0x01f ];
      }
      throw "IndexOutOfBoundsException";
    },
    assocN: function( i, val ) {
      if( i >= 0 && i < this.cnt ) {
        if( i >= this.tailoff() ) {
          var newTail = this.tail.slice( 0 );
          newTail[ i & 0x01f ] = val;
          return new clojure.lang.PersistentVector(
              this.meta(), this.cnt, this.shift, this.root, newTail );
        }
        return new clojure.lang.PersistentVector(
            this.meta(), this.cnt, this.shift,
            this.doAssoc( this.shift, this.root, i, val), this.tail );
      }
      if( i == this.cnt ) {
        return this.cons( val );
      }
      throw "IndexOutOfBoundsException";
    },
    doAssoc: function( level, arr, i, val ) {
      var subidx, ret = arr.slice( 0 );
      if( level == 0 ) {
        ret[ i & 0x01f ] = val;
      }
      else {
        subidx = (i >>> level) & 0x01f;
        ret[ subidx ] = this.doAssoc( level - 5, arr[ subidx ], i, val );
      }
      return ret;
    },
    count: function() { return this.cnt; },
    withMeta: function( _meta ) {
      return new clojure.lang.PersistentVector(
          _meta, this.cnt, this.shift, this.root, this.tail );
    },
    cons: function( val ) {
      var newTail, expansion, newroot, newshift;
      if( this.tail.length < 32 ) {
        newTail = this.tail.concat( [val] );
        return new clojure.lang.PersistentVector(
            this.meta(), this.cnt + 1, this.shift, this.root, newTail );
      }
      expansion = [null];
      newroot = this.pushTail( this.shift-5, this.root, this.tail, expansion);
      newshift = this.shift;
      if( expansion[0] != null ) {
        newroot = [newroot, expansion[0]];
        newshift += 5;
      }
      return new clojure.lang.PersistentVector(
          this.meta(), this.cnt+1, newshift, newroot, [val] );
    },
    empty: function() {
      return clojure.lang.PersistentVector.EMPTY.withMeta( this.meta() );
    },
    pushTail: function( level, arr, tailNode, expansion)
    {
      var ret, newchild;
      if( level == 0 ) {
        newchild = tailNode;
      }
      else {
        newchild = this.pushTail(
            level - 5, arr[arr.length - 1], tailNode, expansion);
        if( expansion[0] == null ) {
          ret = arr.slice( 0 );
          ret[ arr.length - 1 ] = newchild;
          return ret;
        }
        else {
          newchild = expansion[0];
        }
      }
      //expansion
      if( arr.length == 32 ) {
        expansion[0] = [newchild];
        return arr;
      }
      expansion[0] = null;
      return arr.concat([newchild]);
    },
    pop: function() {
      if( this.cnt == 0 ) {
        throw "IllegalStateException: Can't pop empty vector";
      }
      if( this.cnt == 1 ) {
        return clojure.lang.PersistentVector.EMPTY.withMeta( this.meta() );
      }
      var newTail, ptail, newroot, newshift;
      if( this.tail.length > 1 ) {
        newTail = this.tail.slice( 0, this.tail.length - 1 );
        return new clojure.lang.PersistentVector(
            this.meta(), this.cnt - 1, this.shift, this.root, newTail );
      }
      ptail = [null];
      newroot = this.popTail( this.shift - 5, this.root, ptail );
      newshift = this.shift;
      if( newroot == null ) {
        newroot = clojure.lang.RT.EMPTY_ARRAY;
      }
      if( this.shift > 5 && newroot.length == 1 ) {
        newroot = newroot[0];
        newshift -= 5;
      }
      return new clojure.lang.PersistentVector(
          this.meta(), this.cnt - 1, newshift, newroot, ptail[0] );
    },
    popTail: function( shift, arr, ptail ) {
      if( shift > 0 ) {
        var newchild = this.popTail( shift - 5, arr[ arr.length - 1 ], ptail ),
            ret;
        if( newchild != null ) {
          ret = arr.slice( 0 );
          ret[ arr.length - 1 ] = newchild;
          return ret;
        }
      }
      if( shift == 0 ) {
        ptail[0] = arr[ arr.length - 1 ];
      }
      //contraction
      if( arr.length == 1 ) {
        return null;
      }
      return arr.slice( 0, arr.length - 1 );
    }
  }
});

clojure.lang.PersistentVector.EMPTY =
  new clojure.lang.PersistentVector(
      {}, 0, 5, clojure.lang.RT.EMPTY_ARRAY, clojure.lang.RT.EMPTY_ARRAY );

clojure.JS.definterface( clojure.lang, "IPersistentMap",
    [clojure.lang.Associative]);

clojure.JS.defclass( clojure.lang, "APersistentMap", {
  extend: clojure.lang.AFn,
  implement: [clojure.lang.IPersistentMap, clojure.JS.Collection],
  init: function(_meta) {
    this._meta = _meta;
    this._hash = -1;
  },
  methods: {
    cons: function(o){
      if( clojure.JS.instanceq( clojure.lang.IPersistentVector, o ) ) {
        if( o.count() != 2 )
          throw "Vector arg to map conj must be a pair";
        return this.assoc( o.nth(0), o.nth(1) );
      }
      var e, ret = this, es = clojure.core.seq( o );
      for( ; es; es = es.rest() ) {
        e = es.first();
        ret = ret.assoc( e.getKey(), e.getValue() );
      }
      return ret;
    },
    equals: function(m){
      if( ! clojure.JS.instanceq( clojure.lang.IPersistentMap, m ) )
        return false;
      if( m.count() != this.count() || m.hashCode() != this.hashCode() )
        return false;
      var e, me, s = this.seq();
      for( ; s; s = s.rest() ) {
        e = s.first();
        me = m.entryAt( e.getKey() );
        if( me === null
            || ! clojure.lang.Util.equal( e.getValue(), me.getValue() ))
        {
          return false;
        }
      }
      return true;
    },
    hashCode: function(){
      if( this._hash == -1 ) {
        var e, hash = this.count(),
            s = this.seq();
        for( ; s; s = s.rest() ) {
          e = s.first();
          hash ^= clojure.lang.Util.hashCombine(
              clojure.lang.Util.hash( e.getKey() ),
              clojure.lang.Util.hash( e.getValue() ) );
        }
        this._hash = hash;
      }
      return _hash;
    },
    containsAll: function(){ throw "not implemented yet"; },
    invoke: function(k,notFound){ return this.valAt(k,notFound); },
    toArray: function(){ return clojure.lang.RT.seqToArray( this.seq() ); },
    size: function(){ return this.count(); },
    isEmpty: function(){ return this.count() === 0; },
    contains: function(e){
      if( clojure.JS.instanceq( clojure.lang.MapEntry, e ) ) {
        var v = this.entryAt( e.getKey() );
        return (v!==null && clojure.lang.Util.equal(v.getValue(),e.getValue()));
      }
      return false;
    }
  }
});

clojure.JS.defclass( clojure.lang.APersistentMap, "KeySeq", {
  extend: clojure.lang.ASeq,
  init: function(_meta, _seq) {
    this._meta = _meta;
    this._seq = _seq;
  },
  statics: {
    create: function(seq){
      if(seq === null)
        return null;
      return new clojure.lang.APersistentMap.KeySeq(null,seq);
    }
  },
  methods: {
    first: function(){ return this._seq.first().getKey(); },
    rest: function(){
      return clojure.lang.APersistentMap.KeySeq.create( this._seq.rest() );
    },
    withMeta: function(_meta){
      return new clojure.lang.APersistentMap.KeySeq( _meta, this._seq );
    }
  }
});

clojure.JS.defclass( clojure.lang.APersistentMap, "ValSeq", {
  extend: clojure.lang.ASeq,
  init: function(_meta, _seq) {
    this._meta = _meta;
    this._seq = _seq;
  },
  statics: {
    create: function(seq){
      if(seq === null)
        return null;
      return new clojure.lang.APersistentMap.ValSeq(seq);
    }
  },
  methods: {
    first: function(){ return this._seq.first().getValue(); },
    rest: function(){
      return clojure.lang.APersistentMap.ValSeq.create( this._seq.rest() );
    },
    withMeta: function(_meta){
      return new clojure.lang.APersistentMap.ValSeq( _meta, this._seq );
    }
  }
});

clojure.JS.defclass( clojure.lang, "PersistentHashMap", {
  extend: clojure.lang.APersistentMap,
  init: function(_meta, _count, _root){
    this._meta = _meta;
    this._count = _count;
    this._root = _root;
  },
  statics: {
    create: function(init){
      var ret = clojure.lang.PersistentHashMap.EMPTY,
          s = clojure.core.seq( init );
      for( ; s; s=s.rest().rest() ){
        if( s.rest() === null )
          throw "No value supplied for key: " + s.first();
        ret = ret.assoc( s.first(), clojure.core.second( s ) );
      }
      return ret;
    },
    mask: function(hash, shift){ return (hash >>> shift) & 0x01f; }
  },
  methods:{
    containsKey: function(key){ return this.entryAt(key) !== null; },
    entryAt: function(k){ return this._root.find(clojure.lang.Util.hash(k),k);},
    assoc: function(k,v){
      var addedLeaf=[null],
          newroot = this._root.assoc(
             0, clojure.lang.Util.hash(k), k, v, addedLeaf );
      if( newroot == this._root )
        return this;
      return new clojure.lang.PersistentHashMap(
          this._meta, this._count + (addedLeaf[0] === null ? 0 : 1), newroot );
    },
    valAt: function(k, notFound){
      var e = this.entryAt(k);
      if( e !== null )
        return e.val();
      if( notFound === undefined )
        return null;
      return notFound;
    },
    assocEx: function(k,v){
      if(this.containsKey(k))
        throw "Key already present";
      return this.assoc(k,v);
    },
    without: function(k){
      var newroot = this._root.without( clojure.lang.Util.hash(k), k );
      if( newroot == this._root )
        return this;
      if( newroot == null )
        return this.empty();
      return new clojure.lang.PersistentHashMap(
          this._meta, this._count-1, newroot );
    },
    count: function(){ return this._count; },
    seq: function(){ return this._root.nodeSeq(); },
    empty: function(){
      return clojure.lang.PersistentHashMap.EMPTY.withMeta( this._meta );
    },
    withMeta: function(_meta){
      return new clojure.lang.PersistentHashMap( _meta, this._count,this._root);
    }
  }
});

clojure.JS.defclass( clojure.lang.PersistentHashMap, "EmptyNode", {
  methods: {
    assoc: function(shift, hash, key, val, addedLeaf){
      var ret = new clojure.lang.PersistentHashMap.LeafNode( hash, key, val );
      addedLeaf[0] = ret;
      return ret;
    },
    without: function(h,k){ return this; },
    find:    function(h,k){ return null; },
    nodeSeq: function(){ return null;},
    getHash: function(){ return 0;}
  }
});

clojure.lang.PersistentHashMap.EMPTY = new clojure.lang.PersistentHashMap(
    null, 0, new clojure.lang.PersistentHashMap.EmptyNode() );

clojure.JS.defclass( clojure.lang.PersistentHashMap, "FullNode", {
  init: function(nodes, shift){
    this._nodes = nodes;
    this._shift = shift;
    this._hash = nodes[0].getHash();
  },
  statics: {
    bitpos: function(hash, shift) {
      return 1 << clojure.lang.PersistentHashMap.mask( hash, shift );
    }
  },
  methods: {
    assoc: function( levelShift, hash, key, val, addedLeaf ) {
      var newnodes,
          PHM = clojure.lang.PersistentHashMap,
          idx = PHM.mask( hash, this._shift ),
          n = this._nodes[idx].assoc( this._shift+5, hash, key, val, addedLeaf);
      if( n == this._nodes[idx] )
        return this;
      else {
        newnodes = nodes.slice();
        newnodes[idx] = n;
        return new PHM.FullNode( newnodes, this._shift );
      }
    },
    without: function(hash, key){
      var newnodes,
          PHM = clojure.lang.PersistentHashMap,
          idx = PHM.mask( hash, this._shift ),
          n = this._nodes[idx].without( hash, key );
      if( n != this._nodes[idx] ) {
        newnodes = nodes.slice();
        if( n == null ) {
          nodes.splice( idx, 1 );
          return new PHM.BitmapIndexedNode(
              ~PHM.FullNode.bitpos(hash,this._shift), newnodes, this._shift );
        }
        newnodes[ idx ] = n;
        return new PHM.FullNode( newnodes, this._shift );
      }
      return this;
    },
    find: function(hash, key) {
      return (nodes[clojure.lang.PersistentHashMap.mask( hash, this._shift )]
        .find( hash, key ));
    },
    nodeSeq: function(){
      return clojure.lang.PersistentHashMap.FullNode.Seq.create( this, 0 );
    },
    getHash: function(){ return this._hash; }
  }
});

clojure.JS.defclass( clojure.lang.PersistentHashMap.FullNode, "Seq", {
  extend: clojure.lang.ASeq,
  init: function( _meta, s, i, node ) {
    this._meta = _meta;
    this.s = s;
    this.i = i;
    this.node = node;
  },
  statics: {
    create: function(node, i){
      if( i >= node.nodes.length )
        return null;
      return new clojure.lang.PersistentHashMap.FullNode.Seq(
          null, node.nodes[i].nodeSeq(), i, node );
    }
  },
  methods: {
    first: function(){ return this.s.first(); },
    rest: function(){
      var Seq = clojure.lang.PersistentHashMap.FullNode.Seq,
          nexts = this.s.rest();
      if( nexts )
        return new Seq( null, nexts, this.i, this.node );
      return Seq.create( node,this.i+1);
    },
    withMeta: function(_meta){
      return new clojure.lang.PersistentHashMap.FullNode.Seq(
          _meta, this.s, this.i, this.node );
    }
  }
});

clojure.JS.defclass( clojure.lang.PersistentHashMap, "BitmapIndexedNode", {
  init: function(bitmap, nodes, shift) {
    this.bitmap = bitmap;
    this.nodes = nodes;
    this.shift = shift;
    this._hash = nodes[0].getHash();
  },
  statics: {
    bitpos: function(hash, shift) {
      return 1 << clojure.lang.PersistentHashMap.mask( hash, shift );
    },
    createA: function(bitmap, nodes, shift){
      var PHM = clojure.lang.PersistentHashMap;
      if(bitmap == -1)
        return new PHM.FullNode( nodes, shift );
      return new PHM.BitmapIndexedNode( bitmap, nodes, shift );
    },
    createB: function(shift, branch, hash, key, val, addedLeaf){
      var PHM = clojure.lang.PersistentHashMap;
      return (new PHM.BitmapIndexedNode(
                PHM.BitmapIndexedNode.bitpos(branch.getHash(), shift),
                [branch],
                shift)).assoc( shift, hash, key, val, addedLeaf );
    }
  },
  methods: {
    index: function(bit) { return clojure.JS.bitcount(this.bitmap & (bit-1) );},
    assoc: function(levelShift, hash, key, val, addedLeaf){
      var newnodes, n,
          BIN = clojure.lang.PersistentHashMap.BitmapIndexedNode,
          bit = BIN.bitpos( hash, this.shift ),
          idx = this.index( bit );
      if((this.bitmap & bit) != 0) {
        n = this.nodes[idx].assoc( this.shift+5, hash, key, val, addedLeaf);
        if( n == this.nodes[idx] )
          return this;
        else {
          newnodes = this.nodes.slice();
          newnodes[idx] = n;
          return new BIN( this.bitmap, newnodes, this.shift );
        }
      }
      else {
        addedLeaf[0]= new clojure.lang.PersistentHashMap.LeafNode(hash,key,val);
        newnodes = this.nodes.slice();
        newnodes.splice( idx, 0, addedLeaf[0] );
        return BIN.createA( this.bitmap | bit, newnodes, this.shift );
      }
    },
    without: function( hash, key ) {
      var BIN = clojure.lang.PersistentHashMap.BitmapIndexedNode,
          bit = BIN.bitpos( hash, this.shift ),
          idx, n, newnodes;
      if((this.bitmap & bit) !== 0) {
        idx = this.index( bit );
        n = this.nodes[ idx ].without( hash, key );
        if( n != this.nodes[ idx ] ) {
          if( n === null ) {
            if( this.bitmap == bit )
              return null;
            newnodes = this.nodes.slice();
            newnodes.splice( idx, 1 );
            return new BIN( this.bitmap & ~bit, newnodes, this.shift );
          }
          newnodes = this.nodes.slice();
          newnodes[ idx ] = n;
          return new BIN( this.bitmap, newnodes, this.shift );
        }
      }
      return this;
    },
    find: function( hash, key ) {
      var BIN = clojure.lang.PersistentHashMap.BitmapIndexedNode,
          bit = BIN.bitpos( hash, this.shift );
      if((this.bitmap & bit) !== 0)
        return this.nodes[ this.index(bit) ].find( hash, key );
      return null;
    },
    getHash: function(){ return this._hash; },
    nodeSeq: function(){
      return clojure.lang.PersistentHashMap.BitmapIndexedNode.Seq.create(
          this, 0 );
    }
  }
});

clojure.JS.defclass( clojure.lang.PersistentHashMap.BitmapIndexedNode, "Seq", {
  extend: clojure.lang.ASeq,
  init: function( _meta, s, i, node ) {
    this._meta = _meta;
    this.s = s;
    this.i = i;
    this.node = node;
  },
  statics: {
    create: function(node, i){
      if( i >= node.nodes.length )
        return null;
      return new clojure.lang.PersistentHashMap.BitmapIndexedNode.Seq(
          null, node.nodes[i].nodeSeq(), i, node );
    }
  },
  methods: {
    first: function(){ return this.s.first(); },
    rest: function(){
      var Seq = clojure.lang.PersistentHashMap.BitmapIndexedNode.Seq,
          nexts = this.s.rest();
      if( nexts )
        return new Seq( null, nexts, this.i, this.node );
      return Seq.create( this.node, this.i+1 );
    },
    withMeta: function(_meta){
      return new clojure.lang.PersistentHashMap.BitmapIndexedNode.Seq(
          _meta, this.s, this.i, this.node );
    }
  }
});

clojure.JS.defclass( clojure.lang.PersistentHashMap, "LeafNode", {
  extend: clojure.lang.AMapEntry,
  init: function( hash, key, val ) {
    this.hash = hash;
    this._key = key;
    this._val = val;
  },
  methods: {
    assoc: function(shift, hash, key, val, addedLeaf) {
      var newLeaf, PHM = clojure.lang.PersistentHashMap;
      if( hash == this.hash ) {
        if( clojure.lang.Util.equal( key, this._key ) ) {
          if( val == this._val )
            return this;
          return new PHM.LeafNode( hash, key, val );
        }
        newLeaf = new PHM.LeafNode( hash, key, val );
        addedLeaf[0] = newLeaf;
        return new PHM.HashCollisionNode( hash, [this, newLeaf] );
      }
      return PHM.BitmapIndexedNode.createB(
          shift, this, hash, key, val, addedLeaf );
    },
    without: function(hash, key){
      if(hash == this.hash && clojure.lang.Util.equal( key, this._key ))
        return null;
      return this;
    },
    find: function(hash, key){
      if(hash == this.hash && clojure.lang.Util.equal( key, this._key ))
        return this;
      return null;
    },
    nodeSeq: function(){ return clojure.core.cons( this, null ); },
    getHash: function(){ return this.hash; },
    key: function(){ return this._key; },
    val: function(){ return this._val; },
    getKey: function(){ return this._key; },
    getValue: function(){ return this._val; }
  }
});

clojure.JS.defclass( clojure.lang.PersistentHashMap, "HashCollisionNode", {
  init: function(hash, leaves){
    this.hash = hash;
    this.leaves = leaves;
  },
  methods: {
    assoc: function(shift, hash, key, val, addedLeaf) {
      var idx, newLeaves, PHM = clojure.lang.PersistentHashMap;
      if( hash == this.hash ) {
        idx = this.findIndex( hash, key );
        if( idx != -1 ) {
          if( this.leaves[idx].val == val )
            return this;
          newLeaves = this.leaves.slice();
          newLeaves[idx] = new PHM.LeafNode( hash, key, val );
          return new PHM.HashCollisionNode( hash, newLeaves );
        }
        addedLeaf[0] = new PHM.LeafNode( hash, key, val );
        newLeaves = this.leaves.concat( addedLeaf );
        return new PHM.HashCollisionNode( hash, newLeaves );
      }
      return PHM.BitmapIndexedNode.createB(shift,this,hash,key,val,addedLeaf);
    },
    without: function(hash, key){
      var idx = this.findIndex( hash, key );
      if( idx != -1 )
        return leaves[ idx ];
      return null;
    },
    find: function(hash, key){
      var idx = this.findIndex(hash, key);
      if(idx != -1)
        return this.leaves[idx];
      return null;
    },
    nodeSeq: function(){
      return clojure.lang.ArraySeq.create(this.leaves);
    },
    findIndex: function(hash, key){
      for( var i = 0; i < this.leaves.length; ++i ) {
        if( this.leaves[i].find( hash, key ) != null )
          return i;
      }
      return -1;
    },
    getHash: function(){ return this.hash; }
  }
});

// XXX dirty little hack until such time as PersistentArrayMap is ported
clojure.lang.PersistentArrayMap = clojure.lang.PersistentHashMap;

clojure.JS.definterface( clojure.lang, "IPersistentSet",
    [ clojure.lang.IPersistentCollection ] );

clojure.JS.defclass( clojure.lang, "APersistentSet", {
  extend: clojure.lang.AFn,
  implement: [ clojure.lang.IPersistentSet ],
  init: function( meta, impl ) {
    this._meta = meta;
    this.impl = impl;
    this._hash = -1;
  },
  methods: {
    contains: function(key){ return this.impl.containsKey(key); },
    get: function(key){ return this.impl.valAt(key); },
    count: function(){ return this.impl.count(); },
    seq: function(){ return clojure.core.keys( this.impl ); },
    invoke: function(key){ return this.get(key); },
    equals: function(m) {
      if( ! clojure.instanceq( clojure.lang.IPersistentSet ) )
        return false;
      if( m.count() != this.count() || m.hashCode() != this.hashCode() )
        return false;
      for( var s = this.seq(); s; s = s.rest() ) {
        if( ! m.contains( s.first() ) )
          return false;
      }
      return true;
    },
    hashCode: function() {
      if( this._hash == -1 ) {
        var hash = this.count(),
            s = this.seq();
        for( ; s; s = s.rest() ) {
          hash = clojure.lang.Util.hashCombine(
              hash, clojure.lang.Util.hash( s.first() ) );
        }
        this._hash = hash;
      }
      return this._hash;
    },
    toArray: function(){ return clojure.lang.RT.seqToArray( this.seq() ); },
    containsAll: function(c){ throw "not yet implemented"; },
    size: function(){ return this.count(); },
    isEmpty: function(){ return this.count() == 0; }
  }
});

clojure.JS.defclass( clojure.lang, "PersistentHashSet", {
  extend: clojure.lang.APersistentSet,
  init: function( meta, impl ) {
    clojure.lang.APersistentSet.call( this, meta, impl );
  },
  statics: {
    create: function(init){
      var ret = clojure.lang.PersistentHashSet.EMPTY,
          s = clojure.core.seq( init );
      for( ; s; s=s.rest() ){
        ret = ret.cons( s.first() );
      }
      return ret;
    }
  },
  methods: {
    disjoin: function(key) {
      if( this.contains(key) )
        return new clojure.lang.PersistentHashSet(
            this._meta, this.impl.without(key));
      return this;
    },
    cons: function(o) {
      if( this.contains(o) )
        return this;
      return new clojure.lang.PersistentHashSet(
          this._meta, this.impl.assoc(o));
    },
    empty: function(){
      return clojure.lang.PersistentHashSet.EMPTY.withMeta( this._meta );
    },
    withMeta: function(_meta){
      return new clojure.lang.PersistentHashSet( _meta, this.impl );
    }
  }
});

clojure.lang.PersistentHashSet.EMPTY = new clojure.lang.PersistentHashSet(
    null, clojure.lang.PersistentHashMap.EMPTY );

clojure.JS.defclass( clojure.lang, "MultiFn", {
  extend: clojure.lang.AFn,
  init: function( dispatchFn, defaultDispatchVal ) {
    this.dispatchFn = dispatchFn;
    this.defaultDispatchVal = defaultDispatchVal;
    this.methodTable = clojure.lang.PersistentHashMap.EMPTY;
    this.methodCache = clojure.lang.PersistentHashMap.EMPTY;
    this.preferTable = clojure.lang.PersistentHashMap.EMPTY;
    this.cachedHierarchy = null;
  },
  methods: {
    addMethod: function( dispatchVal, method ){
      this.methodTable = this.methodTable.assoc( dispatchVal, method );
      this.resetCache();
      return this;
    },
    removeMethod: function( dispatchVal ){
      this.methodTable = this.methodTable.without( dispatchVal );
      this.resetCache();
      return this;
    },
    preferMethod: function( dispatchValX, dispatchValY ){
      if( this.prefers( dispatchValY, dispatchValX ) )
        throw ("Preference conflict: " + dispatchValY +
            " is already preferred to" + dispatchValX);
      var oldset = clojure.core.get(
          this.preferTable, dispatchValX, clojure.lang.PersistentHashSet.EMPTY);
      this.preferTable = this.preferTable.assoc(
          dispatchValX, clojure.core.conj( oldset, dispatchValY ) );
      this.resetCache();
      return this;
    },
    prefers: function(x,y) {
      var xprefs = this.preferTable.valAt(x);
      return xprefs && xprefs.contains(y);
    },
    isA: function(x,y) { return clojure.core.isa_QMARK_( x, y ); },
    dominates: function(x,y) { return this.prefers(x,y) || this.isA(x,y); },
    resetCache: function() {
      this.methodCache = this.methodTable;
      this.cachedHierarchy = clojure.core.global_hierarchy;
      return this.methodCache;
    },
    getFn: function(dispatchVal) {
      if( this.cachedHierarchy != clojure.core.global_hierarchy )
        this.resetCache();
      var targetFn =
        this.methodCache.valAt( dispatchVal ) ||
        this.findAndCacheBestMethod( dispatchVal ) ||
        this.methodTable.valAt( this.defaultDispatchVal );
      if( targetFn === null )
        throw "No method for dispatch value: " + dispatchVal;
      return targetFn;
    },
    findAndCacheBestMethod: function( dispatchVal ) {
      var e, bestEntry = null,
          s = this.methodTable.seq();
      for( ; s; s = s.rest() ) {
        e = s.first();
        if( this.isA( dispatchVal, e.getKey() ) ) {
          if( bestEntry===null || this.dominates(e.getKey(),bestEntry.getKey()))
            bestEntry = e;
          if( ! this.dominates( bestEntry.getKey(), e.getKey() ) )
            throw ["Multiple methods match dispatch value:", dispatchVal,
                  "->", e.getKey(), "and", bestEntry.getKey(),
                  "and neither is preferred"].join(' ');
        }
      }
      if( bestEntry === null )
        return null;
      // skip multi-threading protection
      this.methodCache = this.methodCache.assoc(
          dispatchVal, bestEntry.getValue());
      return bestEntry.getValue();
    },
    invoke: function() {
      return (this.getFn( this.dispatchFn.apply( null, arguments ) )
        .apply( null, arguments ));
    }
  }
});

clojure.core.print_method = new clojure.lang.MultiFn(
  function (x, writer){ return clojure.core.class_(x); },
  clojure.core.keyword("","default"));

clojure.core.print_method.addMethod( java.lang.Class, function(o,w) {
  w.write("#="+clojure.lang.RT.className(o));
});

clojure.JS.relayPrintMethod( Number,  java.lang.Number );
clojure.JS.relayPrintMethod( Array,   java.util.Collection );
clojure.JS.relayPrintMethod( Boolean, java.lang.Boolean );
clojure.JS.relayPrintMethod( String,  java.lang.String,
    function(o) { return new clojure.JS.String(o); } );
clojure.JS.relayPrintMethod( clojure.JS.Class, java.lang.Class );

clojure.JS.def(clojure.core,"_STAR_print_readably_STAR_",true);

clojure.JS.implement( clojure.lang.Namespace, "Namespace" );

clojure.lang.Namespace.find = function( s ) {
  return clojure.JS.global[ s.getName() ];
};

clojure.JS.merge( clojure.lang.Namespace.prototype, {
  getMappings: function() { return this; },
  hashCode: function() { return clojure.hash( this.name ); }
});

clojure.core.in_ns(clojure.core.symbol("user"));

(function() {
  var buf = [];
  function write(s) {
    s = s.toString();
    var last, parts = s.split(/\n/);
    if( parts.length == 1 ) {
      buf.push(s);
    }
    else {
      last = parts.pop();
      print( buf.join('') + parts.join('\n') );
      buf = [ last ];
    }
  }
  clojure.core._STAR_out_STAR_ = { append: write, write: write };
})();
