/**
 * styleguide-plugin.js
 *
 * The plugin is instantiated and the emitted styleguide.html file is
 * generated here.
 */

import minimatch from 'minimatch';
import fs from 'fs';
import path from 'path';
import PlaygroundPlugin from './default-plugins/playground/plugin';
import some from 'lodash/some';

let id = -1;
/**
 * Instantiates the plugin
 * @param {Object} options           The options
 * @param {String} options.include   A list of glob patterns that matches the components
 * @param {String} options.dest      The destination the styleguide should be emitted at
 */
function StyleguidePlugin(options) {
  this.id = (++id);

  if (!options.include) {
    throw new Error('You need to specify where your components are in the "include" option!\n\n');
  }

  // Assert that a HTML file was specified in the dest option
  if (options.dest && options.dest.indexOf('.html') !== options.dest.length - 5) {
    throw new Error('You need to specify a .html file in the "dest" option!\n\n');
  }

  this.options = options || {};
}

/**
 * Gets the cache of a compiler
 */
StyleguidePlugin.prototype.getCache = function getCache(compiler) {
  // Attach a cache to the compile if not present
  if (!compiler.styleguideCache) {
    compiler.styleguideCache = {}; // eslint-disable-line no-param-reassign
  }

  // Create a cache for this instance
  if (!compiler.styleguideCache[this.id]) {
    compiler.styleguideCache[this.id] = {}; // eslint-disable-line no-param-reassign
  }

  return compiler.styleguideCache[this.id];
};

/**
 * Initializes the plugin, called after the main StyleguidePlugin function above
 */
StyleguidePlugin.prototype.apply = function apply(compiler) {
  if (this.options.plugins && this.options.plugins.length > 0) {
    this.registerPlugins(compiler);
  } else {
    this.registerDefaultPlugins(compiler);
  }

  // Create the cache for this instace of the compiler
  const cache = this.getCache(compiler);
  const loaderRequest = `${require.resolve('./loader.js')}?${this.id}`;

  compiler.plugin('normal-module-factory', (nmf) => {
    nmf.plugin('after-resolve', (data, callback) => {
      // Once the loader is already in loaders bail out and don't inject it again
      if (data.loaders.indexOf(loaderRequest) >= 0) {
        return callback(null, data);
      }

      // Load all files hat are matched by the directory glob specified in
      // options.include with the loader in ./loader.js
      // (minimatch checks if a path matches a glob pattern)
      const matched = some(this.options.include, (pattern) => (
        minimatch(path.relative(compiler.context, data.userRequest), pattern)
      ));
      if (matched) {
        data.loaders.unshift(loaderRequest);
      }

      callback(null, data);
      return undefined;
    });
  });

  compiler.plugin('emit', (compilation, callback) => {
    // Generate a string with a script tag for every component
    const paths = {};
    Object.keys(cache).forEach((request) => {
      const requestPath = request.replace(/^.+!/, '').replace(/\?.+$/, '');
      const relativePath = path.relative(compiler.options.context, requestPath);

      // TODO should be relative not absolute:
      paths[relativePath] = `/${cache[request]}`;
    });

    // TODO add option for hot reloading and identify port automatically
    // Inject the component script tags and the client js into a basic HTML template
    const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>Styleguide</title>
      </head>
      <body>
        <div id='styleguide-root'>Root</div>
        <script src="/styleguide/client-api.js"></script>
        <script>
          window.STYLEGUIDE_PLUGIN_CLIENT_API.scripts = ${JSON.stringify(paths)};
        </script>
        <script src="/styleguide/client-bundle.js"></script>
        <script src="http://localhost:8080/webpack-dev-server.js"></script>
      </body>
    </html>`;
    const styleguidePath = this.options.dest || 'styleguide/index.html';

    // And emit that HTML template as 'styleguide.html'
    compilation.assets[styleguidePath] = { // eslint-disable-line no-param-reassign
      source: () => html,
      size: () => html.length,
    };

    // Add styleguide base javascript
    if (!compilation.assets['styleguide/client-bundle.js']) {
      const clientJs = fs.readFileSync(path.join(__dirname, 'client-bundle.js'));
      compilation.assets['styleguide/client-bundle.js'] = { // eslint-disable-line no-param-reassign
        source: () => clientJs,
        size: () => clientJs.length,
      };
    }

    if (!compilation.assets['styleguide/client-api.js']) {
      const clientJs = fs.readFileSync(path.join(__dirname, 'client-api.js'));
      compilation.assets['styleguide/client-api.js'] = { // eslint-disable-line no-param-reassign
        source: () => clientJs,
        size: () => clientJs.length,
      };
    }

    callback();
  });
};

/**
 * Register the default plugins
 */
StyleguidePlugin.prototype.registerDefaultPlugins = function registerDefaultPlugins(compiler) {
  const playgroundPlugin = new PlaygroundPlugin();
  playgroundPlugin.apply(compiler);
};

/**
 * Register the custom, user defined plugins
 */
StyleguidePlugin.prototype.registerPlugins = function registerPlugins(compiler) {
  const plugins = this.options.plugins;
  for (let i = 0; i < plugins.length; i++) {
    plugins[i].apply(compiler);
  }
};

export default StyleguidePlugin;

@@ -298,16 +298,54 @@
 h/some';
+%0Aimport isArray from 'lodash/isArray';
 %0A%0Alet id
@@ -609,16 +609,88 @@
 tted at%0A
+ * @param %7BArray%7D  options.plugins   The plugins to use for the project%0A
  */%0Afunc
@@ -743,16 +743,66 @@
 ++id);%0A%0A
+  // Assert that the include option was specified%0A
   if (!o
@@ -1156,16 +1156,207 @@
 );%0A  %7D%0A%0A
+  // Assert that the plugins option is an array if specified%0A  if (options.plugins && !isArray(options.plugins)) %7B%0A    throw new Error('The %22plugins%22 option needs to be an array!%5Cn%5Cn');%0A  %7D%0A%0A
   this.o
@@ -1961,24 +1961,24 @@
 n above%0A */%0A
-
 StyleguidePl
@@ -2027,16 +2027,72 @@
 iler) %7B%0A
+  // Register either the plugins or the default plugins%0A
   if (th

Add errors to options in main webpack plugin