content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE TemplateHaskell #-}
  {-# LANGUAGE QuasiQuotes #-}
  
  module Fay.Runtime where
  
  import Text.Shakespeare.Text
  import Data.Text.Lazy as T
  import Fay.Config
  
  -- | Get the default runtime source.
  getRuntimeSource :: Config -> String
  getRuntimeSource cfg =
    let ifTs :: T.Text -> T.Text
        ifTs a = if configTypeScript cfg then a else ""
        ifTsJs :: T.Text -> T.Text -> T.Text
        ifTsJs a b = if configTypeScript cfg then a else b
    in T.unpack [lt|
  /*******************************************************************************
   * Misc.
   */
  #{ifTs "var Fay:{[key:string]: any;} = {};"}
  
  // Workaround for missing functionality in IE 8 and earlier.
  if( Object.create === undefined ) {
    Object.create = function( o ) {
      function F(){}
      F.prototype = o;
      return new F();
    };
  }
  
  // Insert properties of b in place into a.
  function Fay$$objConcat(a,b){
    for (var p in b) if (b.hasOwnProperty(p)){
      a[p] = b[p];
    }
    return a;
  }
  
  /*******************************************************************************
   * Thunks.
   */
  
  // Force a thunk (if it is a thunk) until WHNF.
  function Fay$$_(thunkish,nocache#{ifTs "?: boolean"}){
    while (thunkish instanceof Fay$$$) {
      thunkish = thunkish.force(nocache);
    }
    return thunkish;
  }
  
  // Apply a function to arguments (see method2 in Fay.hs).
  function Fay$$__(){
    var f = arguments[0];
    for (var i = 1, len = arguments.length; i < len; i++) {
      f = (f instanceof Fay$$$? Fay$$_(f) : f)(arguments[i]);
    }
    return f;
  }
  
  // Thunk object.
  function Fay$$$(value){
    this.forced = false;
    this.value = value;
  }
  
  // Force the thunk.
  Fay$$$.prototype.force = function(nocache) {
    return nocache ?
      this.value() :
      (this.forced ?
       this.value :
       (this.value = this.value(), this.forced = true, this.value));
  };
  
  
  function Fay$$seq(x) {
    return function(y) {
      Fay$$_(x,false);
      return y;
    }
  }
  
  function Fay$$seq$36$uncurried(x,y) {
    Fay$$_(x,false);
    return y;
  }
  
  /*******************************************************************************
   * Monad.
   */
  
  function Fay$$Monad(value){
    this.value = value;
  }
  
  // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs.
  // >>
  function Fay$$then(a){
    return function(b){
      return Fay$$bind(a)(function(_){
        return b;
      });
    };
  }
  
  // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs.
  // >>
  function Fay$$then$36$uncurried(a,b){
    return Fay$$bind$36$uncurried(a,function(_){ return b; });
  }
  
  // >>=
  // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs.
  function Fay$$bind(m){
    return function(f){
      return new Fay$$$(function(){
        var monad = Fay$$_(m,true);
        return Fay$$_(f)(monad.value);
      });
    };
  }
  
  // >>=
  // This is used directly from Fay, but can be rebound or shadowed. See primOps in Types.hs.
  function Fay$$bind$36$uncurried(m,f){
    return new Fay$$$(function(){
      var monad = Fay$$_(m,true);
      return Fay$$_(f)(monad.value);
    });
  }
  
  // This is used directly from Fay, but can be rebound or shadowed.
  function Fay$$$_return(a){
    return new Fay$$Monad(a);
  }
  
  // Allow the programmer to access thunk forcing directly.
  function Fay$$force(thunk){
    return function(type){
      return new Fay$$$(function(){
        Fay$$_(thunk,type);
        return new Fay$$Monad(Fay$$unit);
      })
    }
  }
  
  // This is used directly from Fay, but can be rebound or shadowed.
  function Fay$$return$36$uncurried(a){
    return new Fay$$Monad(a);
  }
  
  // Unit: ().
  var Fay$$unit = null;
  
  /*******************************************************************************
   * Serialization.
   * Fay <-> JS. Should be bijective.
   */
  
  // Serialize a Fay object to JS.
  function Fay$$fayToJs(type,fayObj){
    var base = type[0];
    var args = type[1];
    var jsObj;
    if(base == "action") {
      // A nullary monadic action. Should become a nullary JS function.
      // Fay () -> function(){ return ... }
      return function(){
        return Fay$$fayToJs(args[0],Fay$$_(fayObj,true).value);
      };
  
    }
    else if(base == "function") {
      // A proper function.
      return function(){
        var fayFunc = fayObj;
        var return_type = args[args.length-1];
        var len = args.length;
        // If some arguments.
        if (len > 1) {
          // Apply to all the arguments.
          fayFunc = Fay$$_(fayFunc,true);
          // TODO: Perhaps we should throw an error when JS
          // passes more arguments than Haskell accepts.
  
          // Unserialize the JS values to Fay for the Fay callback.
          if (args == "automatic_function")
          {
            for (var i = 0; i < arguments.length; i++) {
              fayFunc = Fay$$_(fayFunc(Fay$$jsToFay(["automatic"],arguments[i])),true);
            }
            return Fay$$fayToJs(["automatic"], fayFunc);
          }
  
          for (var i = 0, len = len; i < len - 1 && fayFunc instanceof Function; i++) {
            fayFunc = Fay$$_(fayFunc(Fay$$jsToFay(args[i],arguments[i])),true);
          }
          // Finally, serialize the Fay return value back to JS.
          var return_base = return_type[0];
          var return_args = return_type[1];
          // If it's a monadic return value, get the value instead.
          if(return_base == "action") {
            return Fay$$fayToJs(return_args[0],fayFunc.value);
          }
          // Otherwise just serialize the value direct.
          else {
            return Fay$$fayToJs(return_type,fayFunc);
          }
        } else {
          throw new Error("Nullary function?");
        }
      };
  
    }
    else if(base == "string") {
      return Fay$$fayToJs_string(fayObj);
    }
    else if(base == "list") {
      // Serialize Fay list to JavaScript array.
      var arr = [];
      fayObj = Fay$$_(fayObj);
      while(fayObj instanceof Fay$$Cons) {
        arr.push(Fay$$fayToJs(args[0],fayObj.car));
        fayObj = Fay$$_(fayObj.cdr);
      }
      return arr;
    }
    else if(base == "tuple") {
      // Serialize Fay tuple to JavaScript array.
      var arr = [];
      fayObj = Fay$$_(fayObj);
      var i = 0;
      while(fayObj instanceof Fay$$Cons) {
        arr.push(Fay$$fayToJs(args[i++],fayObj.car));
        fayObj = Fay$$_(fayObj.cdr);
      }
      return arr;
    }
    else if(base == "defined") {
      fayObj = Fay$$_(fayObj);
      return fayObj instanceof Fay.FFI._Undefined
        ? undefined
        : Fay$$fayToJs(args[0],fayObj.slot1);
    }
    else if(base == "nullable") {
      fayObj = Fay$$_(fayObj);
      return fayObj instanceof Fay.FFI._Null
        ? null
        : Fay$$fayToJs(args[0],fayObj.slot1);
    }
    else if(base == "double" || base == "int" || base == "bool") {
      // Bools are unboxed.
      return Fay$$_(fayObj);
    }
    else if(base == "ptr")
      return fayObj;
    else if(base == "unknown")
      return Fay$$fayToJs(["automatic"], fayObj);
    else if(base == "automatic" && fayObj instanceof Function) {
      return Fay$$fayToJs(["function", "automatic_function"], fayObj);
    }
    else if(base == "automatic" || base == "user") {
      fayObj = Fay$$_(fayObj);
  
      if(fayObj instanceof Fay$$Cons || fayObj === null){
        // Serialize Fay list to JavaScript array.
        var arr = [];
        while(fayObj instanceof Fay$$Cons) {
          arr.push(Fay$$fayToJs(["automatic"],fayObj.car));
          fayObj = Fay$$_(fayObj.cdr);
        }
        return arr;
      } else {
        var fayToJsFun = fayObj && fayObj.instance && Fay$$fayToJsHash[fayObj.instance];
        return fayToJsFun ? fayToJsFun(type,type[2],fayObj) : fayObj;
      }
    }
  
    throw new Error("Unhandled Fay->JS translation type: " + base);
  }
  
  // Stores the mappings from fay types to js objects.
  // This will be populated by compiled modules.
  var Fay$$fayToJsHash = {};
  
  // Specialized serializer for string.
  function Fay$$fayToJs_string(fayObj){
    // Serialize Fay string to JavaScript string.
    var str = "";
    fayObj = Fay$$_(fayObj);
    while(fayObj instanceof Fay$$Cons) {
      str += Fay$$_(fayObj.car);
      fayObj = Fay$$_(fayObj.cdr);
    }
    return str;
  };
  function Fay$$jsToFay_string(x){
    return Fay$$list(x)
  };
  
  // Special num/bool serializers.
  function Fay$$jsToFay_int(x){return x;}
  function Fay$$jsToFay_double(x){return x;}
  function Fay$$jsToFay_bool(x){return x;}
  
  function Fay$$fayToJs_int(x){return Fay$$_(x);}
  function Fay$$fayToJs_double(x){return Fay$$_(x);}
  function Fay$$fayToJs_bool(x){return Fay$$_(x);}
  
  // Unserialize an object from JS to Fay.
  function Fay$$jsToFay(type,jsObj){
    var base = type[0];
    var args = type[1];
    var fayObj;
    if(base == "action") {
      // Unserialize a "monadic" JavaScript return value into a monadic value.
      return new Fay$$Monad(Fay$$jsToFay(args[0],jsObj));
    }
    else if(base == "function") {
      // Unserialize a function from JavaScript to a function that Fay can call.
      // So
      //
      //    var f = function(x,y,z){ … }
      //
      // becomes something like:
      //
      //    function(x){
      //      return function(y){
      //        return function(z){
      //          return new Fay$$$(function(){
      //            return Fay$$jsToFay(f(Fay$$fayTojs(x),
      //                                  Fay$$fayTojs(y),
      //                                  Fay$$fayTojs(z))
      //    }}}}};
      var returnType = args[args.length-1];
      var funArgs = args.slice(0,-1);
  
      if (jsObj.length > 0) {
        var makePartial = function(args){
          return function(arg){
            var i = args.length;
            var fayArg = Fay$$fayToJs(funArgs[i],arg);
            var newArgs = args.concat([fayArg]);
            if(newArgs.length == funArgs.length) {
              return new Fay$$$(function(){
                return Fay$$jsToFay(returnType,jsObj.apply(this,newArgs));
              });
            } else {
              return makePartial(newArgs);
            }
          };
        };
        return makePartial([]);
      }
      else
        return function (arg) {
          return Fay$$jsToFay(["automatic"], jsObj(Fay$$fayToJs(["automatic"], arg)));
        };
    }
    else if(base == "string") {
      // Unserialize a JS string into Fay list (String).
      // This is a special case, when String is explicit in the type signature,
      // with `Automatic' a string would not be decoded.
      return Fay$$list(jsObj);
    }
    else if(base == "list") {
      // Unserialize a JS array into a Fay list ([a]).
      var serializedList = [];
      for (var i = 0, len = jsObj.length; i < len; i++) {
        // Unserialize each JS value into a Fay value, too.
        serializedList.push(Fay$$jsToFay(args[0],jsObj[i]));
      }
      // Pop it all in a Fay list.
      return Fay$$list(serializedList);
    }
    else if(base == "tuple") {
      // Unserialize a JS array into a Fay tuple ((a,b,c,...)).
      var serializedTuple = [];
      for (var i = 0, len = jsObj.length; i < len; i++) {
        // Unserialize each JS value into a Fay value, too.
        serializedTuple.push(Fay$$jsToFay(args[i],jsObj[i]));
      }
      // Pop it all in a Fay list.
      return Fay$$list(serializedTuple);
    }
    else if(base == "defined") {
      return jsObj === undefined
        ? new Fay.FFI._Undefined()
        : new Fay.FFI._Defined(Fay$$jsToFay(args[0],jsObj));
    }
    else if(base == "nullable") {
      return jsObj === null
        ? new Fay.FFI._Null()
        : new Fay.FFI.Nullable(Fay$$jsToFay(args[0],jsObj));
    }
    else if(base == "int") {
      // Int are unboxed, so there's no forcing to do.
      // But we can do validation that the int has no decimal places.
      // E.g. Math.round(x)!=x? throw "NOT AN INTEGER, GET OUT!"
      fayObj = Math.round(jsObj);
      if(fayObj!==jsObj) throw "Argument " + jsObj + " is not an integer!";
      return fayObj;
    }
    else if (base == "double" ||
             base == "bool" ||
             base ==  "ptr") {
      return jsObj;
    }
    else if(base == "unknown")
      return Fay$$jsToFay(["automatic"], jsObj);
    else if(base == "automatic" && jsObj instanceof Function) {
      #{ifTsJs "let type: string[][]" "var type"} = [["automatic"]];
      for (var i = 0; i < jsObj.length; i++)
        type.push(["automatic"]);
      return Fay$$jsToFay(["function", type], jsObj);
    }
    else if(base == "automatic" && jsObj instanceof Array) {
      var list = null;
      for (var i = jsObj.length - 1; i >= 0; i--) {
        list = new Fay$$Cons(Fay$$jsToFay([base], jsObj[i]), list);
      }
      return list;
    }
    else if(base == "automatic" || base == "user") {
      if (jsObj && jsObj['instance']) {
        var jsToFayFun = Fay$$jsToFayHash[jsObj["instance"]];
        return jsToFayFun ? jsToFayFun(type,type[2],jsObj) : jsObj;
      }
      else
        return jsObj;
    }
  
    throw new Error("Unhandled JS->Fay translation type: " + base);
  }
  
  // Stores the mappings from js objects to fay types.
  // This will be populated by compiled modules.
  var Fay$$jsToFayHash = {};
  
  /*******************************************************************************
   * Lists.
   */
  
  // Cons object.
  function Fay$$Cons(car,cdr){
    this.car = car;
    this.cdr = cdr;
  }
  
  // Make a list.
  function Fay$$list(xs){
    var out = null;
    for(var i=xs.length-1; i>=0;i--)
      out = new Fay$$Cons(xs[i],out);
    return out;
  }
  
  // Built-in list cons.
  function Fay$$cons(x){
    return function(y){
      return new Fay$$Cons(x,y);
    };
  }
  
  // List index.
  // `list' is already forced by the time it's passed to this function.
  // `list' cannot be null and `index' cannot be out of bounds.
  function Fay$$index(index,list){
    for(var i = 0; i < index; i++) {
      list = Fay$$_(list.cdr);
    }
    return list.car;
  }
  
  // List length.
  // `list' is already forced by the time it's passed to this function.
  function Fay$$listLen(list,max){
    for(var i = 0; list !== null && i < max + 1; i++) {
      list = Fay$$_(list.cdr);
    }
    return i == max;
  }
  
  /*******************************************************************************
   * Numbers.
   */
  
  // Built-in *.
  function Fay$$mult(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) * Fay$$_(y);
      });
    };
  }
  
  function Fay$$mult$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) * Fay$$_(y);
    });
  
  }
  
  // Built-in +.
  function Fay$$add(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) + Fay$$_(y);
      });
    };
  }
  
  // Built-in +.
  function Fay$$add$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) + Fay$$_(y);
    });
  
  }
  
  // Built-in -.
  function Fay$$sub(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) - Fay$$_(y);
      });
    };
  }
  // Built-in -.
  function Fay$$sub$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) - Fay$$_(y);
    });
  
  }
  
  // Built-in /.
  function Fay$$divi(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) / Fay$$_(y);
      });
    };
  }
  
  // Built-in /.
  function Fay$$divi$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) / Fay$$_(y);
    });
  
  }
  
  /*******************************************************************************
   * Booleans.
   */
  
  // Are two values equal?
  function Fay$$equal(lit1, lit2) {
    // Simple case
    lit1 = Fay$$_(lit1);
    lit2 = Fay$$_(lit2);
    if (lit1 === lit2) {
      return true;
    }
    // General case
    if (lit1 instanceof Array) {
      if (lit1.length != lit2.length) return false;
      for (var len = lit1.length, i = 0; i < len; i++) {
        if (!Fay$$equal(lit1[i], lit2[i])) return false;
      }
      return true;
    } else if (lit1 instanceof Fay$$Cons && lit2 instanceof Fay$$Cons) {
      do {
        if (!Fay$$equal(lit1.car,lit2.car))
          return false;
        lit1 = Fay$$_(lit1.cdr), lit2 = Fay$$_(lit2.cdr);
        if (lit1 === null || lit2 === null)
          return lit1 === lit2;
      } while (true);
    } else if (typeof lit1 == 'object' && typeof lit2 == 'object' && lit1 && lit2 &&
               lit1.instance === lit2.instance) {
      for(var x in lit1) {
        if(!Fay$$equal(lit1[x],lit2[x]))
          return false;
      }
      return true;
    } else {
      return false;
    }
  }
  
  // Built-in ==.
  function Fay$$eq(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$equal(x,y);
      });
    };
  }
  
  function Fay$$eq$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$equal(x,y);
    });
  
  }
  
  // Built-in /=.
  function Fay$$neq(x){
    return function(y){
      return new Fay$$$(function(){
        return !(Fay$$equal(x,y));
      });
    };
  }
  
  // Built-in /=.
  function Fay$$neq$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return !(Fay$$equal(x,y));
    });
  
  }
  
  // Built-in >.
  function Fay$$gt(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) > Fay$$_(y);
      });
    };
  }
  
  // Built-in >.
  function Fay$$gt$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) > Fay$$_(y);
    });
  
  }
  
  // Built-in <.
  function Fay$$lt(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) < Fay$$_(y);
      });
    };
  }
  
  
  // Built-in <.
  function Fay$$lt$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) < Fay$$_(y);
    });
  
  }
  
  
  // Built-in >=.
  function Fay$$gte(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) >= Fay$$_(y);
      });
    };
  }
  
  // Built-in >=.
  function Fay$$gte$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) >= Fay$$_(y);
    });
  
  }
  
  // Built-in <=.
  function Fay$$lte(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) <= Fay$$_(y);
      });
    };
  }
  
  // Built-in <=.
  function Fay$$lte$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) <= Fay$$_(y);
    });
  
  }
  
  // Built-in &&.
  function Fay$$and(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) && Fay$$_(y);
      });
    };
  }
  
  // Built-in &&.
  function Fay$$and$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) && Fay$$_(y);
    });
    ;
  }
  
  // Built-in ||.
  function Fay$$or(x){
    return function(y){
      return new Fay$$$(function(){
        return Fay$$_(x) || Fay$$_(y);
      });
    };
  }
  
  // Built-in ||.
  function Fay$$or$36$uncurried(x,y){
  
    return new Fay$$$(function(){
      return Fay$$_(x) || Fay$$_(y);
    });
  
  }
  
  /*******************************************************************************
   * Mutable references.
   */
  
  // Make a new mutable reference.
  function Fay$$Ref(x){
    this.value = x;
  }
  
  // Write to the ref.
  function Fay$$writeRef(ref,x){
    ref.value = x;
  }
  
  // Get the value from the ref.
  function Fay$$readRef(ref){
    return ref.value;
  }
  
  /*******************************************************************************
   * Dates.
   */
  function Fay$$date(str){
    return Date.parse(str);
  }
  
  /*******************************************************************************
   * Data.Var
   */
  
  function Fay$$Ref2(val){
    this.val = val;
  }
  
  function Fay$$Sig(){
    this.handlers = [];
  }
  
  function Fay$$Var(val){
    this.val = val;
    this.handlers = [];
  }
  
  // Helper used by Fay$$setValue and for merging
  function Fay$$broadcastInternal(self, val, force){
    var handlers = self.handlers;
    var exceptions = [];
    for(#{ifTsJs "let" "var"} len = handlers.length, i = 0; i < len; i++) {
      try {
        force(handlers[i][1](val), true);
      } catch (e) {
        exceptions.push(e);
      }
    }
    // Rethrow the encountered exceptions.
    if (exceptions.length > 0) {
      console.error("Encountered " + exceptions.length + " exception(s) while broadcasing a change to ", self);
      for(#{ifTsJs "let len: number" "var len"} = exceptions.length, i = 0; i < len; i++) {
        (function(exception) {
          setTimeout(function() { throw exception; }, 0);
        })(exceptions[i]);
      }
    }
  }
  
  function Fay$$setValue(self, val, force){
    if (self instanceof Fay$$Ref2) {
      self.val = val;
    } else if (self instanceof Fay$$Var) {
      self.val = val;
      Fay$$broadcastInternal(self, val, force);
    } else if (self instanceof Fay$$Sig) {
      Fay$$broadcastInternal(self, val, force);
    } else {
      throw "Fay$$setValue given something that's not a Ref2, Var, or Sig"
    }
  }
  
  function Fay$$subscribe(self, f){
    var key = {};
    self.handlers.push([key,f]);
    var searchStart = self.handlers.length - 1;
    return function(_){
      for(var i = Math.min(searchStart, self.handlers.length - 1); i >= 0; i--) {
        if(self.handlers[i][0] == key) {
          self.handlers = self.handlers.slice(0,i).concat(self.handlers.slice(i+1));
          return;
        }
      }
      return _; // This variable has to be used, otherwise Closure
                // strips it out and Fay serialization breaks.
    };
  }
  
  /*******************************************************************************
   * Application code.
   */
  
  |]
  