/* @flow weak */
"use strict";

var assert = require("assert");
var generator = require("./generator.js");
var primitive = require("./primitive.js");
var show = require("./show.js");
var shrink = require("./shrink.js");
var utils = require("./utils.js");

/**
  ### Arbitrary combinators
*/

/**
  - `nonshrink(arb: arbitrary a): arbitrary a`

      Non shrinkable version of arbitrary `arb`.
*/
function nonshrink(arb) {
  arb = utils.force(arb);

  return {
    generator: arb.generator,
    shrink: shrink.noop,
    show: arb.show,
  };
}

/**
  - `array(arb: arbitrary a): arbitrary (array a)`
*/
function array(arb) {
  arb = utils.force(arb || primitive.json);

  return {
    generator: generator.array(arb.generator),
    shrink: shrink.array(arb.shrink),
    show: show.array(arb.show),
  };
}

/**
  - `nearray(arb: arbitrary a): arbitrary (array a)`
*/
function nearray(arb) {
  arb = utils.force(arb || primitive.json);

  return {
    generator: generator.nearray(arb.generator),
    shrink: shrink.nearray(arb.shrink),
    show: show.array(arb.show),
  };
}

/**
  - `pair(arbA: arbitrary a, arbB : arbitrary b): arbitrary (pair a b)`

      If not specified `a` and `b` are equal to `value()`.
*/
function pair(a, b) {
  a = utils.force(a || primitive.json);
  b = utils.force(b || primitive.json);

  return {
    generator: generator.pair(a.generator, b.generator),
    shrink: shrink.pair(a.shrink, b.shrink),
    show: show.pair(a.show, b.show),
  };
}

/**
  - `tuple(arbs: (arbitrary a, arbitrary b...)): arbitrary (a, b...)`
*/
function tuple(arbs) {
  arbs = arbs.map(utils.force);
  return {
    generator: generator.tuple(utils.pluck(arbs, "generator")),
    shrink: shrink.tuple(utils.pluck(arbs, "shrink")),
    show: show.tuple(utils.pluck(arbs, "show")),
  };
}

/**
  - `map(arb: arbitrary a): arbitrary (map a)`

      Generates a JavaScript object with properties of type `A`.
*/
function fromArray(arrayOfPairs) {
  var res = {};
  arrayOfPairs.forEach(function (p) {
    res[p[0]] = p[1];
  });
  return res;
}

function toArray(m) {
  var res = [];
  Object.keys(m).forEach(function (k) {
    res.push([k, m[k]]);
  });
  return res;
}

function map(arb) {
  arb = utils.force(arb || primitive.json);
  var pairArbitrary = pair(primitive.string(), arb);
  var arrayArbitrary = array(pairArbitrary);

  return {
    generator: arrayArbitrary.generator.map(fromArray),
    shrink: arrayArbitrary.shrink.isomap(fromArray, toArray),
    show: function (m) {
      return "{" + Object.keys(m).map(function (k) {
        return k + ": " + arb.show(m[k]);
      }).join(", ") + "}";
    }
  };
}

/**
  - `oneof(gs : array (arbitrary a)...) : arbitrary a`

      Randomly uses one of the given arbitraries.
*/
function oneof() {
  assert(arguments.length !== 0, "oneof: at least one parameter expected");

  // TODO: write this in more functional way
  var generators = [];
  var append = function (a) {
    generators.push(utils.force(a).generator);
  };
  for (var i = 0; i < arguments.length; i++) {
    var arg = arguments[i];
    if (utils.isArray(arg)) {
      arg.forEach(append);
    } else {
      append(arg);
    }
  }

  return {
    generator: generator.oneof(generators),
    // TODO: make shrink
    shrink: shrink.noop,
    show: show.def,
  };
}

/**
  - `record(spec: { key: arbitrary a... }): arbitrary { key: a... }`

      Generates a javascript object with given record spec.
*/
function record(spec) {
  var forcedSpec = {};
  // TODO: use mapValues
  Object.keys(spec).forEach(function (k) {
    forcedSpec[k] = utils.force(spec[k]);
  });

  return {
    generator: generator.bless(function (size) {
      var res = {};
      Object.keys(spec).forEach(function (k) {
        res[k] = forcedSpec[k].generator(size);
      });
      return res;
    }),
    shrink: shrink.bless(function (value) {
      // TODO: use mapValues
      var shrinkSpec = {};
      Object.keys(forcedSpec).forEach(function (k) {
        shrinkSpec[k] = forcedSpec[k].shrink;
      });
      return shrink.record(shrinkSpec, value);
    }),
    show: function (m) {
      return "{" + Object.keys(m).map(function (k) {
        return k + ": " + forcedSpec[k].show(m[k]);
      }).join(", ") + "}";
    }
  };
}

module.exports = {
  nonshrink: nonshrink,
  pair: pair,
  tuple: tuple,
  array: array,
  nearray: nearray,
  map: map,
  oneof: oneof,
  record: record,
};

@@ -540,66 +540,47 @@
 %0A%7D%0A%0A
-/**%0A  - %60array(arb: arbitrary a): arbitrary (array a)%60%0A*/%0A
+function arrayImpl(flavour) %7B%0A  return 
 func
@@ -589,32 +589,34 @@
 on array(arb) %7B%0A
+  
   arb = utils.fo
@@ -636,32 +636,34 @@
 imitive.json);%0A%0A
+  
   return %7B%0A    g
@@ -653,32 +653,34 @@
    return %7B%0A    
+  
 generator: gener
@@ -683,22 +683,25 @@
 enerator
-.array
+%5Bflavour%5D
 (arb.gen
@@ -705,32 +705,34 @@
 generator),%0A    
+  
 shrink: shrink.a
@@ -729,22 +729,25 @@
 : shrink
-.array
+%5Bflavour%5D
 (arb.shr
@@ -744,32 +744,34 @@
 r%5D(arb.shrink),%0A
+  
     show: show.a
@@ -778,32 +778,39 @@
 rray(arb.show),%0A
+    %7D;%0A
   %7D;%0A%7D%0A%0A/**%0A  - 
@@ -810,18 +810,16 @@
 **%0A  - %60
-ne
 array(ar
@@ -863,215 +863,136 @@
 %0A*/%0A
-function nearray(arb) %7B%0A  arb = utils.force(arb %7C%7C primitive.json);%0A%0A  return %7B%0A    generator: generator.nearray(arb.generator),%0A    shrink: shrink.nearray(arb.shrink),%0A    show: show.array(arb.show),%0A  %7D;%0A%7D
+var array = arrayImpl(%22array%22);%0A%0A/**%0A  - %60nearray(arb: arbitrary a): arbitrary (array a)%60%0A*/%0Avar nearray = arrayImpl(%22nearray%22);
 %0A%0A/*

refactor array & nearray arbitrary