// Generated by CoffeeScript 2.5.0
(function() {
  var ConfigNode, LoadYaml, Merge, Promise, Root, arrows, bulk, compose, defaults, functionArguments, identity, isArray, key, load, loadConfigTypes, load_, merge, mergeConfigs, mergeTwoConfigs, path, resolve, resolveStaticPaths, sigmatch, unit, value, wrap;

  Promise = require("bluebird");

  functionArguments = require("function-arguments");

  Merge = require("./merger");

  compose = function(...funs) {
    return funs.reduceRight(function(a, b) {
      return function(...x) {
        return b(a(...x));
      };
    });
  };

  //defaults = require("./default-settings")
  defaults = {};

  path = require("path");

  bulk = require("bulk-require");

  ({isArray} = require("util"));

  sigmatch = require("sigmatch");

  LoadYaml = require("./load-yaml");

  ConfigNode = require("./config-node");

  Root = require("./root-node");

  loadConfigTypes = function(dirs) {
    return dirs.map(function(dir) {
      return bulk(dir, '*');
    }).reduce((function(a, b) {
      return merge(a, b);
    }), {});
  };

  merge = Merge({
    customMerge: function(lhs, rhs0, pass) {
      var mergedOpts, rhs;
      // Since we will be dealing with config nodes a lot, we define
      // sepcial merging behaviour

      // if lhs is a config node and rhs is a plain object, instead pretend that rhs
      // is a config node of the same type as lhs.  Rational: It is rather unlikely
      // that an application would want to override a custom node with a plain
      // object. Otoh, wanting to override some *options* of said custom node is a
      // very frequently encountered problem.  Note that in the unlikely case that
      // you *really* want to override a custom config node with a plain object, you
      // can do this explicitly: ironically, you would have to create a custom node
      // with a custom merge method that returns a plain object.
      rhs = (lhs != null) && lhs instanceof ConfigNode && (rhs0 != null ? rhs0.constructor : void 0) === Object ? new lhs.constructor(rhs0) : rhs0;
      // now, if rhs is a ConfigNode, we allow for some special merging magic.
      // In particular, we allow custom node types to define their own merging
      // behaviour
      if (rhs instanceof ConfigNode) {
        //console.log "merge: rhs is ConfigNode", rhs.constructor.name
        //console.log "merge: lhs is instance of", lhs?.constructor?.name
        // Custom ConfigNodes can always override the merging behaviour
        if (rhs.merge != null) {
          //console.log "merge: rhs defines custom merge strategy"
          return rhs.merge(lhs, merge);
        // If no explicit merge behaviour is defined by rhs, but if the type of rhs
        // is the same or a specialization of the lhs type then we should be able to
        // merge both nodes by merging their options.
        } else if ((lhs != null) && lhs instanceof ConfigNode && rhs instanceof lhs.constructor) {
          //console.log "merge: rhs specializes lhs"
          mergedOpts = merge(lhs._options, rhs._options);
          //console.log "mergedOpts", mergedOpts
          return new rhs.constructor(mergedOpts);
        } else {
          // Otherwise don't try anything fancy, stick with rhs.
          return rhs;
        }
      } else {
        // stick with the default behaviour for non-ConfigNode data
        // For most non-trivial cases this means: rhs wins, lhs is discarded.
        return pass;
      }
    }
  });

  resolveStaticPaths = function(obj) {
    var blockName, dir, i, len, ref, ref1, replaceEntries;
    if (typeof obj !== "object") {
      throw new Error("häh?" + obj);
    }
    if (obj == null) {
      return void 0;
    }
    dir = (ref = obj.__dirname) != null ? ref : obj.__filename != null ? path.dirname(obj.__filename) : void 0;
    if (dir == null) {
      return obj;
    }
    replaceEntries = function(blockName) {
      var key, ref1, ref2, tmp, value;
      if (obj[blockName] != null) {
        tmp = {};
        ref2 = (ref1 = obj[blockName]) != null ? ref1 : {};
        for (key in ref2) {
          value = ref2[key];
          if (typeof value === "string") {
            tmp[key] = path.resolve(dir, value);
          }
          if (typeof value === "object") {
            tmp[key] = value.map(function(v) {
              return path.resolve(dir, v);
            });
          }
        }
        return obj[blockName] = tmp;
      }
    };
    ref1 = ['static', 'dynamic'];
    for (i = 0, len = ref1.length; i < len; i++) {
      blockName = ref1[i];
      replaceEntries(blockName);
    }
    return obj;
  };

  resolve = function({file, required, content, configTypes = {}}) {
    var loadYaml;
    if (file != null) {
      if (content != null) {
        return merge({
          __filename: file,
          __dirname: path.dirname(file)
        }, content);
      } else {
        loadYaml = LoadYaml(configTypes, !required);
        content = loadYaml(file);
        if (content != null) {
          return merge(content, {
            __filename: file,
            __dirname: path.dirname(file)
          });
        } else {
          //console.log "no content?", file
          return null;
        }
      }
    } else {
      return content;
    }
  };

  mergeTwoConfigs = function(a = {}, b = {}) {
    var c, files, filesA, filesB, ref, ref1;
    filesA = (ref = a.__files) != null ? ref : a.__filename != null ? [a.__filename] : [];
    filesB = (ref1 = b.__files) != null ? ref1 : b.__filename != null ? [b.__filename] : [];
    files = filesA.concat(filesB);
    c = merge(a, b);
    delete c.__filename;
    delete c.__dirname;
    if (files.length > 0) {
      c.__files = files;
    }
    return c;
  };

  mergeConfigs = function(...configs) {
    return configs.reduce(mergeTwoConfigs);
  };

  identity = function(x) {
    return Promise.resolve(x);
  };

  load_ = function({required = true, envVars = [], constructFileName}) {
    return function(cfg) {
      var env, file, newContent;
      env = envVars.map(function(name) {
        var ref;
        return (ref = cfg.__env) != null ? ref[name] : void 0;
      });
      file = path.resolve(constructFileName(...env));
      newContent = resolveStaticPaths(resolve({
        file: file,
        required: required,
        configTypes: cfg.__types,
        content: void 0
      }));
      if (envVars.length > 0) {
        return unit(mergeConfigs(cfg, newContent, {
          __usedEnvVars: envVars
        }));
      } else {
        return unit(mergeConfigs(cfg, newContent));
      }
    };
  };

  load = function(opts) {
    return sigmatch(function(match) {
      match("s", function(file) {
        return load_(merge(opts, {
          constructFileName: (function() {
            return file;
          })
        }));
      });
      match("a,f", function(deps, file) {
        return load_(merge(opts, {
          constructFileName: file,
          envVars: deps
        }));
      });
      match("f", function(fun) {
        var deps;
        deps = functionArguments(fun);
        return load_(merge(opts, {
          constructFileName: fun,
          envVars: deps
        }));
      });
      return match(".*", function() {
        throw new Error("unsupported call signature");
      });
    });
  };

  arrows = {
    unit: function(cfg) {
      return wrap(cfg, identity);
    },
    types: function(types = {}) {
      return function(cfg) {
        return unit((Object.keys(types).length > 0 ? merge(cfg, {
          __types: types
        }) : cfg));
      };
    },
    typePath: function(...dirs0) {
      return function(cfg) {
        var dirs, prevDirs;
        dirs = dirs0.map(function(d) {
          return path.resolve(d);
        });
        return unit((dirs.length > 0 ? (({
          __typePath: prevDirs = []
        } = cfg), merge(cfg, {
          __types: loadConfigTypes(dirs),
          __typePath: prevDirs.concat(dirs)
        })) : cfg));
      };
    },
    load: load({
      required: true
    }),
    tryLoad: load({
      required: false
    }),
    add: function(obj, file) {
      return function(cfg) {
        return unit(mergeConfigs(cfg, resolveStaticPaths(resolve({
          file: file ? path.resolve(file) : void 0,
          content: obj
        }))));
      };
    },
    then: function(f) {
      if (f == null) {
        return unit;
      } else {
        return function(cfg) {
          return wrap(cfg, f);
        };
      }
    }
  };

  unit = arrows.unit;

  wrap = function(cfg = defaults, action = identity) {
    var bind, build, instance, key, run, value;
    bind = function(f = unit) {
      var m;
      m = f(cfg);
      return wrap(m._cfg, function(env, finalCfg, argv0) {
        return Promise.resolve(action(env, finalCfg, argv0)).then(function(argv1) {
          return m.action(env, finalCfg, argv1);
        });
      });
    };
    build = function() {
      return new Root(cfg);
    };
    run = function(env, argv) {
      return action(env, build(), argv);
    };
    instance = {
      bind: bind,
      action: action,
      _cfg: cfg,
      run: run,
      build: build
    };
    for (key in arrows) {
      value = arrows[key];
      if (key !== "unit") {
        (function(key, value) {
          return instance[key] = function(...args) {
            return this.bind(value(...args));
          };
        })(key, value);
      }
    }
    return instance;
  };

  // the built-in functions are also made available as static "methods"
  module.exports = wrap;

  for (key in arrows) {
    value = arrows[key];
    module.exports[key] = value;
  }

}).call(this);
