var assert = require("assert");
var _ = require("underscore");
var buildmessage = require('./buildmessage.js');
var utils = require('./utils.js');
var compiler = require('./compiler.js');
var archinfo = require('./archinfo.js');
var files = require('./files.js');
var catalog = require('./catalog.js');

function toArray (x) {
  if (_.isArray(x))
    return x;
  return x ? [x] : [];
}

function toArchArray (arch) {
  if (! _.isArray(arch)) {
    arch = arch ? [arch] : compiler.ALL_ARCHES;
  }
  arch = _.uniq(arch);
  arch = _.map(arch, mapWhereToArch);

  // avoid using _.each so as to not add more frames to skip
  for (var i = 0; i < arch.length; ++i) {
    var inputArch = arch[i];
    var isMatch = _.any(_.map(compiler.ALL_ARCHES, function (actualArch) {
      return archinfo.matches(actualArch, inputArch);
    }));
    if (! isMatch) {
      buildmessage.error(
        "Invalid 'where' argument: '" + inputArch + "'",
        // skip toArchArray in addition to the actual API function
        {useMyCaller: 1});
    }
  }
  return arch;
}

// We currently have a 1 to 1 mapping between 'where' and 'arch'.
// 'client' -> 'web'
// 'server' -> 'os'
// '*' -> '*'
function mapWhereToArch (where) {
  if (where === 'server') {
    return 'os';
  } else if (where === 'client') {
    return 'web';
  } else {
    return where;
  }
}

// Iterates over the list of target archs and calls f(arch) for all archs
// that match an element of self.allarchs.
function forAllMatchingArchs (archs, f) {
  _.each(archs, function (arch) {
    _.each(compiler.ALL_ARCHES, function (matchArch) {
      if (archinfo.matches(matchArch, arch)) {
        f(matchArch);
      }
    });
  });
}

/**
 * @name  PackageAPI
 * @class PackageAPI
 * @instanceName api
 * @global
 * @summary Type of the API object passed into the `Package.onUse` function.
 */
function PackageAPI (options) {
  var self = this;
  assert.ok(self instanceof PackageAPI);

  options = options || {};

  self.buildingIsopackets = !!options.buildingIsopackets;

  // source files used.  Map arch -> relPath -> {relPath, fileOptions}
  self.sources = {};

  // symbols exported
  self.exports = {};

  // packages used and implied (keys are 'package', 'unordered', and
  // 'weak').  an "implied" package is a package that will be used by a unibuild
  // which uses us.
  self.uses = {};
  self.implies = {};

  _.each(compiler.ALL_ARCHES, function (arch) {
    self.sources[arch] = {};
    self.exports[arch] = [];
    self.uses[arch] = [];
    self.implies[arch] = [];
  });

  self.releaseRecords = [];
}

_.extend(PackageAPI.prototype, {
  // Called when this package wants to make another package be
  // used. Can also take literal package objects, if you have
  // anonymous packages you want to use (eg, app packages)
  //
  // @param arch 'web', 'web.browser', 'web.cordova', 'server',
  // or an array of those.
  // The default is ['web', 'server'].
  //
  // options can include:
  //
  // - unordered: if true, don't require this package to load
  //   before us -- just require it to be loaded anytime. Also
  //   don't bring this package's imports into our
  //   namespace. If false, override a true value specified in
  //   a previous call to use for this package name. (A
  //   limitation of the current implementation is that this
  //   flag is not tracked per-environment or per-role.)  This
  //   option can be used to resolve circular dependencies in
  //   exceptional circumstances, eg, the 'meteor' package
  //   depends on 'handlebars', but all packages (including
  //   'handlebars') have an implicit dependency on
  //   'meteor'. Internal use only -- future support of this
  //   is not guaranteed. #UnorderedPackageReferences
  //
  // - weak: if true, don't require this package to load at all, but if
  //   it's going to load, load it before us.  Don't bring this
  //   package's imports into our namespace and don't allow us to use
  //   its plugins. (Has the same limitation as "unordered" that this
  //   flag is not tracked per-environment or per-role; this may
  //   change.)

  /**
   * @memberOf PackageAPI
   * @instance
   * @summary Depend on package `packagename`.
   * @locus package.js
   * @param {String|String[]} packageNames Packages being depended on.
   * Package names may be suffixed with an @version tag.
   *
   * In general, you must specify a package's version (e.g.,
   * `'accounts@1.0.0'` to use version 1.0.0 or a higher
   * compatible version (ex: 1.0.1, 1.5.0, etc.)  of the
   * `accounts` package). If you are sourcing core
   * packages from a Meteor release with `versionsFrom`, you may leave
   * off version names for core packages. You may also specify constraints,
   * such as `my:forms@=1.0.0` (this package demands `my:forms` at `1.0.0` exactly),
   * or `my:forms@1.0.0 || =2.0.1` (`my:forms` at `1.x.y`, or exactly `2.0.1`).
   * @param {String} [architecture] If you only use the package on the
   * server (or the client), you can pass in the second argument (e.g.,
   * `'server'`, `'client'`, `'web.browser'`, `'web.cordova'`) to specify
   * what architecture the package is used with.
   * @param {Object} [options]
   * @param {Boolean} options.weak Establish a weak dependency on a
   * package. If package A has a weak dependency on package B, it means
   * that including A in an app does not force B to be included too — but,
   * if B is included or by another package, then B will load before A.
   * You can use this to make packages that optionally integrate with or
   * enhance other packages if those packages are present.
   * When you weakly depend on a package you don't see its exports.
   * You can detect if the possibly-present weakly-depended-on package
   * is there by seeing if `Package.foo` exists, and get its exports
   * from the same place.
   * @param {Boolean} options.unordered It's okay to load this dependency
   * after your package. (In general, dependencies specified by `api.use`
   * are loaded before your package.) You can use this option to break
   * circular dependencies.
   */
  use: function (names, arch, options) {
    var self = this;

    // Support `api.use(package, {weak: true})` without arch.
    if (_.isObject(arch) && !_.isArray(arch) && !options) {
      options = arch;
      arch = null;
    }
    options = options || {};

    names = toArray(names);
    arch = toArchArray(arch);

    // A normal dependency creates an ordering constraint and a "if I'm
    // used, use that" constraint. Unordered dependencies lack the
    // former; weak dependencies lack the latter. There's no point to a
    // dependency that lacks both!
    if (options.unordered && options.weak) {
      buildmessage.error(
        "A dependency may not be both unordered and weak.",
        { useMyCaller: true });
      // recover by ignoring
      return;
    }

    // using for loop rather than underscore to help with useMyCaller
    for (var i = 0; i < names.length; ++i) {
      var name = names[i];
      try {
        var parsed = utils.parsePackageConstraint(name);
      } catch (e) {
        if (!e.versionParserError)
          throw e;
        buildmessage.error(e.message, {useMyCaller: true});
        // recover by ignoring
        continue;
      }

      forAllMatchingArchs(arch, function (a) {
        self.uses[a].push({
          package: parsed.package,
          constraint: parsed.constraintString,
          unordered: options.unordered || false,
          weak: options.weak || false
        });
      });
    }
  },

  // Called when this package wants packages using it to also use
  // another package.  eg, for umbrella packages which want packages
  // using them to also get symbols or plugins from their components.

  /**
   * @memberOf PackageAPI
   * @summary Give users of this package access to another package (by passing  in the string `packagename`) or a collection of packages (by passing in an  array of strings [`packagename1`, `packagename2`]
   * @locus package.js
   * @instance
   * @param {String|String[]} packageSpecs Name of a package, or array of package names, with an optional @version component for each.
   */
  imply: function (names, arch) {
    var self = this;

    // We currently disallow build plugins in debugOnly packages; but if
    // you could use imply in a debugOnly package, you could pull in the
    // build plugin from an implied package, which would have the same
    // problem as allowing build plugins directly in the package. So no
    // imply either!
    if (self.debugOnly) {
      buildmessage.error("can't use imply in debugOnly packages");
      // recover by ignoring
      return;
    }

    names = toArray(names);
    arch = toArchArray(arch);

    // using for loop rather than underscore to help with useMyCaller
    for (var i = 0; i < names.length; ++i) {
      var name = names[i];
      try {
        var parsed = utils.parsePackageConstraint(name);
      } catch (e) {
        if (!e.versionParserError)
          throw e;
        buildmessage.error(e.message, {useMyCaller: true});
        // recover by ignoring
        continue;
      }

      forAllMatchingArchs(arch, function (a) {
        // We don't allow weak or unordered implies, since the main
        // purpose of imply is to provide imports and plugins.
        self.implies[a].push({
          package: parsed.package,
          constraint: parsed.constraintString
        });
      });
    }
  },

  // Top-level call to add a source file to a package. It will
  // be processed according to its extension (eg, *.coffee
  // files will be compiled to JavaScript).

  /**
   * @memberOf PackageAPI
   * @instance
   * @summary Specify the source code for your package.
   * @locus package.js
   * @param {String|String[]} filename Name of the source file, or array of strings of source file names.
   * @param {String} [architecture] If you only want to export the file
   * on the server (or the client), you can pass in the second argument
   * (e.g., 'server', 'client', 'web.browser', 'web.cordova') to specify
   * what architecture the file is used with.
   */
  addFiles: function (paths, arch, fileOptions) {
    var self = this;

    paths = toArray(paths);
    arch = toArchArray(arch);

    // Convert Dos-style paths to Unix-style paths.
    // XXX it is possible to convert an already Unix-style path by mistake
    // and break it. e.g.: 'some\folder/anotherFolder' is a valid path
    // consisting of two components. #WindowsPathApi
    paths = _.map(paths, function (p) {
      if (p.indexOf('/') !== -1) {
        // it is already a Unix-style path most likely
        return p;
      }
      return files.convertToPosixPath(p, true);
    });

    var errors = [];
    _.each(paths, function (path) {
      forAllMatchingArchs(arch, function (a) {
        if (_.has(self.sources[a], path)) {
          errors.push("Duplicate source file: " + path);
          return;
        }
        var source = {relPath: path};
        if (fileOptions)
          source.fileOptions = fileOptions;
        self.sources[a][path] = source;
      });
    });

    // Spit out all the errors at the end, where the number of stack frames to
    // skip is just 1 instead of something like 7 from forAllMatchingArchs and
    // _.each.  Avoid using _.each here to keep stack predictable.
    for (var i = 0; i < errors.length; ++i) {
      buildmessage.error(errors[i], { useMyCaller: true });
    }
  },

  // Use this release to resolve unclear dependencies for this package. If
  // you don't fill in dependencies for some of your implies/uses, we will
  // look at the packages listed in the release to figure that out.

  /**
   * @memberOf PackageAPI
   * @instance
   * @summary Use versions of core packages from a release. Unless provided, all packages will default to the versions released along with `meteorRelease`. This will save you from having to figure out the exact versions of the core packages you want to use. For example, if the newest release of meteor is `METEOR@0.9.0` and it includes `jquery@1.0.0`, you can write `api.versionsFrom('METEOR@0.9.0')` in your package, and when you later write `api.use('jquery')`, it will be equivalent to `api.use('jquery@1.0.0')`. You may specify an array of multiple releases, in which case the default value for constraints will be the "or" of the versions from each release: `api.versionsFrom(['METEOR@0.9.0', 'METEOR@0.9.5'])` may cause `api.use('jquery')` to be interpreted as `api.use('jquery@1.0.0 || 2.0.0')`.
   * @locus package.js
   * @param {String | String[]} meteorRelease Specification of a release: track@version. Just 'version' (e.g. `"0.9.0"`) is sufficient if using the default release track `METEOR`.
   */
  versionsFrom: function (releases) {
    var self = this;

    // Packages in isopackets really ought to be in the core release, by
    // definition, so saying that they should use versions from another
    // release doesn't make sense. Moreover, if we're running from a
    // checkout, we build isopackets before we initialize catalog.official
    // (since we may need the ddp isopacket to refresh catalog.official),
    // so we wouldn't actually be able to interpret the release name
    // anyway.
    if (self.buildingIsopackets) {
      buildmessage.error(
        "packages in isopackets may not use versionsFrom");
      // recover by ignoring
      return;
    }

    releases = toArray(releases);

    // using for loop rather than underscore to help with useMyCaller
    for (var i = 0; i < releases.length; ++i) {
      var release = releases[i];

      // If you don't specify a track, use our default.
      if (release.indexOf('@') === -1) {
        release = catalog.DEFAULT_TRACK + "@" + release;
      }

      var relInf = release.split('@');
      if (relInf.length !== 2) {
        buildmessage.error("Release names in versionsFrom may not contain '@'.",
                           { useMyCaller: true });
        return;
      }
      var releaseRecord = catalog.official.getReleaseVersion(
        relInf[0], relInf[1]);
      if (!releaseRecord) {
        buildmessage.error("Unknown release "+ release,
                           { tags: { refreshCouldHelp: true } });
      } else {
        self.releaseRecords.push(releaseRecord);
      }
    }
  },

  // Export symbols from this package.
  //
  // @param symbols String (eg "Foo") or array of String
  // @param arch 'web', 'server', 'web.browser', 'web.cordova'
  // or an array of those.
  // The default is ['web', 'server'].
  // @param options 'testOnly', boolean.

  /**
   * @memberOf PackageAPI
   * @instance
   * @summary Export package-level variables in your package. The specified variables (declared without `var` in the source code) will be available to packages that use this package.
   * @locus package.js
   * @param {String} exportedObject Name of the object.
   * @param {String} [architecture] If you only want to export the object
   * on the server (or the client), you can pass in the second argument
   * (e.g., 'server', 'client', 'web.browser', 'web.cordova') to specify
   * what architecture the export is used with.
   */
  export: function (symbols, arch, options) {
    var self = this;

    // Support `api.export("FooTest", {testOnly: true})` without
    // arch.
    if (_.isObject(arch) && !_.isArray(arch) && !options) {
      options = arch;
      arch = null;
    }
    options = options || {};

    symbols = toArray(symbols);
    arch = toArchArray(arch);

    _.each(symbols, function (symbol) {
      // XXX be unicode-friendlier
      if (!symbol.match(/^([_$a-zA-Z][_$a-zA-Z0-9]*)$/)) {
        buildmessage.error("Bad exported symbol: " + symbol,
                           { useMyCaller: true });
        // recover by ignoring
        return;
      }
      forAllMatchingArchs(arch, function (w) {
        self.exports[w].push({name: symbol, testOnly: !!options.testOnly});
      });
    });
  }
});

// XXX COMPAT WITH 0.8.x
PackageAPI.prototype.add_files = PackageAPI.prototype.addFiles;

exports.PackageAPI = PackageAPI;


// See comments on minimize and maximize.

var minMax = function (solver, solution, costTerms, costWeights, options, isMin) {

var curCost = curSolution.getWeightedSum(costTerms, costWeights);

var optFormula = options && options.formula;

var weightedSum = (optFormula || Logic.weightedSum(costTerms, costWeights));

keep keep keep keep replace replace keep keep keep keep keep

// See comments on minimizeWeightedSum and maximizeWeightedSum.

var minMaxWS = function (solver, solution, costTerms, costWeights, options,

Package.describe({
  summary: "Logging facility.",
  version: '1.0.7'
});

Npm.depends({
  "cli-color": "0.2.3"
});

Npm.strip({
  "es5-ext": ["test/"]
});

Cordova.depends({
  'org.apache.cordova.console': '0.2.13'
});

Package.onUse(function (api) {
  api.export('Log');
  api.use(['underscore', 'ejson']);
  api.addFiles('logging.js');
  api.addFiles('logging_cordova.js', 'web.cordova');
});

Package.onTest(function (api) {
  api.use(['tinytest', 'underscore', 'ejson']);
  api.use('logging', ['client', 'server']);
  api.addFiles('logging_test.js', ['server', 'client']);
});


// if possible.  For minimization, the alternate strategy 'bottom-up' is

// available, which starts at 0 and tries ever higher costs until one

// works.  All strategies first try and see if a cost of 0 is possible.

Logic.Solver.prototype.minimize = function (solution, costTerms, costWeights,

return minMax(this, solution, costTerms, costWeights, options, true);

keep keep keep keep replace replace replace keep keep

// ("costTerms" is kind of a misnomer since they may be Formulas or Terms.)

Logic.Solver.prototype.minimizeWeightedSum = function (solution, costTerms,

return minMaxWS(this, solution, costTerms, costWeights, options, true);

Tinytest.add("logic-solver - require", function (test) {
  var s = new Logic.Solver;

  s.require('foo');
  test.equal(s._clauseData(), [[3]]);
  test.equal(s._clauseStrings(), ["foo"]);
  s.forbid('foo');
  test.equal(s._clauseData(), [[3], [-3]]);
  test.equal(s._clauseStrings(), ["foo", "-foo"]);

  s.require([['foo'], '-bar'], '--foo', 'foo');
  test.equal(s._clauseData(), [[3], [-3], [3], [-4], [3], [3]]);
  test.equal(s._clauseStrings(), ["foo", "-foo", "foo",
                                  "-bar", "foo", "foo"]);
});

Tinytest.add("logic-solver - _clauseStrings", function (test) {
  var s = new Logic.Solver;

  s.require('foo');

  test.equal(s._clauseStrings(), ["foo"]);
  s.require('-myPackage 1.0.0');
  test.equal(s._clauseStrings(), ["foo", '-"myPackage 1.0.0"']);
});

Tinytest.add("logic-solver - illegal NameTerms", function (test) {
  var s = new Logic.Solver;

  test.throws(function () {
    s.require('');
  });

  test.throws(function () {
    s.require('-');
  });

  test.throws(function () {
    s.require('0');
  });

  test.throws(function () {
    s.require('$x');
  });

  test.throws(function () {
    s.require('-1');
  });

  test.throws(function () {
    s.require('---123');
  });
});

Tinytest.add("logic-solver - toNameTerm, toNumTerm", function (test) {
  var s = new Logic.Solver;

  test.equal(s.toNumTerm("foo"), 3);
  test.equal(s.toNumTerm("-foo"), -3);

  test.equal(s.toNameTerm(3), "foo");
  test.equal(s.toNameTerm(-3), "-foo");

  test.equal(s.toNameTerm("-----foo"), "-foo");
});

var formatLines = function (stringArray) {
  return JSON.stringify(stringArray).replace(/","/g, '",\n "');
};

var checkClauses = function (test, f, expected) {
  check(f, Function);
  check(expected, [String]);
  var s = new Logic.Solver;
  f(s);
  test.equal(formatLines(s._clauseStrings()),
             formatLines(expected));
};

var runClauseTests = function (test, funcsAndExpecteds) {
  check(funcsAndExpecteds.length % 2, 0);
  for (var i = 0; i < funcsAndExpecteds.length; i++) {
    var f = funcsAndExpecteds[i];
    i++;
    var expected = funcsAndExpecteds[i];
    checkClauses(test, f, expected);
  }
};

Tinytest.add("logic-solver - bad NumTerms", function (test) {
  test.throws(function () {
    var s = new Logic.Solver;
    s.require(3);
  });

  test.throws(function () {
    var s = new Logic.Solver;
    s.require(-3);
  });

  test.throws(function () {
    var s = new Logic.Solver;
    s.require(0);
  });

  test.throws(function () {
    var s = new Logic.Solver;
    s.require(Logic.or(3));
  });
});

Tinytest.add("logic-solver - true and false", function (test) {
  runClauseTests(test, [
    // Clauses that forbid $F and require $T are automatically
    // generated as the first two clauses.  Using each of them
    // causes the relevant clause to be included in the output.
    function (s) {
      s.require(Logic.or(Logic.TRUE, Logic.not(Logic.TRUE)));
    },
    ["$T", "$T v -$T"],
    function (s) {
      s.require(Logic.or(Logic.not(Logic.TRUE),
                         Logic.not(Logic.FALSE)));
    },
    ["-$F", "$T", "-$T v -$F"],
    // requiring or forbidding $T, $F, or the negation of one
    // of those is optimizated.  this is helpful when formulas
    // expand to one of these (e.g. Logic.and() => $T => []).
    function (s) { s.require(Logic.TRUE); }, [],
    function (s) { s.require(Logic.FALSE); }, [""],
    function (s) { s.require(Logic.not(Logic.TRUE)); }, [""],
    function (s) { s.require(Logic.not(Logic.FALSE)); }, [],
    function (s) { s.forbid(Logic.TRUE); }, [""],
    function (s) { s.forbid(Logic.FALSE); }, [],
    function (s) { s.forbid(Logic.not(Logic.TRUE)); }, [],
    function (s) { s.forbid(Logic.not(Logic.FALSE)); }, [""]
  ]);
});

Tinytest.add("logic-solver - Logic.or", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.or('A', 'B'));
    },
    ["A v B"],
    function (s) {
      s.require(Logic.or(['A', 'B']));
    },
    ["A v B"],
    function (s) {
      s.require(Logic.or(['A'], ['B']));
    },
    ["A v B"],
    function (s) {
      s.require('A');
      s.require(Logic.or('-C', 'D', 3));
    },
    ["A", "-C v D v A"],
    function (s) {
      s.forbid(Logic.or('A', '-B'));
    },
    ["-A", "B"],
    function (s) {
      s.forbid(Logic.or());
    },
    [],
    function (s) {
      s.require(Logic.or());
    },
    [""]
  ]);
});

Tinytest.add("logic-solver - Formula sharing", function (test) {
  var f = Logic.or("A", "B");
  var s1 = new Logic.Solver;
  var s2 = new Logic.Solver;

  s1.require("X");
  s1.require(f);

  s2.forbid(f);

  test.equal(s1._clauseData(), [[3], [4, 5]]);
  test.equal(s2._clauseData(), [[-3], [-4]]);
});

Tinytest.add("logic-solver - nested Logic.or", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.or(Logic.or("A", "B"), Logic.or("C", "D")));
    },
    ["A v B v -$or1", "C v D v -$or2", "$or1 v $or2"]
  ]);
});

Tinytest.add("logic-solver - Logic.not term", function (test) {
  test.equal(Logic.not("foo"), "-foo");
  test.equal(Logic.not("-foo"), "foo");
  test.equal(Logic.not("--foo"), "-foo");
  test.equal(Logic.not(1), -1);
  test.equal(Logic.not(-1), 1);
});

Tinytest.add("logic-solver - Logic.not formula", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.not(Logic.or("A", "B")));
    },
    ["-A", "-B"],
    function (s) {
      s.forbid(Logic.not(Logic.or("A", "B")));
    },
    ["A v B"],
    function (s) {
      s.require(Logic.or(Logic.not(Logic.or("A", "B")), "C"));
    },
    ["-A v $or1", "-B v $or1", "-$or1 v C"]
  ]);
});

Tinytest.add("logic-solver - Require/forbid after formula gen", function (test) {
  runClauseTests(test, [
    function (s) {
      // Use a formula in the positive and then require it.  Requiring
      // the formula does not regenerate its clauses, it just requires
      // the formula's variable ($or1).
      var f = Logic.or("A", "B");
      s.require(Logic.or(f, "C"));
      s.require(f);
    },
    ["A v B v -$or1","$or1 v C","$or1"]
  ]);

  runClauseTests(test, [
    function (s) {
      // Use a formula in the posiive and then forbid it.
      // Forbidding a formula that has not been used in the
      // negative before requires generating new clauses.
      var f = Logic.or("A", "B");
      s.require(Logic.or(f, "C"));
      s.forbid(f);
    },
    ["A v B v -$or1","$or1 v C","-A v $or1","-B v $or1","-$or1"]
  ]);

  runClauseTests(test, [
    function (s) {
      // Use a formula in the negative and then forbid it.
      var f = Logic.or("A", "B");
      s.require(Logic.or(Logic.not(f), "C"));
      s.forbid(f);
    },
    ["-A v $or1","-B v $or1","-$or1 v C","-$or1"]
  ]);

  runClauseTests(test, [
    function (s) {
      // Use a formula in the negative and then require it.
      var f = Logic.or("A", "B");
      s.require(Logic.or(Logic.not(f), "C"));
      s.require(f);
    },
    ["-A v $or1","-B v $or1","-$or1 v C","A v B v -$or1","$or1"]
  ]);

  runClauseTests(test, [
    function (s) {
      var f = Logic.or("A", "B");
      s.require(Logic.and(f, "C"));
      s.require(f);
    },
    // Arguments to AND are generated in place, meaning that if `f`
    // is used elsewhere, its clauses will be generated twice.
    // Oh well.  It's a trade-off.  The same applies to OR when
    // generating the false case.
    ["A v B",
     "C",
     "A v B"]
  ]);
});

Tinytest.add("logic-solver - Logic.and", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.and('A', 'B'));
    },
    ["A", "B"],
    function (s) {
      s.require(Logic.and(['A', 'B']));
    },
    ["A", "B"],
    function (s) {
      s.require(Logic.and(['A'], ['-B'], 'C'));
    },
    ["A", "-B", "C"],
    function (s) {
      s.forbid(Logic.and('A', '-B', 'C'));
    },
    ["-A v B v -C"],
    function (s) {
      s.forbid(Logic.and());
    },
    [""],
    function (s) {
      s.require(Logic.and());
    },
    [],
    function (s) {
      s.require(Logic.or(Logic.and(Logic.or("A", "B"),
                                   Logic.or("-A", "C")),
                         "-D"));
    },
    ["A v B v -$and1",
     "-A v C v -$and1",
     "$and1 v -D"],
    function (s) {
      s.require(Logic.or(Logic.not(Logic.and(Logic.or("A", "B"),
                                             Logic.or("-A", "C"))),
                         "-D"));
    },
    ["-$or1 v -$or2 v $and1",
     "A v $or2",
     "-C v $or2",
     "-A v $or1",
     "-B v $or1",
     "-$and1 v -D"]
  ]);
});

Tinytest.add("logic-solver - Logic.xor", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.xor()); },
    [""],
    function (s) {
      s.forbid(Logic.xor()); },
    [],
    function (s) {
      s.require(Logic.or(Logic.xor(), Logic.xor())); },
    ["-$F", "$F v $F"],
    function (s) {
      s.require(Logic.xor("A")); },
    ["A"],
    function (s) {
      s.forbid(Logic.xor("A")); },
    ["-A"],
    function (s) {
      s.require(Logic.xor("A", "B")); },
    ["A v B", "-A v -B"],
    function (s) {
      s.forbid(Logic.xor("A", "B")); },
    ["A v -B", "-A v B"],
    function (s) {
      s.require(Logic.xor(["A", []], ["B"], [])); },
    ["A v B", "-A v -B"],
    function (s) {
      s.require(Logic.xor("A", "B", "C")); },
    ["A v B v C", "A v -B v -C", "-A v B v -C", "-A v -B v C"],
    function (s) {
      s.forbid(Logic.xor("A", "B", "C"));  },
    ["-A v -B v -C", "-A v B v C", "A v -B v C", "A v B v -C"],
    function (s) {
      s.require(Logic.xor("A", "B", "C", "D")); },
    ["A v B v C v -$xor1",
     "A v -B v -C v -$xor1",
     "-A v B v -C v -$xor1",
     "-A v -B v C v -$xor1",
     "$xor1 v D",
     "-A v -B v -C v $xor1",
     "-A v B v C v $xor1",
     "A v -B v C v $xor1",
     "A v B v -C v $xor1",
     "-$xor1 v -D"],
    function (s) {
      s.forbid(Logic.xor("A", "B", "C", "D")); },
    ["A v B v C v -$xor1",
     "A v -B v -C v -$xor1",
     "-A v B v -C v -$xor1",
     "-A v -B v C v -$xor1",
     "$xor1 v -D",
     "-A v -B v -C v $xor1",
     "-A v B v C v $xor1",
     "A v -B v C v $xor1",
     "A v B v -C v $xor1",
     "-$xor1 v D"],
    function (s) {
      s.require(Logic.xor("A", "B", "C", "D", "E")); },
    ["A v B v C v -$xor1",
     "A v -B v -C v -$xor1",
     "-A v B v -C v -$xor1",
     "-A v -B v C v -$xor1",
     "D v E v -$xor2",
     "-D v -E v -$xor2",
     "$xor1 v $xor2",
     "-A v -B v -C v $xor1",
     "-A v B v C v $xor1",
     "A v -B v C v $xor1",
     "A v B v -C v $xor1",
     "D v -E v $xor2",
     "-D v E v $xor2",
     "-$xor1 v -$xor2"],
    function (s) {
      s.forbid(Logic.xor("A", "B", "C", "D", "E")); },
    ["A v B v C v -$xor1",
     "A v -B v -C v -$xor1",
     "-A v B v -C v -$xor1",
     "-A v -B v C v -$xor1",
     "D v -E v $xor2",
     "-D v E v $xor2",
     "$xor1 v -$xor2",
     "-A v -B v -C v $xor1",
     "-A v B v C v $xor1",
     "A v -B v C v $xor1",
     "A v B v -C v $xor1",
     "D v E v -$xor2",
     "-D v -E v -$xor2",
     "-$xor1 v $xor2"]
  ]);
});

Tinytest.add("logic-solver - require/forbid generation", function (test) {
  runClauseTests(test, [
    function (s) {
      var f = Logic.and("A", "B");
      s.require(Logic.or(f, "C"));
      s.forbid(f);
    },
    ["A v -$and1", "B v -$and1", "$and1 v C", "-A v -B v $and1", "-$and1"],
    function (s) {
      var f = Logic.and("A", "B");
      s.require(Logic.or(Logic.not(f), "C"));
      s.require(f);
    },
    ["-A v -B v $and1", "-$and1 v C", "A v -$and1", "B v -$and1", "$and1"],
    function (s) {
      var f = Logic.and("A", "B");
      s.require(f);
      s.require(f);
    },
    ["A", "B"],
    function (s) {
      var f = Logic.and("A", "B");
      s.forbid(f);
      s.forbid(f);
    },
    ["-A v -B"],
    function (s) {
      var f = Logic.and("A", "B");
      s.require(f);
      s.forbid(f);
      s.forbid(f);
    },
    ["A", "B", ""],
    function (s) {
      var f = Logic.and("A", "B");
      s.forbid(f);
      s.require(f);
      s.require(f);
    },
    ["-A v -B", ""],
    function (s) {
      var f = Logic.and("A", "B");
      s.require(f);
      s.require(Logic.or(f, "C"));
    },
    ["$T", "A", "B", "$T v C"],
    function (s) {
      var f = Logic.and("A", "B");
      s.require(f);
      s.require(Logic.or(Logic.not(f), "C"));
    },
    ["$T", "A", "B", "-$T v C"],
    function (s) {
      var f = Logic.and("A", "B");
      s.forbid(f);
      s.require(Logic.or(Logic.not(f), "C"));
    },
    ["-$F", "-A v -B", "-$F v C"],
    function (s) {
      var f = Logic.and("A", "B");
      s.require(f);
      s.forbid(f);
      s.require(Logic.or(f, "C"));
    },
    ["$T", "A", "B", "", "$T v C"],
    function (s) {
      var f = Logic.and("A", "B");
      s.forbid(f);
      s.require(f);
      s.require(Logic.or(f, "C"));
    },
    ["$T", "-A v -B", "", "$T v C"]
  ]);
});

Tinytest.add("logic-solver - Logic.atMostOne", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.atMostOne()); },
    [],
    function (s) {
      s.forbid(Logic.atMostOne()); },
    [""],
    function (s) {
      s.require(Logic.atMostOne("A")); },
    [],
    function (s) {
      s.forbid(Logic.atMostOne("A")); },
    [""],
    function (s) {
      s.require(Logic.atMostOne("A", "B")); },
    ["-A v -B"],
    function (s) {
      s.forbid(Logic.atMostOne("A", "B")); },
    ["A", "B"],
    function (s) {
      s.require(Logic.atMostOne("A", "B", "C")); },
    ["-A v -B", "-A v -C", "-B v -C"],
    function (s) {
      s.forbid(Logic.atMostOne("A", "B", "C")); },
    ["A v B", "A v C", "B v C"],
    function (s) {
      s.require(Logic.atMostOne("A", "B", "C", "D")); },
    // If D is true, then all of A,B,C must be false.
    // Two of A,B,C must be false.
    ["-A v $or1",
     "-B v $or1",
     "-C v $or1",
     "-$or1 v -D",
     "-A v -B",
     "-A v -C",
     "-B v -C"],
    function (s) {
      s.forbid(Logic.atMostOne("A", "B", "C", "D")); },
    // If any two of A,B,C are false (lines 3,4,5), then we'll need
    // one of A,B,C and D to be true (lines 1,2 by implication of
    // line 6).  (This isn't the reasoning that generated the clauses,
    // but it's one way to think of it.)
    ["A v B v C v $atMostOne1",
     "D v $atMostOne1",
     "A v B v $atMostOne2",
     "A v C v $atMostOne2",
     "B v C v $atMostOne2",
     "-$atMostOne1 v -$atMostOne2"],
    function (s) {
      s.require(Logic.atMostOne("A", "B", "C", "D", "E")); },
    ["-A v $or1",
     "-B v $or1",
     "-C v $or1",
     "-D v $or2",
     "-E v $or2",
     "-$or1 v -$or2",
     "-A v -B",
     "-A v -C",
     "-B v -C",
     "-D v -E"],
    function (s) {
      s.forbid(Logic.atMostOne("A", "B", "C", "D", "E")); },
    ["A v B v C v $atMostOne1",
     "D v E v $atMostOne1",
     "A v B v $atMostOne2",
     "A v C v $atMostOne2",
     "B v C v $atMostOne2",
     "D v $atMostOne3",
     "E v $atMostOne3",
     "-$atMostOne1 v -$atMostOne2 v -$atMostOne3"]
  ]);
});

Tinytest.add("logic-solver - Logic.implies, Logic.equiv", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.implies("A", "B")); },
    ["-A v B"],
    function (s) {
      s.forbid(Logic.implies("A", "B")); },
    ["A", "-B"],
    function (s) {
      s.require(Logic.or(Logic.implies("A", "B"), "C")); },
    ["-A v B v -$implies1", "$implies1 v C"],
    function (s) {
      s.require(Logic.or(Logic.implies(Logic.or("A", "D"), "B"), "C")); },
    ["-$or1 v B v -$implies1",
     "-A v $or1",
     "-D v $or1",
     "$implies1 v C"],
    function (s) {
      s.require(Logic.equiv("A", "B")); },
    ["A v -B",
     "-A v B"],
    function (s) {
      s.forbid(Logic.equiv("A", "B")); },
    ["A v B",
     "-A v -B"],
    function (s) {
      s.require(Logic.equiv(Logic.or("A", "B"),
                           Logic.or("C", "D"))); },
    ["A v B v -$or1",
     "-C v $or2",
     "-D v $or2",
     "$or1 v -$or2",
     "-A v $or1",
     "-B v $or1",
     "C v D v -$or2",
     "-$or1 v $or2"]
  ]);
});

Tinytest.add("logic-solver - Logic.exactlyOne", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.exactlyOne()); },
    [""],
    function (s) {
      s.forbid(Logic.exactlyOne()); },
    [],
    function (s) {
      s.require(Logic.exactlyOne("A")); },
    ["A"],
    function (s) {
      s.forbid(Logic.exactlyOne("A")); },
    ["-A"],
    function (s) {
      s.require(Logic.exactlyOne("A", "B")); },
    ["A v B", "-A v -B"],
    function (s) {
      s.forbid(Logic.exactlyOne("A", "B")); },
    ["A v -B", "-A v B"],
    function (s) {
      s.require(Logic.exactlyOne("A", "B", "C")); },
    ["-A v -B",
     "-A v -C",
     "-B v -C",
     "A v B v C"],
    function (s) {
      s.forbid(Logic.exactlyOne("A", "B", "C")); },
    ["A v B v $atMostOne1",
     "A v C v $atMostOne1",
     "B v C v $atMostOne1",
     "-A v $or1",
     "-B v $or1",
     "-C v $or1",
     "-$atMostOne1 v -$or1"]
  ]);
});

var equalBitFormulas = function (test, bits1, bits2) {
  test.isTrue(bits1 instanceof Logic.Bits);
  test.isTrue(bits2 instanceof Logic.Bits);
  // the elements of bits1 and bits2 will have to be
  // terms (or the same Formula objects) for this to
  // compare by value
  test.equal(bits1.bits, bits2.bits);
};

Tinytest.add("logic-solver - Logic.constantBits", function (test) {
  equalBitFormulas(test, Logic.constantBits(0), new Logic.Bits([]));
  equalBitFormulas(test, Logic.constantBits(1), new Logic.Bits(["$T"]));
  equalBitFormulas(test, Logic.constantBits(2), new Logic.Bits(["$F", "$T"]));
  equalBitFormulas(test, Logic.constantBits(3), new Logic.Bits(["$T", "$T"]));
  equalBitFormulas(test, Logic.constantBits(4), new Logic.Bits(["$F", "$F", "$T"]));
  equalBitFormulas(test, Logic.constantBits(5), new Logic.Bits(["$T", "$F", "$T"]));
});

Tinytest.add("logic-solver - Logic.equalBits", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits([]),
                                new Logic.Bits([]))); },
    [],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits([]),
                               new Logic.Bits([]))); },
    [""],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits(["A"]),
                                new Logic.Bits([]))); },
    ["-A"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits([]),
                                new Logic.Bits(["A"]))); },
    ["-A"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits(["A"]),
                               new Logic.Bits([]))); },
    ["A"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits([]),
                               new Logic.Bits(["A"]))); },
    ["A"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits(["A", "B", "C"]),
                                new Logic.Bits([]))); },
    ["-A", "-B", "-C"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits([]),
                                new Logic.Bits(["A", "B", "C"]))); },
    ["-A", "-B", "-C"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits(["A", "B", "C"]),
                               new Logic.Bits([]))); },
    ["A v B v C"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits([]),
                               new Logic.Bits(["A", "B", "C"]))); },
    ["A v B v C"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits(["A"]),
                                new Logic.Bits(["B"]))); },
    ["A v -B", "-A v B"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits(["A"]),
                               new Logic.Bits(["B"]))); },
    ["A v B", "-A v -B"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits(["A", "B"]),
                                new Logic.Bits(["X", "Y"]))); },
    ["A v -X", "-A v X",
     "B v -Y", "-B v Y"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits(["A", "B"]),
                               new Logic.Bits(["X", "Y"]))); },
    ["A v X v $equiv1",
     "-A v -X v $equiv1",
     "B v Y v $equiv2",
     "-B v -Y v $equiv2",
     "-$equiv1 v -$equiv2"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits(["A", "B"]),
                                new Logic.Bits(["X"]))); },
    ["A v -X", "-A v X", "-B"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits(["A", "B"]),
                               new Logic.Bits(["X"]))); },
    ["A v X v $equiv1",
     "-A v -X v $equiv1",
     "-$equiv1 v B"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits(["A"]),
                                new Logic.Bits(["X", "Y"]))); },
    ["A v -X", "-A v X", "-Y"],
    function (s) {
      s.forbid(Logic.equalBits(new Logic.Bits(["A"]),
                               new Logic.Bits(["X", "Y"]))); },
    ["A v X v $equiv1",
     "-A v -X v $equiv1",
     "-$equiv1 v Y"],
    function (s) {
      s.require(Logic.equalBits(new Logic.Bits([Logic.or("A", "B")]),
                                new Logic.Bits([Logic.or("C", "D")]))); },
    ["A v B v -$or1",
     "-C v $or2",
     "-D v $or2",
     "$or1 v -$or2",
     "-A v $or1",
     "-B v $or1",
     "C v D v -$or2",
     "-$or1 v $or2"]
  ]);
});

Tinytest.add("logic-solver - Logic.lessThan[OrEqual]", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits([]),
                               new Logic.Bits([]))); },
    [""],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits([]),
                                      new Logic.Bits([]))); },
    [],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits([]),
                              new Logic.Bits([]))); },
    [],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits([]),
                                     new Logic.Bits([]))); },
    [""],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits(["A"]),
                               new Logic.Bits([]))); },
    [""],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits(["A"]),
                                      new Logic.Bits([]))); },
    ["-A"],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits(["A"]),
                              new Logic.Bits([]))); },
    [],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits(["A"]),
                                     new Logic.Bits([]))); },
    ["A"],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits([]),
                               new Logic.Bits(["A"]))); },
    ["A"],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits([]),
                                      new Logic.Bits(["A"]))); },
    [],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits([]),
                              new Logic.Bits(["A"]))); },
    ["-A"],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits([]),
                                     new Logic.Bits(["A"]))); },
    [""],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits(["A"]),
                               new Logic.Bits(["B"]))); },
    ["-A v B",
     "A v B",
     "-A v -B"],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits(["A"]),
                                      new Logic.Bits(["B"]))); },
    ["-A v B"],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits(["A"]),
                              new Logic.Bits(["B"]))); },
    ["-B v A"],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits(["A"]),
                                     new Logic.Bits(["B"]))); },
    ["-B v A",
     "B v A",
     "-B v -A"],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits(["B", "A"]),
                               new Logic.Bits(["Y", "X"]))); },
    ["-A v X",
     "A v X v -$xor1",
     "-A v -X v -$xor1",
     "$xor1 v -B v Y",
     "B v Y v -$xor2",
     "-B v -Y v -$xor2",
     "$xor2 v $xor1"],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits(["B", "A"]),
                                      new Logic.Bits(["Y", "X"]))); },
    ["-A v X",
     "A v X v -$xor1",
     "-A v -X v -$xor1",
     "$xor1 v -B v Y"],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits(["B", "A"]),
                              new Logic.Bits(["Y", "X"]))); },
    ["-X v A",
     "X v A v -$xor1",
     "-X v -A v -$xor1",
     "$xor1 v -Y v B"],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits(["B", "A"]),
                                     new Logic.Bits(["Y", "X"]))); },
    ["-X v A",
     "X v A v -$xor1",
     "-X v -A v -$xor1",
     "$xor1 v -Y v B",
     "Y v B v -$xor2",
     "-Y v -B v -$xor2",
     "$xor2 v $xor1"],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits(["A"]),
                               new Logic.Bits(["Y", "X"]))); },
    ["X v -A v Y",
     "A v Y v -$xor1",
     "-A v -Y v -$xor1",
     "$xor1 v X"],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits(["A"]),
                                      new Logic.Bits(["Y", "X"]))); },
    ["X v -A v Y"],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits(["A"]),
                              new Logic.Bits(["Y", "X"]))); },
    ["-X",
     "-Y v A"],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits(["A"]),
                                     new Logic.Bits(["Y", "X"]))); },
    ["-X",
     "-Y v A",
     "Y v A",
     "-Y v -A"],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits(["B", "A"]),
                               new Logic.Bits(["X"]))); },
    ["-A",
     "-B v X",
     "B v X",
     "-B v -X"],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits(["B", "A"]),
                                      new Logic.Bits(["X"]))); },
    ["-A",
     "-B v X"],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits(["B", "A"]),
                              new Logic.Bits(["X"]))); },
    ["A v -X v B"],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits(["B", "A"]),
                                     new Logic.Bits(["X"]))); },
    ["A v -X v B",
     "X v B v -$xor1",
     "-X v -B v -$xor1",
     "$xor1 v A"],
    function (s) {
      s.require(Logic.lessThan(new Logic.Bits(["C", "B", "A"]),
                               new Logic.Bits(["Z", "Y", "X"]))); },
    ["-A v X",
     "A v X v -$xor1",
     "-A v -X v -$xor1",
     "$xor1 v -B v Y",
     "B v Y v -$xor2",
     "-B v -Y v -$xor2",
     "$xor2 v $xor1 v -C v Z",
     "C v Z v -$xor3",
     "-C v -Z v -$xor3",
     "$xor3 v $xor2 v $xor1"],
    function (s) {
      s.require(Logic.lessThanOrEqual(new Logic.Bits(["C", "B", "A"]),
                                      new Logic.Bits(["Z", "Y", "X"]))); },
    ["-A v X",
     "A v X v -$xor1",
     "-A v -X v -$xor1",
     "$xor1 v -B v Y",
     "B v Y v -$xor2",
     "-B v -Y v -$xor2",
     "$xor2 v $xor1 v -C v Z"],
    function (s) {
      s.forbid(Logic.lessThan(new Logic.Bits(["C", "B", "A"]),
                              new Logic.Bits(["Z", "Y", "X"]))); },
    ["-X v A",
     "X v A v -$xor1",
     "-X v -A v -$xor1",
     "$xor1 v -Y v B",
     "Y v B v -$xor2",
     "-Y v -B v -$xor2",
     "$xor2 v $xor1 v -Z v C"],
    function (s) {
      s.forbid(Logic.lessThanOrEqual(new Logic.Bits(["C", "B", "A"]),
                                     new Logic.Bits(["Z", "Y", "X"]))); },
    ["-X v A",
     "X v A v -$xor1",
     "-X v -A v -$xor1",
     "$xor1 v -Y v B",
     "Y v B v -$xor2",
     "-Y v -B v -$xor2",
     "$xor2 v $xor1 v -Z v C",
     "Z v C v -$xor3",
     "-Z v -C v -$xor3",
     "$xor3 v $xor2 v $xor1"]
  ]);
});

Tinytest.add("logic-solver - half/full sum/carry", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(new Logic.HalfAdderSum("A", "B")); },
    ["A v B", "-A v -B"],
    function (s) {
      s.forbid(new Logic.HalfAdderSum("A", "B")); },
    ["A v -B", "-A v B"],
    function (s) {
      s.require(Logic.or(new Logic.HalfAdderSum("A", "B"), "C")); },
    ["A v B v -$hsum1",
     "-A v -B v -$hsum1",
     "$hsum1 v C"],
    function (s) {
      s.require(new Logic.HalfAdderCarry("A", "B")); },
    ["A", "B"],
    function (s) {
      s.forbid(new Logic.HalfAdderCarry("A", "B")); },
    ["-A v -B"],
    function (s) {
      s.require(Logic.or(new Logic.HalfAdderCarry("A", "B"), "C")); },
    ["A v -$hcarry1",
     "B v -$hcarry1",
     "$hcarry1 v C"]
  ]);

  runClauseTests(test, [
    function (s) {
      s.require(new Logic.FullAdderSum("A", "B", "C")); },
    ["A v B v C",
     "A v -B v -C",
     "-A v B v -C",
     "-A v -B v C"],
    function (s) {
      s.forbid(new Logic.FullAdderSum("A", "B", "C")); },
    ["-A v -B v -C",
     "-A v B v C",
     "A v -B v C",
     "A v B v -C"],
    function (s) {
      s.require(Logic.or(new Logic.FullAdderSum("A", "B", "C"), "D")); },
    ["A v B v C v -$fsum1",
     "A v -B v -C v -$fsum1",
     "-A v B v -C v -$fsum1",
     "-A v -B v C v -$fsum1",
     "$fsum1 v D"],
    function (s) {
      s.require(new Logic.FullAdderCarry("A", "B", "C")); },
    ["A v B",
     "A v C",
     "B v C"],
    function (s) {
      s.forbid(new Logic.FullAdderCarry("A", "B", "C")); },
    ["-A v -B",
     "-A v -C",
     "-B v -C"],
    function (s) {
      s.require(Logic.or(new Logic.FullAdderCarry("A", "B", "C"), "D")); },
    ["A v B v -$fcarry1",
     "A v C v -$fcarry1",
     "B v C v -$fcarry1",
     "$fcarry1 v D"]
  ]);
});

Tinytest.add("logic-solver - sum of terms", function (test) {
  runClauseTests(test, [
    function (s) {
      s.require(
        // XY = A + B + C
        Logic.equalBits(new Logic.Bits(["Y", "X"]),
                        Logic.sum("A", "B", "C")));
    },
    ["-A v -B v -C v $fsum1",
     "-A v B v C v $fsum1",
     "A v -B v C v $fsum1",
     "A v B v -C v $fsum1",
     "Y v -$fsum1",
     "A v B v C v -$fsum1",
     "A v -B v -C v -$fsum1",
     "-A v B v -C v -$fsum1",
     "-A v -B v C v -$fsum1",
     "-Y v $fsum1",
     "-A v -B v $fcarry1",
     "-A v -C v $fcarry1",
     "-B v -C v $fcarry1",
     "X v -$fcarry1",
     "A v B v -$fcarry1",
     "A v C v -$fcarry1",
     "B v C v -$fcarry1",
     "-X v $fcarry1"],
    function (s) {
      s.require(
        // AB + C = XYZ
        Logic.equalBits(new Logic.Bits(["Z", "Y", "X"]),
                        Logic.sum(new Logic.Bits(["B", "A"]), "C")));
    },
    ["B v -C v $hsum1",
     "-B v C v $hsum1",
     "Z v -$hsum1",
     "B v C v -$hsum1",
     "-B v -C v -$hsum1",
     "-Z v $hsum1",
     "A v -$hcarry2 v $hsum2",
     "-A v $hcarry2 v $hsum2",
     "B v -$hcarry2",
     "C v -$hcarry2",
     "-B v -C v $hcarry2",
     "Y v -$hsum2",
     "A v $hcarry2 v -$hsum2",
     "-A v -$hcarry2 v -$hsum2",
     "-Y v $hsum2",
     "-A v -$hcarry2 v $hcarry1",
     "X v -$hcarry1",
     "A v -$hcarry1",
     "B v -$hcarry1",
     "C v -$hcarry1",
     "-X v $hcarry1"],
    function (s) {
      s.require(
        // 8X + 15Y = ZABCDE
        Logic.equalBits(new Logic.Bits(["E", "D", "C", "B", "A", "Z"]),
                        Logic.weightedSum(["X", "Y"], [8, 15])));
    },
    // C, D, and E all = Y
    // AB = X + Y
    // Z = 0
    ["E v -Y",
     "-E v Y",
     "D v -Y",
     "-D v Y",
     "C v -Y",
     "-C v Y",
     "X v -Y v $hsum1",
     "-X v Y v $hsum1",
     "B v -$hsum1",
     "X v Y v -$hsum1",
     "-X v -Y v -$hsum1",
     "-B v $hsum1",
     "-X v -Y v $hcarry1",
     "A v -$hcarry1",
     "X v -$hcarry1",
     "Y v -$hcarry1",
     "-A v $hcarry1",
     "-Z"],
    function (s) {
      // A + B < 2
      s.require(Logic.lessThan(Logic.sum("A", "B"), Logic.constantBits(2)));
    },
    ["-$F",
     "$T",
     "-A v -B v $hcarry1",
     "-$hcarry1 v $T",
     "$hcarry1 v $T v -$xor1",
     "-$hcarry1 v -$T v -$xor1",
     "A v -$hcarry1",
     "B v -$hcarry1",
     "A v -B v $hsum1",
     "-A v B v $hsum1",
     "$xor1 v -$hsum1 v $F",
     "$hsum1 v $F v -$xor2",
     "-$hsum1 v -$F v -$xor2",
     "A v B v -$hsum1",
     "-A v -B v -$hsum1",
     "$xor2 v $xor1"]
  ]);
});

Tinytest.add("logic-solver - MiniSat", function (test) {
  var M = new Logic._MiniSat;
  // Unique solution is (1,2,3,4) = (0,1,0,0)
  test.isTrue(M.addClause([-4]));
  test.isTrue(M.addClause([-1, -2]));
  test.isTrue(M.addClause([4, -1, 2]));
  test.isTrue(M.addClause([1, 2, 3]));
  test.isTrue(M.addClause([1, 2, -3, 4]));
  test.isTrue(M.addClause([1, -2, -3]));
  test.isTrue(M.solve());
  test.equal(M.getSolution(), [null, false, true, false, false]);
  M.addClause([1, -2, 3, 4]);
  test.isFalse(M.solve());
  test.isFalse(M.addClause([4]));
});

Tinytest.add("logic-solver - MiniSat solveAssuming", function (test) {
  var M = new Logic._MiniSat;
  M.ensureVar(1);
  test.isTrue(M.solveAssuming(1));
  test.equal(M.getSolution(), [null, true]);
  test.isTrue(M.solveAssuming(1));
  test.equal(M.getSolution(), [null, true]);
  test.isTrue(M.addClause([-1]));
  test.isTrue(M.addClause([2, -2]));
  test.isTrue(M.solve());
  test.equal(M.getSolution(), [null, false, false]); // empirically
  test.isTrue(M.solveAssuming(2));
  test.equal(M.getSolution(), [null, false, true]);
  test.isTrue(M.solve());
});

Tinytest.add("logic-solver - simple solve", function (test) {
  var s = new Logic.Solver;
  // Unique solution is (A,B,C,D) = (0,1,0,0)
  s.require("-D");
  s.require(Logic.or("-A", "-B"));
  s.require(Logic.or("D", "-A", "B"));
  s.require(Logic.or("A", "B", "C"));
  s.require(Logic.or("A", "B", "-C", "D"));
  s.require(Logic.or("A", "-B", "-C"));
  var sol = s.solve();
  test.equal(s._minisat._clauses.length, 8); // includes "$T" and "-$F"
  test.isTrue(sol);
  test.equal(sol.getMap(), {
    A: false, B: true, C: false, D: false
  });
  s.require(Logic.or("A", "-B", "C", "D"));
  var sol2 = s.solve();
  test.isFalse(sol2);
  // make sure we only added the new clause
  test.equal(s._minisat._clauses.length, 9);
});

Tinytest.add("logic-solver - getVarNum", function (test) {
  var s = new Logic.Solver();
  s.require("A");
  var a = s.getVarNum("A");
  test.isTrue(a > 0); // this also confirms it's a number
  test.equal(s.getVarNum("B", true), 0); // noCreate = true
  var b = s.getVarNum("B");
  test.notEqual(a, b);
  var a2 = s.getVarNum("A");
  var b2 = s.getVarNum("B");
  test.equal(a, a2);
  test.equal(b, b2);
});

Tinytest.add("logic-solver - assumptions", function (test) {
  var s = new Logic.Solver;
  s.getVarNum("A");
  s.getVarNum("B");
  s.getVarNum("C");
  s.getVarNum("D");
  // MiniSat could return any assignment of the variables here,
  // but we happen to know that it uses all-false as a starting
  // point for search.
  test.equal(s.solve().getMap(), { A: false, B: false, C: false, D: false });

  var atLeastOne = Logic.or("A", "B", "C", "D");
  // which of A,B,C,D comes back true is totally arbitrary, but it's
  // deterministic as long as we don't touch anything.
  test.equal(s.solveAssuming(atLeastOne).getMap(),
             { A: false, B: true, C: false, D: false });
  test.equal(formatLines(s._clauseStrings()),
             formatLines(["$or1 v -$assump1",
                          "A v B v C v D v -$or1"]));

  // assume the same thing again
  test.equal(s.solveAssuming(atLeastOne).getMap(),
             { A: false, B: true, C: false, D: false });
  test.equal(formatLines(s._clauseStrings()),
             formatLines(["$or1 v -$assump1",
                          "A v B v C v D v -$or1",
                          "$or1 v -$assump2"]));

  var none = Logic.and("-A", "-B", "-C", "-D");
  test.equal(s.solveAssuming(none).getMap(),
             { A: false, B: false, C: false, D: false });
  test.equal(formatLines(s._clauseStrings()),
             formatLines(["$or1 v -$assump1",
                          "A v B v C v D v -$or1",
                          "$or1 v -$assump2",
                          "$and1 v -$assump3",
                          "-A v -$and1",
                          "-B v -$and1",
                          "-C v -$and1",
                          "-D v -$and1"]));

  // require a formula that was previously just temporarily assumed!
  s.require(atLeastOne);
  test.equal(s.solve().getMap(),
             // any one could be true
             { A: false, B: true, C: false, D: false });
  test.equal(formatLines(s._clauseStrings()),
             formatLines(["$or1 v -$assump1",
                          "A v B v C v D v -$or1",
                          "$or1 v -$assump2",
                          "$and1 v -$assump3",
                          "-A v -$and1",
                          "-B v -$and1",
                          "-C v -$and1",
                          "-D v -$and1",
                          "$or1"]));

  test.equal(s.solveAssuming("D").getMap(),
             // at least D is true; other than that, anything goes
             { A: false, B: true, C: false, D: true });
  test.equal(formatLines(s._clauseStrings()),
             formatLines(["$or1 v -$assump1",
                          "A v B v C v D v -$or1",
                          "$or1 v -$assump2",
                          "$and1 v -$assump3",
                          "-A v -$and1",
                          "-B v -$and1",
                          "-C v -$and1",
                          "-D v -$and1",
                          "$or1",
                          "D v -$assump4"]));

  var sum = Logic.sum("A", "B", "C", "D");
  var atLeast2 = Logic.greaterThanOrEqual(sum, Logic.constantBits(2));
  test.equal(s.solveAssuming(atLeast2).getMap(),
             // any two or more, including D
             { A: false, B: true, C: false, D: true });
  s.require(atLeast2);
  var atLeast3 = Logic.greaterThanOrEqual(sum, Logic.constantBits(3));
  test.equal(s.solveAssuming(atLeast3).getMap(),
             // any three or more, including D
             { A: true, B: true, C: false, D: true });
  s.require(atLeast3);
  var atLeast4 = Logic.greaterThanOrEqual(sum, Logic.constantBits(4));
  test.equal(s.solveAssuming(atLeast4).getMap(),
             { A: true, B: true, C: true, D: true });

  s.forbid("C");
  test.equal(s.solve().getMap(),
             { A: true, B: true, C: false, D: true });
});

Tinytest.add("logic-solver - eight queens", function (test) {
  var boardSquare = function (r, c) {
    return String(r) + ',' + String(c);
  };

  Logic._disablingTypeChecks(function () {

    var solver = new Logic.Solver;
    var nums = _.range(1, 9); // 1..8
    _.each(nums, function (x) {
      // one per row x, one per column x
      solver.require(Logic.exactlyOne(_.map(nums, function (y) {
        return boardSquare(x, y);
      })));
      solver.require(Logic.exactlyOne(_.map(nums, function (y) {
        return boardSquare(y, x);
      })));
    });

    // At most one queen per diagonal.  A diagonal
    // consists of squares whose row + column sums
    // to a constant, or the horizontal flip of
    // such a set of squares.
    for (var flip = 0; flip <= 1; flip++) {
      for (var sum = 2; sum <= 16; sum++) {
        var vars = [];
        for (var r = 1; r <= sum-1; r++) {
          var c = sum - r;
          if (flip)
            c = 9-c;
          if (r >= 1 && r <= 8 && c >= 1 && c <= 8) {
            vars.push(boardSquare(r,c));
          }
        }
        solver.require(Logic.atMostOne(vars));
      }
    }

    var solution = solver.solve().getTrueVars();

    // solution might be, for example,
    // ["16", "24", "31", "45", "58", "62", "77", "83"]
    test.equal(solution.length, 8);
    test.isTrue(/^([1-8],[1-8] ){7}[1-8],[1-8]$/.test(solution.join(' ')));
    var assertEightDifferent = function (transformFunc) {
      test.equal(_.uniq(_.map(solution, transformFunc)).length, 8);
    };
    // queens occur in eight different rows, eight different columns
    assertEightDifferent(function (queen) { return queen.charAt(0); });
    assertEightDifferent(function (queen) { return queen.charAt(2); });
    // queens' row/col have eight different sums, eight different differences
    assertEightDifferent(function (queen) {
      return Number(queen.charAt(0)) - Number(queen.charAt(2));
    });
    assertEightDifferent(function (queen) {
      return Number(queen.charAt(0)) + Number(queen.charAt(2));
    });
  });
});

Tinytest.add("logic-solver - Sudoku", function (test) {
  var v = function (row, col, value) {
    return row + "," + col + "=" + value;
  };

  Logic._disablingTypeChecks(function () {
    //console.profile('sudoku');

    var solver = new Logic.Solver();

    // All rows, columns, and digits are 0-based internally.
    for (var x = 0; x < 9; x++) {
      // Find the top-left of box x. For example, Box 0 has a top-left
      // of (0,0).  Box 3 has a top-left of (3,0).
      var boxRow = Math.floor(x/3)*3;
      var boxCol = (x%3)*3;
      for (var y = 0; y < 9; y++) {
        var numberInEachSquare = [];
        var columnHavingYInRowX = [];
        var rowHavingYInColumnX = [];
        var squareHavingYInBoxX = [];
        for (var z = 0; z < 9; z++) {
          numberInEachSquare.push(v(x,y,z));
          columnHavingYInRowX.push(v(x,z,y));
          rowHavingYInColumnX.push(v(z,x,y));
          squareHavingYInBoxX.push(v(
            boxRow + Math.floor(z/3),
            boxCol + (z%3),
            y));
        }
        solver.require(Logic.exactlyOne(numberInEachSquare));
        solver.require(Logic.exactlyOne(columnHavingYInRowX));
        solver.require(Logic.exactlyOne(rowHavingYInColumnX));
        solver.require(Logic.exactlyOne(squareHavingYInBoxX));
      }
    }

    // Input a pretty hard Sudoku from here:
    // http://www.menneske.no/sudoku/eng/showpuzzle.html?number=6903541
    var puzzle = [
      "....839..",
      "1......3.",
      "..4....7.",
      ".42.3....",
      "6.......4",
      "....7..1.",
      ".2.......",
      ".8...92..",
      "...25...6"
    ];
    for (var r = 0; r < 9; r++) {
      var str = puzzle[r];
      for (var c = 0; c < 9; c++) {
        // zero-based digit
        var digit = str.charCodeAt(c) - 49;
        if (digit >= 0 && digit < 9) {
          solver.require(v(r, c, digit));
        }
      }
    }

    var solution = solver.solve().getTrueVars();
    var solutionString = _.map(solution, function (v) {
      return String(Number(v.slice(-1)) + 1);
    }).join('').match(/.{9}/g).join('\n');
    test.equal(solutionString, [
      "765483921",
      "198726435",
      "234915678",
      "842531769",
      "617892354",
      "359674812",
      "926147583",
      "581369247",
      "473258196"
    ].join('\n'));

    //console.profileEnd('sudoku');
  });
});

Tinytest.add("logic-solver - goes to eleven", function (test) {
  var solver = new Logic.Solver();
  var eleven = Logic.constantBits(11);
  var x = Logic.variableBits("x", 5);
  solver.require(Logic.lessThanOrEqual(x, eleven));
  solver.require(Logic.lessThanOrEqual(eleven, x));
  test.equal(solver.solve().getTrueVars().join(','), "x$0,x$1,x$3");
});

Tinytest.add("logic-solver - evaluate", function (test) {
  var isTrue = function (x) {
    test.isTrue(x === true); // require exact "true"
  };
  var isFalse = function (x) {
    test.isFalse(x !== false); // require exact "false"
  };

  var s = new Logic.Solver();
  s.require("A", "-B");
  var f = Logic.and("A", "-B");
  s.require(f);
  var g = Logic.and("A", "B");
  s.forbid(g);
  var h1 = Logic.xor("A", "B");
  var h2 = Logic.and("A", "B");
  s.require(Logic.or("$T", h1));
  s.require(Logic.or("$T", h2));
  var sol = s.solve();
  isTrue(sol.evaluate("A"));
  isFalse(sol.evaluate("-A"));
  isTrue(sol.evaluate("--A"));
  isFalse(sol.evaluate("B"));
  isTrue(sol.evaluate("-B"));
  isTrue(sol.evaluate(f));
  isFalse(sol.evaluate(g));
  isTrue(sol.evaluate(h1));
  isFalse(sol.evaluate(h2));
  isFalse(sol.evaluate(Logic.not(h1)));
  isTrue(sol.evaluate(Logic.not(h2)));
  isTrue(sol.evaluate(Logic.exactlyOne("A", "B")));
  isFalse(sol.evaluate(Logic.exactlyOne("-A", "B")));

  s.require(Logic.or("$T", Logic.not(h1)));
  s.require(Logic.or("$T", Logic.not(h2)));
  isTrue(sol.evaluate(h1));
  isFalse(sol.evaluate(h2));

  test.throws(function () {
    sol.evaluate("C");
  });

  s.require("D");
  test.throws(function () {
    sol.evaluate("D");
  });
  test.throws(function () {
    sol.evaluate(Logic.or("D", "$T"));
  });

  test.equal(sol.evaluate(
    new Logic.Bits(["A", "B", "-A", "$F", "-B"])), 17);

  var numClauses = s.clauses.length;
  test.equal(sol.evaluate(Logic.weightedSum([Logic.or("A", "B"),
                                             "A", "A", "-B"],
                                            [7, 7, 7, 7])), 28);
  test.equal(s.clauses.length, numClauses);
});

Tinytest.add("logic-solver - toy packages", function (test) {

  var withSolver = function (func) {

    var solver = new Logic.Solver();

    _.each(allPackageVersions, function (versions, package) {
      versions = _.map(versions, function (v) {
        return package + "@" + v;
      });
      // e.g. atMostOne(["foo@1.0.0", "foo@1.0.1", "foo@2.0.0"])
      solver.require(Logic.atMostOne(versions));
      // e.g. equiv("foo", or(["foo@1.0.0", ...]))
      solver.require(Logic.equiv(package, Logic.or(versions)));
    });

    _.each(dependencies, function (depMap, packageVersion) {
      _.each(depMap, function (compatibleVersions, package2) {
        // e.g. implies("bar@1.2.4", "foo")
        solver.require(Logic.implies(packageVersion, package2));
        // Now ban all incompatible versions of package2 if
        // we select this packageVersion.
        // NOTE: This is not the best way to express constraints.  It's
        // not what we do in the real package constraint solver.
        _.each(allPackageVersions[package2], function (v) {
          if (! _.contains(compatibleVersions, v)) {
            solver.require(Logic.implies(packageVersion,
                                         Logic.not(package2 + "@" + v)));
          }
        });
      });
    });

    var optimize = function (solver, costVectorMap) {
      var solution = solver.solve();
      if (! solution) {
        return null;
      }

      var terms = [];
      var weightVectors = [];
      var vectorLength = null;
      _.each(costVectorMap, function (vector, key) {
        terms.push(key);
        weightVectors.push(vector);
        if (vectorLength === null) {
          vectorLength = vector.length;
        } else {
          if (vectorLength !== vector.length) {
            throw new Error("Uneven vector lengths: " + vectorLength +
                            " and " + vector.length);
          }
        }
      });

      for (var i = 0; i < vectorLength; i++) {
        var weights = _.pluck(weightVectors, i);
        solution = solver.minimize(solution, terms, weights);
      }

      return solution;
    };

    var solve = function (optionalCosts) {
      var solution = (optionalCosts ? optimize(solver, optionalCosts) :
                      solver.solve());
      if (! solution) {
        return solution; // null
      } else {
        // only return variables like "foo@1.0.0", not "foo"
        return _.filter(solution.getTrueVars(), function (v) {
          return v.indexOf('@') >= 0;
        });
      }
    };

    func(solver, solve);
  };

  var allPackageVersions = {
    'foo': ['1.0.0', '1.0.1', '2.0.0'],
    'bar': ['1.2.3', '1.2.4', '1.2.5'],
    'baz': ['3.0.0']
  };

  // Exact dependencies.
  var dependencies = {
    'bar@1.2.3': { foo: ['1.0.0'] },
    'bar@1.2.4': { foo: ['1.0.1'] },
    'bar@1.2.5': { foo: ['2.0.0'] },
    'baz@3.0.0': { foo: ['1.0.0', '1.0.1'],
                   bar: ['1.2.4', '1.2.5'] }
  };

  withSolver(function (solver, solve) {
    // Ask for "bar@1.2.5", get both it and "foo@2.0.0"
    solver.require("bar@1.2.5");
    test.equal(solve(), ["bar@1.2.5", "foo@2.0.0"]);
  });

  withSolver(function (solver, solve) {
    // Ask for "foo@1.0.1" and *some* version of bar!
    solver.require("foo@1.0.1");
    solver.require("bar");
    test.equal(solve(), ["bar@1.2.4", "foo@1.0.1"]);
  });

  withSolver(function (solver, solve) {
    // Ask for versions that can't be combined
    solver.require("foo@1.0.1");
    solver.require("bar@1.2.3");
    test.equal(solve(), null);
  });

  withSolver(function (solver, solve) {
    // Ask for baz, automatically get versions of foo and bar
    // such that foo satisfies bar's dependency!
    solver.require("baz");
    test.equal(solve(), ["bar@1.2.4",
                         "baz@3.0.0",
                         "foo@1.0.1"]);
  });

  withSolver(function (solver, solve) {
    // pick earliest versions
    solver.require("foo");
    solver.require("bar");
    test.equal(solve({ "foo@1.0.0": [0],
                       "foo@1.0.1": [1],
                       "foo@2.0.0": [2],
                       "bar@1.2.3": [0],
                       "bar@1.2.4": [1],
                       "bar@1.2.5": [2] }),
               ["bar@1.2.3", "foo@1.0.0"]);
  });

  withSolver(function (solver, solve) {
    // pick latest versions
    solver.require("foo");
    solver.require("bar");
    test.equal(solve({ "foo@1.0.0": [2],
                       "foo@1.0.1": [1],
                       "foo@2.0.0": [0],
                       "bar@1.2.3": [2],
                       "bar@1.2.4": [1],
                       "bar@1.2.5": [0] }),
               ["bar@1.2.5", "foo@2.0.0"]);
  });

  withSolver(function (solver, solve) {
    // pick earliest versions (but give solver a
    // cost vector with extra stuff)
    solver.require("foo");
    solver.require("bar");
    test.equal(solve({ "foo@1.0.0": [1, 0],
                       "foo@1.0.1": [1, 1],
                       "foo@2.0.0": [1, 2],
                       "bar@1.2.3": [2, 0],
                       "bar@1.2.4": [2, 1],
                       "bar@1.2.5": [2, 2] }),
               ["bar@1.2.3", "foo@1.0.0"]);
  });

  withSolver(function (solver, solve) {
    // pick latest versions (but give solver a
    // bigger vector to work with)
    solver.require("foo");
    solver.require("bar");
    test.equal(solve({ "foo@1.0.0": [1, 2],
                       "foo@1.0.1": [1, 1],
                       "foo@2.0.0": [1, 0],
                       "bar@1.2.3": [2, 2],
                       "bar@1.2.4": [2, 1],
                       "bar@1.2.5": [2, 0] }),
               ["bar@1.2.5", "foo@2.0.0"]);
  });

});

Tinytest.add("logic-solver - minimize", function (test) {
  var s = new Logic.Solver();
  s.require(Logic.or("A", "B", "C", "D"));
  // cost is equal to the number of false variables
  var costTerms = ["-A", "-B", "-C", "-D"];
  var costWeights = 1;
  var solution1 = s.solve();
  // nothing forces the cost (= the number of false variables)
  // to be greater than 0, but MiniSat will always discover
  // a sparser solution than (1,1,1,1) first.
  test.isTrue(solution1.getWeightedSum(costTerms, costWeights) > 0);
  var solution2 = s.minimize(solution1, costTerms, costWeights);
  test.isFalse(solution1 === solution2);
  test.equal(solution2.getWeightedSum(costTerms, costWeights), 0);
  test.equal(solution2.getTrueVars(), ["A", "B", "C", "D"]);
});

Tinytest.add("logic-solver - maximize", function (test) {
  var s = new Logic.Solver();
  // Find subset of {2, 5, 10, 11, 15} that sums to as close
  // as possible to 19 without going over.
  var costWeights = [2, 5, 10, 11, 15];
  // name variables after the weights
  var costTerms = _.map(costWeights, function (w) {
    return "#"+w;
  });
  var ws = Logic.weightedSum(costTerms, costWeights);
  s.require(Logic.lessThanOrEqual(ws, Logic.constantBits(19)));
  var sol = s.solve();
  var sol2 = s.maximize(sol, costTerms, costWeights, ws);
  test.equal(sol2.getTrueVars(), ["#11", "#2", "#5"]);
});

Tinytest.add("logic-solver - weightedSum", function (test) {
  var s = new Logic.Solver();
  s.require(Logic.equalBits(
    Logic.weightedSum(["A", "B"], [1, 4]),
    Logic.constantBits(5)));
  var sol = s.solve();
  test.isTrue(sol);
  test.equal(sol.getTrueVars(), ["A", "B"]);
});

Tinytest.add("logic-solver - type-checking", function (test) {
  // on by default
  test.throws(function () {
    Logic.or({});
  }, /is not a Formula or Term/);

  // ... but can turn it off (this shouldn't throw)
  Logic._disablingTypeChecks(function () {
    Logic.or({});
  });
});


Package.describe({
  summary: "Tests for JavaScript code analysis for Meteor",
  version: "1.0.3"
});

// The tests are in a separate package so that it is possible to compile
// 'js-analyze' as a isopack and then load it via `isopack.load` without
// any dependencies.
Package.onTest(function (api) {
  api.use('tinytest');
  api.use('js-analyze');
  api.addFiles('js_analyze_tests.js', 'server');
});


Package.onUse(function(api) {
  api.use('first-unordered', {unordered: true});
});


Logic.Solver.prototype.maximize = function (solution, costTerms, costWeights,

return minMax(this, solution, costTerms, costWeights, options, false);

Logic.Solver.prototype.maximizeWeightedSum = function (solution, costTerms,

return minMaxWS(this, solution, costTerms, costWeights, options, false);


#!/bin/bash

# This used to be run standalone, but now it's intended to always be
# invoked by 'meteor self-test' (and eventually we should port the
# whole thing to JavaScript, or at least the parts that don't
# duplicate our existing JS-based test coverage).
#
# To run it, set METEOR_TOOL_PATH to the 'meteor' script to use, plus,
# as usual, METEOR_WAREHOUSE_DIR if you want to stub out the
# warehouse.

set -e -x

cd "`dirname "$0"`"/../../..

# METEOR_TOOL_PATH is the path to the 'meteor' that we will use for
# our tests. There is a vestigal capability to default to running the
# 'meteor' that sets next to this script in a checkout, but we should
# probably just take that out.
if [ -z "$METEOR_TOOL_PATH" ]; then
    METEOR="`pwd`/meteor"
else
    METEOR="$METEOR_TOOL_PATH"
fi

if [ -z "$NODE" ]; then
    NODE="$(pwd)/scripts/node.sh"
fi

# Ensure that $NODE is set properly. Note that $NODE may not have access to
# modules from the dev bundle.
$NODE --version

TEST_TMPDIR=`mktemp -d -t meteor-cli-test-XXXXXXXX`
OUTPUT="$TEST_TMPDIR/output"
trap 'echo "[...]"; tail -25 $OUTPUT; echo FAILED ; rm -rf "$TEST_TMPDIR" >/dev/null 2>&1' EXIT

cd "$TEST_TMPDIR"

DIR="skel with spaces"
$METEOR create "$DIR"
test -d "$DIR"
test -f "$DIR/$DIR.js"

## Tests in a meteor project
cd "$DIR"

MONGOMARK='--bind_ip 127.0.0.1 --smallfiles --port 9101'
# kill any old test meteor
# there is probably a better way to do this, but it is at least portable across macos and linux
# (the || true is needed on linux, whose xargs will invoke kill even with no args)

## Begin actual tests

ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill || true

! $METEOR mongo >> $OUTPUT 2>&1
$METEOR reset >> $OUTPUT 2>&1

test ! -d .meteor/local
! ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT

PORT=9100
$METEOR -p $PORT >> $OUTPUT 2>&1 &
METEOR_PID=$!

sleep 5 # XXX XXX lame

test -d .meteor/local/db
ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT
curl -s "http://localhost:$PORT" >> $OUTPUT

echo "show collections" | $METEOR mongo

# kill meteor, see mongo is still running
kill $METEOR_PID

sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!

! ps ax | grep "$METEOR_PID" | grep -v grep >> $OUTPUT
ps ax | grep -e "$MONGOMARK"  | grep -v grep >> $OUTPUT

echo "... rerun"

$METEOR -p $PORT >> $OUTPUT 2>&1 &
METEOR_PID=$!

sleep 10 # XXX XXX lame

ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT
curl -s "http://localhost:$PORT" >> $OUTPUT

kill $METEOR_PID
sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!

ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
sleep 2 # need to make sure these kills take effect

$METEOR test-packages -p $PORT >> $OUTPUT 2>&1 &

METEOR_PID=$!

sleep 10 # XXX XXX lame

ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT
curl -s "http://localhost:$PORT" >> $OUTPUT

kill $METEOR_PID
sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!

ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
sleep 2 # need to make sure these kills take effect

echo "... mongo message"

# Run a server on the same port as mongod, so that mongod fails to start up. Rig
# it so that a single connection will cause it to exit.
$NODE -e 'require("net").createServer(function(){process.exit(0)}).listen('$PORT'+1, "127.0.0.1")' &

sleep 1

$METEOR -p $PORT > error.txt || true

grep 'port was closed' error.txt >> $OUTPUT

# Kill the server by connecting to it.
$NODE -e 'require("net").connect({host:"127.0.0.1",port:'$PORT'+1},function(){process.exit(0);})'

# prepare die.js so that we have a server that loads packages and dies
cat > die.js <<EOF
if (Meteor.isServer)
  process.exit(1);
EOF

echo "... local-package-sets -- new package"

mkdir -p "$TEST_TMPDIR/local-packages/a-package-named-bar/"
cat > "$TEST_TMPDIR/local-packages/a-package-named-bar/package.js" <<EOF
Package.describe({
  summary: 'a-package-named-bar',
  version: '1.0.0'
});
Npm.depends({gcd: '0.0.0'});

Package.onUse(function(api) {
  api.addFiles(['call_gcd.js'], 'server');
});
EOF

cat > "$TEST_TMPDIR/local-packages/a-package-named-bar/call_gcd.js" <<EOF
console.log("loaded a-package-named-bar");

var gcd = Npm.require('gcd');
console.log("gcd(4,6)=" + gcd(4,6));
EOF

! $METEOR add a-package-named-bar >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR add a-package-named-bar >> $OUTPUT
$METEOR -p $PORT --once 2>&1 | grep "unknown package .* a-package-named-bar" >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR -p $PORT --once | grep "loaded a-package-named-bar" >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR bundle $TEST_TMPDIR/bundle.tar.gz >> $OUTPUT
tar tvzf $TEST_TMPDIR/bundle.tar.gz >>$OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR build $TEST_TMPDIR/bundle >> $OUTPUT
tar tvzf "$TEST_TMPDIR/bundle/$DIR.tar.gz" >>$OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR -p $PORT --once | grep "gcd(4,6)=2" >> $OUTPUT

echo "... local-package-sets -- overridden package"

mkdir -p "$TEST_TMPDIR/local-packages/accounts-ui/"
cat > "$TEST_TMPDIR/local-packages/accounts-ui/package.js" <<EOF
Package.describe({
  summary: "accounts-ui - overridden",
  version: "1.0.0"
});

EOF

# Remove a-package-named-bar so that the local accounts-ui package is
# the only thing that determines whether we need to set PACKAGE_DIRS. If
# we were to leave a-package-named-bar in the app, then we would need to
# specify PACKAGE_DIRS to get output from 'meteor list', even before
# adding the local accounts-ui package, and we want to be able to run
# 'meteor list' without PACKAGE_DIRS set to see that it picks up the
# core accounts-ui package, not the local one.
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR remove a-package-named-bar >> $OUTPUT

! $METEOR add accounts-ui 2>&1 | grep "accounts-ui - overridden" >> $OUTPUT
$METEOR remove accounts-ui 2>&1 >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR add accounts-ui 2>&1 | grep "accounts-ui - overridden" >> $OUTPUT
! $METEOR list | grep "accounts-ui - overridden" >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR list | grep "accounts-ui - overridden" >> $OUTPUT

# remove die.js, we're done with package tests.
rm die.js

## Cleanup
trap - EXIT
rm -rf "$DIR"
echo PASSED


Lists = new Mongo.Collection('lists');

// Calculate a default name for a list in the form of 'List A'
Lists.defaultName = function() {
  var nextLetter = 'A', nextName = 'List ' + nextLetter;
  while (Lists.findOne({name: nextName})) {
    // not going to be too smart here, can go past Z
    nextLetter = String.fromCharCode(nextLetter.charCodeAt(0) + 1);
    nextName = 'List ' + nextLetter;
  }

  return nextName;
};

Todos = new Mongo.Collection('todos');