// Generated by CoffeeScript 2.3.2
// # CSV Stringifier

// Please look at the [project documentation](https://csv.js.org/stringify/) for additional
// information.
var Stringifier, get, stream, underscore, util;

stream = require('stream');

util = require('util');

get = require('lodash.get');

// ## Usage

// This module export a function as its main entry point and return a transform
// stream.

// Refers to the [official prject documentation](http://csv.adaltas.com/stringify/)
// on how to call this function.
module.exports = function() {
  var callback, chunks, data, options, stringifier;
  if (arguments.length === 3) {
    data = arguments[0];
    options = arguments[1];
    callback = arguments[2];
  } else if (arguments.length === 2) {
    if (Array.isArray(arguments[0])) {
      data = arguments[0];
    } else {
      options = arguments[0];
    }
    if (typeof arguments[1] === 'function') {
      callback = arguments[1];
    } else {
      options = arguments[1];
    }
  } else if (arguments.length === 1) {
    if (typeof arguments[0] === 'function') {
      callback = arguments[0];
    } else if (Array.isArray(arguments[0])) {
      data = arguments[0];
    } else {
      options = arguments[0];
    }
  }
  if (options == null) {
    options = {};
  }
  stringifier = new Stringifier(options);
  if (data) {
    process.nextTick(function() {
      var d, j, len;
      for (j = 0, len = data.length; j < len; j++) {
        d = data[j];
        stringifier.write(d);
      }
      return stringifier.end();
    });
  }
  if (callback) {
    chunks = [];
    stringifier.on('readable', function() {
      var chunk, results;
      results = [];
      while (chunk = stringifier.read()) {
        results.push(chunks.push(chunk));
      }
      return results;
    });
    stringifier.on('error', function(err) {
      return callback(err);
    });
    stringifier.on('end', function() {
      return callback(null, chunks.join(''));
    });
  }
  return stringifier;
};

// You can also use *util.promisify* native function (Node.js 8+) in order to wrap callbacks into promises for more convenient use when source is a readable stream and you are OK with storing entire result set in memory:

// ```
// const { promisify } = require('util');
// const csv = require('csv');
// const stringifyAsync = promisify(csv.stringify);

// //returns promise
// function generateCsv(sourceData) {
//     return stringifyAsync(sourceData);
// }
// ```

// ## `Stringifier([options])`

// Options are documented [here](http://csv.adaltas.com/stringify/).
Stringifier = class Stringifier extends stream.Transform {
  constructor(opts = {}) {
    var base, base1, base2, base3, base4, isRegExp, isString, j, k, len, options, quoted_match, ref, v;
    super({...{
        writableObjectMode: true
      }, ...options});
    options = {};
    for (k in opts) {
      v = opts[k];
      // Immutable options and camelcase conversion
      options[underscore(k)] = v;
    }
    //# Default options
    if (options.delimiter == null) {
      options.delimiter = ',';
    }
    if (options.quote == null) {
      options.quote = '"';
    }
    if (options.quoted == null) {
      options.quoted = false;
    }
    if (options.quoted_empty == null) {
      options.quoted_empty = void 0;
    }
    if (options.quoted_string == null) {
      options.quoted_string = false;
    }
    if (options.eof == null) {
      options.eof = true;
    }
    if (options.escape == null) {
      options.escape = '"';
    }
    if (options.header == null) {
      options.header = false;
    }
    // Normalize the columns option
    options.columns = this.normalize_columns(options.columns);
    if (options.cast == null) {
      options.cast = {};
    }
    // Normalize option `quoted_match`
    if (options.quoted_match === void 0 || options.quoted_match === null || options.quoted_match === false) {
      options.quoted_match = null;
    } else if (!Array.isArray(options.quoted_match)) {
      options.quoted_match = [options.quoted_match];
    }
    if (options.quoted_match) {
      ref = options.quoted_match;
      for (j = 0, len = ref.length; j < len; j++) {
        quoted_match = ref[j];
        isString = typeof quoted_match === 'string';
        isRegExp = quoted_match instanceof RegExp;
        if (!isString && !isRegExp) {
          throw Error(`Invalid Option: quoted_match must be a string or a regex, got ${JSON.stringify(quoted_match)}`);
        }
      }
    }
    if (options.cast.bool) {
      // Backward compatibility
      options.cast.boolean = options.cast.bool;
    }
    // Custom cast
    if ((base = options.cast).string == null) {
      base.string = function(value) {
        return value;
      };
    }
    if ((base1 = options.cast).date == null) {
      base1.date = function(value) {
        // Cast date to timestamp string by default
        return '' + value.getTime();
      };
    }
    if ((base2 = options.cast).boolean == null) {
      base2.boolean = function(value) {
        // Cast boolean to string by default
        if (value) {
          return '1';
        } else {
          return '';
        }
      };
    }
    if ((base3 = options.cast).number == null) {
      base3.number = function(value) {
        // Cast number to string using native casting by default
        return '' + value;
      };
    }
    if ((base4 = options.cast).object == null) {
      base4.object = function(value) {
        // Stringify object as JSON by default
        return JSON.stringify(value);
      };
    }
    if (options.record_delimiter === void 0 || options.record_delimiter === null || options.record_delimiter === false) {
      if (options.record_delimiter == null) {
        options.record_delimiter = '\n';
      }
    } else if (typeof options.record_delimiter === 'string') {
      switch (options.record_delimiter) {
        case 'auto':
          options.record_delimiter = null;
          break;
        case 'unix':
          options.record_delimiter = "\n";
          break;
        case 'mac':
          options.record_delimiter = "\r";
          break;
        case 'windows':
          options.record_delimiter = "\r\n";
          break;
        case 'ascii':
          options.record_delimiter = "\u001e";
          break;
        case 'unicode':
          options.record_delimiter = "\u2028";
      }
    } else if (Buffer.isBuffer(options.record_delimiter)) {
      options.record_delimiter = options.record_delimiter.toString();
    } else {
      throw Error(`Invalid Option: record_delimiter must be a string or a buffer, got ${JSON.stringify(options.record_delimiter)}`);
    }
    // Expose options
    this.options = options;
    // Internal state
    this.state = {
      stop: false
    };
    // Information
    this.info = {
      records: 0
    };
    this;
  }

  // ## `Stringifier.prototype._transform(chunk, encoding, callback)`

  // Implementation of the [transform._transform function](https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback).
  _transform(chunk, encoding, callback) {
    var base, e;
    if (this.state.stop === true) {
      return;
    }
    // Chunk validation
    if (!(Array.isArray(chunk) || typeof chunk === 'object')) {
      this.state.stop = true;
      return callback(Error(`Invalid Record: expect an array or an object, got ${JSON.stringify(chunk)}`));
    }
    // Detect columns from the first record
    if (this.info.records === 0) {
      if (Array.isArray(chunk)) {
        if (this.options.header === true && !this.options.columns) {
          this.state.stop = true;
          return callback(Error('Undiscoverable Columns: header option requires column option or object records'));
        }
      } else {
        if ((base = this.options).columns == null) {
          base.columns = this.normalize_columns(Object.keys(chunk));
        }
      }
    }
    if (this.info.records === 0) {
      // Emit the header
      this.headers();
    }
    try {
      // Emit and stringify the record if an object or an array
      this.emit('record', chunk, this.info.records);
    } catch (error) {
      e = error;
      this.state.stop = true;
      return this.emit('error', e);
    }
    // Convert the record into a string
    if (this.options.eof) {
      chunk = this.stringify(chunk);
      if (chunk == null) {
        return;
      }
      chunk = chunk + this.options.record_delimiter;
    } else {
      chunk = this.stringify(chunk);
      if (chunk == null) {
        return;
      }
      if (this.options.header || this.info.records) {
        chunk = this.options.record_delimiter + chunk;
      }
    }
    // Emit the csv
    this.info.records++;
    this.push(chunk);
    return callback();
  }

  // ## `Stringifier.prototype._flush(callback)`

  // Implementation of the [transform._flush function](https://nodejs.org/api/stream.html#stream_transform_flush_callback).
  _flush(callback) {
    if (this.info.records === 0) {
      this.headers();
    }
    return callback();
  }

  // ## `Stringifier.prototype.stringify(line)`

  // Convert a line to a string. Line may be an object, an array or a string.
  stringify(record) {
    var _record, column, columns, containsEscape, containsQuote, containsRowDelimiter, containsdelimiter, delimiter, err, escape, field, header, i, j, l, len, m, newrecord, quote, quoted, quotedMatch, quotedString, ref, ref1, regexp, shouldQuote, value;
    if (typeof record !== 'object') {
      return record;
    }
    ({columns, delimiter, header, quote, escape} = this.options);
    // Record is an array
    if (Array.isArray(record)) {
      if (columns) {
        // We are getting an array but the user has specified output columns. In
        // this case, we respect the columns indexes
        record.splice(columns.length);
      }
// Cast record elements
      for (i = j = 0, len = record.length; j < len; i = ++j) {
        field = record[i];
        [err, value] = this.__cast(field, {
          index: i,
          column: i,
          records: this.info.records,
          header: header && this.info.records === 0
        });
        if (err) {
          this.emit('error', err);
          return;
        }
        record[i] = [value, field];
      }
    } else {
      // Record is a literal object
      _record = [];
      if (columns) {
        for (i = l = 0, ref = columns.length; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) {
          field = get(record, columns[i].key);
          [err, value] = this.__cast(field, {
            index: i,
            column: columns[i].key,
            records: this.info.records,
            header: header && this.info.records === 0
          });
          if (err) {
            this.emit('error', err);
            return;
          }
          _record[i] = [value, field];
        }
      } else {
        for (column in record) {
          field = record[column];
          [err, value] = this.__cast(field, {
            index: i,
            column: columns[i].key,
            records: this.info.records,
            header: header && this.info.records === 0
          });
          if (err) {
            this.emit('error', err);
            return;
          }
          _record.push([value, field]);
        }
      }
      record = _record;
      _record = null;
    }
    if (Array.isArray(record)) {
      newrecord = '';
      for (i = m = 0, ref1 = record.length; (0 <= ref1 ? m < ref1 : m > ref1); i = 0 <= ref1 ? ++m : --m) {
        [value, field] = record[i];
        if (err) {
          this.emit('error', err);
          return;
        }
        if (value) {
          if (typeof value !== 'string') {
            this.emit('error', Error(`Formatter must return a string, null or undefined, got ${JSON.stringify(value)}`));
            return null;
          }
          containsdelimiter = value.indexOf(delimiter) >= 0;
          containsQuote = (quote !== '') && value.indexOf(quote) >= 0;
          containsEscape = value.indexOf(escape) >= 0 && (escape !== quote);
          containsRowDelimiter = value.indexOf(this.options.record_delimiter) >= 0;
          quoted = this.options.quoted;
          quotedString = this.options.quoted_string && typeof field === 'string';
          quotedMatch = this.options.quoted_match && typeof field === 'string' && this.options.quoted_match.filter(function(quoted_match) {
            if (typeof quoted_match === 'string') {
              return value.indexOf(quoted_match) !== -1;
            } else {
              return quoted_match.test(value);
            }
          });
          quotedMatch = quotedMatch && quotedMatch.length > 0;
          shouldQuote = containsQuote || containsdelimiter || containsRowDelimiter || quoted || quotedString || quotedMatch;
          if (shouldQuote && containsEscape) {
            regexp = escape === '\\' ? new RegExp(escape + escape, 'g') : new RegExp(escape, 'g');
            value = value.replace(regexp, escape + escape);
          }
          if (containsQuote) {
            regexp = new RegExp(quote, 'g');
            value = value.replace(regexp, escape + quote);
          }
          if (shouldQuote) {
            value = quote + value + quote;
          }
          newrecord += value;
        } else if (this.options.quoted_empty || ((this.options.quoted_empty == null) && field === '' && this.options.quoted_string)) {
          newrecord += quote + quote;
        }
        if (i !== record.length - 1) {
          newrecord += delimiter;
        }
      }
      record = newrecord;
    }
    return record;
  }

  // ## `Stringifier.prototype.headers`

  // Print the header line if the option "header" is "true".
  headers() {
    var headers;
    if (!this.options.header) {
      return;
    }
    if (!this.options.columns) {
      return;
    }
    headers = this.options.columns.map(function(column) {
      return column.header;
    });
    if (this.options.eof) {
      headers = this.stringify(headers) + this.options.record_delimiter;
    } else {
      headers = this.stringify(headers);
    }
    return this.push(headers);
  }

  __cast(value, context) {
    var err, type;
    type = typeof value;
    try {
      if (type === 'string') {
        // Fine for 99% of the cases
        return [void 0, this.options.cast.string(value, context)];
      } else if (type === 'number') {
        return [void 0, this.options.cast.number(value, context)];
      } else if (type === 'boolean') {
        return [void 0, this.options.cast.boolean(value, context)];
      } else if (value instanceof Date) {
        return [void 0, this.options.cast.date(value, context)];
      } else if (type === 'object' && value !== null) {
        return [void 0, this.options.cast.object(value, context)];
      } else {
        return [void 0, value, value];
      }
    } catch (error) {
      err = error;
      return [err];
    }
  }

  // ## `Stringifier.prototype.normalize_columns`
  normalize_columns(columns) {
    var column, k, v;
    if (columns == null) {
      return null;
    }
    if (columns != null) {
      if (typeof columns !== 'object') {
        throw Error('Invalid option "columns": expect an array or an object');
      }
      if (!Array.isArray(columns)) {
        columns = (function() {
          var results;
          results = [];
          for (k in columns) {
            v = columns[k];
            results.push({
              key: k,
              header: v
            });
          }
          return results;
        })();
      } else {
        columns = (function() {
          var j, len, results;
          results = [];
          for (j = 0, len = columns.length; j < len; j++) {
            column = columns[j];
            if (typeof column === 'string') {
              results.push({
                key: column,
                header: column
              });
            } else if (typeof column === 'object' && (column != null) && !Array.isArray(column)) {
              if (!column.key) {
                throw Error('Invalid column definition: property "key" is required');
              }
              if (column.header == null) {
                column.header = column.key;
              }
              results.push(column);
            } else {
              throw Error('Invalid column definition: expect a string or an object');
            }
          }
          return results;
        })();
      }
    }
    return columns;
  }

};

module.exports.Stringifier = Stringifier;

underscore = function(str) {
  return str.replace(/([A-Z])/g, function(_, match, index) {
    return '_' + match.toLowerCase();
  });
};
