'use strict';

let _        = require('lodash');
let del      = require('del');
let gear     = require('gear');
let path     = require('path');
let utility  = require('./utility');

let parseHeader = utility.parseHeader;
let tasks       = require('gear-lib');

tasks.clean = function(directories, blobs, done) {
  directories = _.isString(directories) ? [directories] : directories;

  return del(directories).then(() => done(null, blobs));
};
tasks.clean.type = 'collect';

// Depending on the languages required for the current language being
// processed, this task reorders it's dependencies first then include the
// language.
tasks.reorderDeps = function(options, blobs, done) {
  let buffer       = {},
      newBlobOrder = [];

  _.each(blobs, function(blob) {
    let basename = path.basename(blob.name),
        fileInfo = parseHeader(blob.result),
        extra    = { blob: blob, processed: false };

    buffer[basename] = _.merge(extra, fileInfo || {});
  });

  function pushInBlob(object) {
    if(!object.processed) {
      object.processed = true;
      newBlobOrder.push(object.blob);
    }
  }

  _.each(buffer, function(buf) {
    let object;

    if(buf.Requires) {
      _.each(buf.Requires, function(language) {
        object = buffer[language];
        pushInBlob(object);
      });
    }

    pushInBlob(buf);
  });

  done(null, newBlobOrder);
};
tasks.reorderDeps.type = 'collect';

tasks.template = function(template, blob, done) {
  template = template || '';

  let filename = path.basename(blob.name),
      basename = path.basename(filename, '.js'),
      content  = _.template(template)({
        name: basename,
        filename: filename,
        content: blob.result.trim()
      });

  return done(null, new gear.Blob(content, blob));
};

tasks.templateAll = function(options, blobs, done) {
  return options.callback(blobs)
    .then(function(data) {
      let template = options.template || data.template,
          content  = _.template(template)(data);

      return done(null, [new gear.Blob(content)]);
    })
    .catch(done);
};
tasks.templateAll.type = 'collect';

tasks.rename = function(options, blob, done) {
  options = options || {};

  let name = blob.name,
      ext  = new RegExp(path.extname(name) + '$');

  name = name.replace(ext, options.extname);

  return done(null, new gear.Blob(blob.result, { name: name }));
};

// Adds the contributors from `AUTHORS.en.txt` onto the `package.json` file
// and moves the result into the `build` directory.
tasks.buildPackage = function(json, blob, done) {
  let result,
      lines = blob.result.split(/\r?\n/),
      regex = /^- (.*) <(.*)>$/;

  json.contributors = _.transform(lines, function(result, line) {
    let matches = line.match(regex);

    if(matches) {
      result.push({
        name: matches[1],
        email: matches[2]
      });
    }
  }, []);

  result = JSON.stringify(json, null, '  ');

  return done(null, new gear.Blob(result, blob));
};

// Mainly for replacing the keys of `utility.REPLACES` for it's values while
// skipping over strings, regular expressions, or comments. However, this is
// pretty generic so long as you use the `utility.replace` function, you can
// replace a regular expression with a string.
tasks.replaceSkippingStrings = function(params, blob, done) {
  let content = blob.result,
      length  = content.length,
      offset  = 0,

      replace = params.replace || '',
      regex   = params.regex,
      starts  = /\/\/|['"\/]/,

      result  = [],
      chunk, end, match, start, terminator;

  while(offset < length) {
    chunk = content.slice(offset);
    match = chunk.match(starts);
    end   = match ? match.index : length;

    chunk = content.slice(offset, end + offset);
    result.push(chunk.replace(regex, replace));

    offset += end;

    if(match) {
      // We found a starter sequence: either a `//` or a "quote"
      // In the case of `//` our terminator is the end of line.
      // Otherwise it's either a matching quote or an escape symbol.
      terminator = match[0] !== '//' ? new RegExp(`[${match[0]}\\\\]`)
                                     : /$/m;
      start      = offset;
      offset    += 1;

      while(true) {
        chunk = content.slice(offset);
        match = chunk.match(terminator);

        if(!match) {
          return done('Unmatched quote');
        }

        if(match[0] === '\\') {
          offset += match.index + 2;
        } else {
          offset += match.index + 1;
          result.push(content.slice(start, offset));
          break;
        }
      }
    }
  }

  return done(null, new gear.Blob(result.join(''), blob));
};

tasks.filter = function(callback, blobs, done) {
  let filteredBlobs = _.filter(blobs, callback);

  // Re-add in blobs required from header definition
  _.each(filteredBlobs, function(blob) {
    let dirname  = path.dirname(blob.name),
        content  = blob.result,
        fileInfo = parseHeader(content);

    if(fileInfo && fileInfo.Requires) {
      _.each(fileInfo.Requires, function(language) {
        let filename  = `${dirname}/${language}`,
            fileFound = _.find(filteredBlobs, { name: filename });

        if(!fileFound) {
          filteredBlobs.push(
            _.find(blobs, { name: filename }));
        }
      });
    }
  });

  return done(null, filteredBlobs);
};
tasks.filter.type = 'collect';

tasks.readSnippet = function(options, blob, done) {
  let name        = path.basename(blob.name, '.js'),
      fileInfo    = parseHeader(blob.result),
      snippetName = path.join('test', 'detect', name, 'default.txt');

  function onRead(error, blob) {
    if(error) return done(error); // ignore missing snippets

    let meta = { name: `${name}.js`, fileInfo: fileInfo };

    return done(null, new gear.Blob(blob.result, meta));
  }

  gear.Blob.readFile(snippetName, 'utf8', onRead, false);
};

tasks.insertLicenseTag = function(options, blob, done) {
  let hljsVersion = require('../package').version,
      licenseTag  = `/*! highlight.js v${hljsVersion} | ` +
                    `BSD3 License | git.io/hljslicense */\n`;

  return done(null, new gear.Blob(licenseTag + blob.result, blob));
};

// Packages up included languages into the core `highlight.js` and moves the
// result into the `build` directory.
tasks.packageFiles = function(options, blobs, done) {
  let content,
      coreFile  = _.head(blobs),
      languages = _.tail(blobs),

      lines     = coreFile.result
                    .replace(utility.regex.header, '')
                    .split('\n\n'),
      lastLine  = _.last(lines),
      langStr   = _.reduce(languages, (str, language) =>
                           `${str + language.result}\n`, '');

  lines[lines.length - 1] = langStr.trim();

  lines   = lines.concat(lastLine);
  content = lines.join('\n\n');

  return done(null, [new gear.Blob(content)]);
};
tasks.packageFiles.type = 'collect';

module.exports = new gear.Registry({ tasks: tasks });
