import { default as WhiteLister } from 'pretty-text/white-lister';
import { sanitize } from 'pretty-text/sanitizer';
import guid from 'pretty-text/guid';

function deprecate(feature, name){
  return function() {
    if (window.console && window.console.log) {
      window.console.log(feature + ': ' + name + ' is deprecated, please use the new markdown it APIs');
    }
  };
}

function createHelper(featureName, opts, optionCallbacks, pluginCallbacks, getOptions, whiteListed) {
  let helper = {};
  helper.markdownIt = true;
  helper.whiteList = info => whiteListed.push([featureName, info]);
  helper.registerInline = deprecate(featureName,'registerInline');
  helper.replaceBlock = deprecate(featureName,'replaceBlock');
  helper.addPreProcessor = deprecate(featureName,'addPreProcessor');
  helper.inlineReplace = deprecate(featureName,'inlineReplace');
  helper.postProcessTag = deprecate(featureName,'postProcessTag');
  helper.inlineRegexp = deprecate(featureName,'inlineRegexp');
  helper.inlineBetween = deprecate(featureName,'inlineBetween');
  helper.postProcessText = deprecate(featureName,'postProcessText');
  helper.onParseNode = deprecate(featureName,'onParseNode');
  helper.registerBlock = deprecate(featureName,'registerBlock');
  // hack to allow moving of getOptions
  helper.getOptions = () => getOptions.f();

  helper.registerOptions = (callback) => {
    optionCallbacks.push([featureName, callback]);
  };

  helper.registerPlugin = (callback) => {
    pluginCallbacks.push([featureName, callback]);
  };

  return helper;
}

// TODO we may just use a proper ruler from markdown it... this is a basic proxy
class Ruler {
  constructor() {
    this.rules = [];
  }

  getRules() {
    return this.rules;
  }

  getRuleForTag(tag) {
    this.ensureCache();
    return this.cache[tag];
  }

  ensureCache() {
    if (this.cache) { return; }

    this.cache = {};
    for(let i=this.rules.length-1;i>=0;i--) {
      let info = this.rules[i];
      this.cache[info.rule.tag] = info;
    }
  }

  push(name, rule) {
    this.rules.push({name, rule});
    this.cache = null;
  }
}

// block bb code ruler for parsing of quotes / code / polls
function setupBlockBBCode(md) {
  md.block.bbcode = { ruler: new Ruler() };
}

function setupInlineBBCode(md) {
  md.inline.bbcode = { ruler: new Ruler() };
}

function setupTextPostProcessRuler(md) {
  const TextPostProcessRuler = requirejs('pretty-text/engines/discourse-markdown/text-post-process').TextPostProcessRuler;
  md.core.textPostProcess = { ruler: new TextPostProcessRuler() };
}

function renderHoisted(tokens, idx, options) {
  const content = tokens[idx].content;
  if (content && content.length > 0) {
    let id = guid();
    options.discourse.hoisted[id] = tokens[idx].content;
    return id;
  } else {
    return '';
  }
}

function setupUrlDecoding(md) {
  // this fixed a subtle issue where %20 is decoded as space in
  // automatic urls
  md.utils.lib.mdurl.decode.defaultChars = ';/?:@&=+$,# ';
}

function setupHoister(md) {
  md.renderer.rules.html_raw = renderHoisted;
}

const IMG_SIZE_REGEX = /^([1-9]+[0-9]*)x([1-9]+[0-9]*)(\s*,\s*([1-9][0-9]?)%)?$/;
function renderImage(tokens, idx, options, env, slf) {
  var token = tokens[idx];

  let alt = slf.renderInlineAsText(token.children, options, env);

  let split = alt.split('|');
  if (split.length > 1) {
    let match;
    let info = split.splice(split.length-1)[0];

    if (match = info.match(IMG_SIZE_REGEX)) {
      if (match[1] && match[2]) {
        alt = split.join('|');

        let width = match[1];
        let height = match[2];

        if (match[4]) {
          let percent = parseFloat(match[4]) / 100.0;
          width = parseInt(width * percent);
          height = parseInt(height * percent);
        }

        if (token.attrIndex('width') === -1) {
          token.attrs.push(['width', width]);
        }

        if (token.attrIndex('height') === -1) {
          token.attrs.push(['height', height]);
        }
      }

    }
  }

  token.attrs[token.attrIndex('alt')][1] = alt;
  return slf.renderToken(tokens, idx, options);
}

function setupImageDimensions(md) {
  md.renderer.rules.image = renderImage;
}

let Helpers;

export function setup(opts, siteSettings, state) {
  if (opts.setup) {
    return;
  }

  // we got to require this late cause bundle is not loaded in pretty-text
  Helpers = Helpers || requirejs('pretty-text/engines/discourse-markdown/helpers');

  opts.markdownIt = true;

  let optionCallbacks = [];
  let pluginCallbacks = [];

  // ideally I would like to change the top level API a bit, but in the mean time this will do
  let getOptions = {
    f: () => opts
  };

  const check = /discourse-markdown\/|markdown-it\//;
  let features = [];
  let whiteListed = [];

  Object.keys(require._eak_seen).forEach(entry => {
    if (check.test(entry)) {
      const module = requirejs(entry);
      if (module && module.setup) {

        const featureName = entry.split('/').reverse()[0];
        features.push(featureName);
        module.setup(createHelper(featureName, opts, optionCallbacks, pluginCallbacks, getOptions, whiteListed));
      }
    }
  });

  optionCallbacks.forEach(([,callback])=>{
    callback(opts, siteSettings, state);
  });

  // enable all features by default
  features.forEach(feature => {
    if (!opts.features.hasOwnProperty(feature)) {
      opts.features[feature] = true;
    }
  });

  let copy = {};
  Object.keys(opts).forEach(entry => {
    copy[entry] = opts[entry];
    delete opts[entry];
  });

  copy.helpers = {
    textReplace: Helpers.textReplace
  };

  opts.discourse = copy;
  getOptions.f = () => opts.discourse;

  opts.engine = window.markdownit({
    discourse: opts.discourse,
    html: true,
    breaks: opts.discourse.features.newline,
    xhtmlOut: false,
    linkify: opts.discourse.features.linkify,
    typographer: siteSettings.enable_markdown_typographer
  });

  setupUrlDecoding(opts.engine);
  setupHoister(opts.engine);
  setupImageDimensions(opts.engine);
  setupBlockBBCode(opts.engine);
  setupInlineBBCode(opts.engine);
  setupTextPostProcessRuler(opts.engine);

  pluginCallbacks.forEach(([feature, callback])=>{
    if (opts.discourse.features[feature]) {
      opts.engine.use(callback);
    }
  });

  // top level markdown it notifier
  opts.markdownIt = true;
  opts.setup = true;

  if (!opts.discourse.sanitizer || !opts.sanitizer) {
    const whiteLister = new WhiteLister(opts.discourse);

    whiteListed.forEach(([feature, info]) => {
      whiteLister.whiteListFeature(feature, info);
    });

    opts.sanitizer = opts.discourse.sanitizer = (!!opts.discourse.sanitize) ? a=>sanitize(a, whiteLister) : a=>a;
  }

}

export function cook(raw, opts) {
  // we still have to hoist html_raw nodes so they bypass the whitelister
  // this is the case for oneboxes
  let hoisted = {};

  opts.discourse.hoisted = hoisted;

  const rendered = opts.engine.render(raw);
  let cooked = opts.discourse.sanitizer(rendered).trim();

  const keys = Object.keys(hoisted);
  if (keys.length) {
    let found = true;

    const unhoist = function(key) {
      cooked = cooked.replace(new RegExp(key, "g"), function() {
        found = true;
        return hoisted[key];
      });
    };

    while (found) {
      found = false;
      keys.forEach(unhoist);
    }
  }

  delete opts.discourse.hoisted;
  return cooked;

}