function generate(schema, refs, ex) {
  var $ = deref();

  try {
    var seen = {};

    return traverse($(schema, refs, ex), [], function reduce(sub,maxReduceDepth) {
      if (typeof sub.$ref === 'string') {
          var id = sub.$ref;
          delete sub.$ref;          
          if (typeof(maxReduceDepth)=='undefined') maxReduceDepth = 5;
          if (maxReduceDepth<=0) {
            delete sub.$ref;
            delete sub.oneOf;
            delete sub.anyOf;
            delete sub.allOf;
            return sub;
           }
           maxReduceDepth -= 1;
           merge(sub, $.util.findByRef(id, $.refs));              
      }
      if (Array.isArray(sub.allOf)) {
        var schemas = sub.allOf;

        delete sub.allOf;

        // this is the only case where all sub-schemas
        // must be resolved before any merge
        schemas.forEach(function(s) {
          merge(sub, reduce(s));
        });
      }

      if (Array.isArray(sub.oneOf || sub.anyOf)) {
        var mix = sub.oneOf || sub.anyOf;

        delete sub.anyOf;
        delete sub.oneOf;

        merge(sub, random.pick(mix));
      }

      for (var prop in sub) {
        if ((Array.isArray(sub[prop]) || typeof sub[prop] === 'object') && !isKey(prop)) {
          sub[prop] = reduce(sub[prop],maxReduceDepth);
        }
      }

      return sub;
    });
  } catch (e) {
    if (e.path) {
      throw new Error(e.message + ' in ' + '/' + e.path.join('/'));
    } else {
      throw e;
    }
  }
}
module.exports = function(params, callback) {
  var schemas = Array.isArray(params.schemas) ? params.schemas : Array.isArray(params.schema) ? params.schema : [params.schema];

  var schema_directory = params.directory || process.cwd(),
      normalized_fakeroot = typeof params.fakeroot === 'string' && (params.fakeroot.replace(/\/+$/g, '') + '/');

  var $ = deref(),
      _ = new Async();

  function downloadSchemas(from, parent) {
    function pushReference(schema) {
      if (typeof schema.id === 'string') {
        var base = $.util.resolveURL(parent, schema.id);

        base = $.util.getDocumentURI(base) || base;

        if (!$.refs[base]) {
          $.refs[base] = schema;
        }

        extractRefs(schema).forEach(function(ref) {
          var url = $.util.getDocumentURI(ref);

          if ($.refs[url]) {
            return;
          }

          if (url.indexOf(normalized_fakeroot) === 0) {
            var file = path.join(schema_directory, url.replace(normalized_fakeroot, ''));

            if (fs.statSync(file).isFile()) {
              downloadSchemas(parseJSON(fs.readFileSync(file).toString(), file), url);
            }
          } else {
            _.then(function(next) {
              var req = fetchURL[url.split(':')[0]];

              req.get(url, function(res) {
                var body = '';

                res.on('data', function(chunk) {
                  body += chunk;
                });

                res.on('end', function() {
                  try {
                    next(downloadSchemas(parseJSON(body, url), url));
                  } catch (e) {
                    next(e);
                  }
                });
              }).on('error', function() {
                next(new Error('cannot reach ' + url));
              });
            });
          }
        });
      }
    }

    pushReference($.util.normalizeSchema(parent, from, pushReference));
  }

  try {
    if (!Array.isArray(schemas)) {
      throw new Error('Invalid schemas "' + JSON.stringify(schemas) + '" (array expected)');
    }

    schemas.forEach(function(data) {
      downloadSchemas(data, normalized_fakeroot);
    });

    _.run(function(err) {
      callback(err, $.refs, schemas.map(function(data) {
        return $.util.normalizeSchema(normalized_fakeroot, data);
      }));
    });
  } catch (e) {
    callback(e, $.refs, schemas);
  }
};