/*
 * Stairs
 * Copyright 2017 (c) Mark van Renswoude
 *
 * https://git.x2software.net/pub/Stairs
*/
'use strict';

// Borrowed heavily from gulp-concat:
// https://github.com/contra/gulp-concat/
//
// It's very much hardcoded for the ESP8266 Arduino at the moment,
// but feel free to hack away at it if you need it for other purposes!
var through = require('through2');
var path = require('path');
var File = require('vinyl');
var _ = require('lodash');
var Readable = require('stream').Readable;


function escapeContent(content, lineLength)
{
  var lineRegexp = new RegExp('(.{1,' + (lineLength - 1) + '}[^\\\\])', 'g');

  return content
    .replace(/\\/g, '\\\\')
    .replace(/"/g, '\\"')
    .replace(/\r?\n/g, '\\r\\n')
    .replace(lineRegexp, '  "$1"\r\n')
    .replace(/\r\n$/, '');
};


function escapeContentAsByteArray(content, lineLength)
{
  var bytesPerLine = Math.floor(lineLength / 5);
  var lineRegexp = new RegExp('((?:0x..,){1,' + bytesPerLine + '})', 'g');

  return content
    .replace(/(.{2})/g, '0x$1,')
    .replace(lineRegexp, '  $1\r\n')
    .replace(/,\r\n$/, '');
};


function encodeFile(file, opts)
{
  var variableName;

  if (opts.map.hasOwnProperty(file.relative))
    variableName = opts.map[file.relative];
  else
    variableName = file.relative.replace(/[^a-zA-Z0-9_]/g, '_');

  if (variableName === null)
    return '';

  variableName = opts.variablePrefix + variableName;

  var escapedContent;
  var output;

  if (opts.byteArray)
  {
    escapedContent = escapeContentAsByteArray(file.contents.toString('hex'), opts.lineLength);
    output = "const uint8_t " + variableName + "[] PROGMEM = {\r\n" + escapedContent + "};\r\n\r\n";
  }
  else
  {
    escapedContent = escapeContent(file.contents.toString('utf-8'), opts.lineLength);
    output = "const char " + variableName + "[] PROGMEM = \r\n" + escapedContent + ";\r\n\r\n";
  }

  return output;
}


module.exports = function(file, opts)
{
  if (!file)
    throw new Error('gulp-cppstringify: Missing file option');

  opts = _.extend({
    map: [],
    headerDefineName: '__Embedded',
    variablePrefix: 'Embedded',
    lineLength: 100,
    byteArray: false
  }, opts || {});

  var fileName;
  var latestFile = false;
  var latestMod = 0;
  var output = null;


  if (typeof file === 'string')
    fileName = file;
  else if (typeof file.path === 'string')
    fileName = path.basename(file.path);
  else
    throw new Error('gulp-cppstringify: Missing path in file options');


  function bufferContents(file, enc, cb)
  {
    if (file.isNull())
    {
      cb();
      return;
    }

    if (file.isStream())
    {
      this.emit('error', new Error('gulp-cppstringify: Streaming not supported'));
      cb();
      return;
    }

    if (!latestMod || file.stat && file.stat.mtime > latestMod)
    {
      latestFile = file;
      latestMod = file.stat && file.stat.mtime;
    }

    if (output == null)
    {
      output = new Readable();
      output._read = function noop() {};

      output.push("#ifndef " + opts.headerDefineName + "\r\n");
      output.push("#define " + opts.headerDefineName + "\r\n\r\n");
      output.push("#include <pgmspace.h>\r\n\r\n");
    }

    output.push(encodeFile(file, opts));
    cb();
  }


  function endStream(cb)
  {
    if (!latestFile)
    {
      cb();
      return;
    }

    var headerFile;

    if (typeof file === 'string')
    {
      headerFile = latestFile.clone({contents: false});
      headerFile.path = path.join(latestFile.base, file);
    }
    else
      headerFile = new File(file);

    output.push("#endif\r\n");
    output.push(null);
    headerFile.contents = output;

    this.push(headerFile);
    cb();
  }

  return through.obj(bufferContents, endStream);
};