const relateContext = require('./util/relate-context');
const pluginCompat = require('./util/plugin-compat');

const ParserSchemas3 = [
  ['Parser', 'options'],
];
const ParserSchemas4 = [
  ['JsonParser', 'options'],
  ['Parser', 'options', 'sourceType'],
  ['WebAssemblyParser', 'options'],
];

try {
  ParserSchemas3[0].Parser = require('webpack/lib/Parser');
} catch (_) {}

try {
  ParserSchemas4[0].Parser = require('webpack/lib/JsonParser');
  ParserSchemas4[1].Parser = require('webpack/lib/Parser');
  try {
    ParserSchemas4[2].Parser = require('webpack/lib/WebAssemblyParser');
  } catch (_) {
    ParserSchemas4[2].Parser = require('webpack/lib/wasm/WebAssemblyParser');
  }
} catch (_) {}

const freezeArgument = {
};

const thawArgument = {
};

function freezeParser(parser, extra, methods) {
  const schemas = extra.schemas;
  for (let i = 0; i < schemas.length; i++) {
    if (parser.constructor === schemas[i].Parser) {
      const frozen = {
        type: schemas[i][0],
      };
      for (let j = 1; j < schemas[i].length; j++) {
        let arg = parser[schemas[i][j]];
        if (freezeArgument[schemas[i][j]]) {
          arg = freezeArgument[schemas[i][j]](arg, parser, extra, methods);
        }
        frozen[schemas[i][j]] = arg;
      }
      return frozen;
    }
  }
}

function thawParser(frozen, extra, methods) {
  const schemas = extra.schemas;
  for (let i = 0; i < schemas.length; i++) {
    if (frozen.type === schemas[i][0]) {
      const Parser = schemas[i].Parser;
      const args = [];
      for (let j = 1; j < schemas[i].length; j++) {
        let arg = frozen[schemas[i][j]];
        if (thawArgument[schemas[i][j]]) {
          arg = thawArgument[schemas[i][j]](arg, frozen, extra, methods);
        }
        args.push(arg);
      }
      try {
        return new Parser(...args);
      } catch (_) {
        return new (Function.prototype.bind.apply(Parser, [null].concat(args)))();
      }
    }
  }
}

class HardParserPlugin {
  constructor(options) {
    this.options = options || {};
  }

  apply(compiler) {
    const schema = this.options.schema;
    let schemas = ParserSchemas4;
    if (schema < 4) {
      schemas = ParserSchemas3;
    }

    pluginCompat.register(compiler, '_hardSourceBeforeFreezeParser', 'syncWaterfall', ['frozen', 'item', 'extra']);
    pluginCompat.register(compiler, '_hardSourceFreezeParser', 'syncWaterfall', ['frozen', 'item', 'extra']);
    pluginCompat.register(compiler, '_hardSourceAfterFreezeParser', 'syncWaterfall', ['frozen', 'item', 'extra']);

    pluginCompat.register(compiler, '_hardSourceBeforeThawParser', 'syncWaterfall', ['item', 'frozen', 'extra']);
    pluginCompat.register(compiler, '_hardSourceThawParser', 'syncWaterfall', ['item', 'frozen', 'extra']);
    pluginCompat.register(compiler, '_hardSourceAfterThawParser', 'syncWaterfall', ['item', 'frozen', 'extra']);

    let methods;

    pluginCompat.tap(compiler, '_hardSourceMethods', 'HardParserPlugin', _methods => {
      methods = _methods;
    });

    pluginCompat.tap(compiler, '_hardSourceFreezeParser', 'HardParserPlugin freeze', (frozen, parser, extra) => {
      extra.schemas = schemas;
      frozen = freezeParser(parser, extra, methods);
      if (schema === 4) {
        frozen.moduleType = extra.module.type;
      }
      return frozen;
    });

    pluginCompat.tap(compiler, '_hardSourceThawParser', 'HardParserPlugin thaw', (parser, frozen, extra) => {
      if (schema < 4) {
        return extra.normalModuleFactory.getParser(frozen.options);
      }
      else {
        return extra.normalModuleFactory.getParser(frozen.moduleType, frozen.options);
      }
    });
  }
}

module.exports = HardParserPlugin;