/**
 * Various utilities and constants for communicating with the python sandbox.
 */
import * as MemBuffer from 'app/common/MemBuffer';
import log from 'app/server/lib/log';


/**
 * SandboxError is an error type for reporting errors forwarded from the sandbox.
 */
export class SandboxError extends Error {
  constructor(message: string) {
    super("[Sandbox] " + (message || 'Python reported an error'));
  }
}


/**
 * Special msgCode values that precede msgBody to indicate what kind of message it is.
 * These all cost one byte. If we needed more, we should probably switch to a number (5 bytes)
 *    CALL = call to the other side. The data must be an array of [func_name, arguments...]
 *    DATA = data must be a value to return to a call from the other side
 *    EXC = data must be an exception to return to a call from the other side
 */
export const CALL = null;
export const DATA = true;
export const EXC = false;


/**
 * Returns a function that takes data buffers and logs them to log.info() with the given prefix.
 * The logged output is line-oriented, so that the prefix is only inserted at the start of a line.
 * Binary data is encoded as with JSON.stringify.
 */
export function makeLinePrefixer(prefix: string, logMeta: object) {
  return _makeLinePrefixer(prefix, logMeta, text => text.indexOf('\n'));
}

/**
 * Same as makeLinePrefixer, but avoids splitting lines except when a line starts with '[', since
 * the sandbox prefixes all log messages with "[LEVEL]" prefix.
 */
export function makeLogLinePrefixer(prefix: string, logMeta: object) {
  return _makeLinePrefixer(prefix, logMeta, text => {
    const newline = text.indexOf("\n[");
    // If no next log message, split at the last newline. Any earlier newlines would be included.
    return (newline !== -1) ? newline : text.lastIndexOf("\n");
  });
}

function _makeLinePrefixer(prefix: string, logMeta: object, findLineEnd: (text: string) => number) {
  let partial = '';
  return (data: Uint8Array) => {
    partial += MemBuffer.arrayToString(data);
    let newline;
    while (partial && (newline = findLineEnd(partial)) !== -1) {
      const line = partial.slice(0, newline);
      partial = partial.slice(newline + 1);
      // Escape some parts of the string by serializing it to JSON (without the quotes).
      log.origLog('info', "%s%s", prefix,
        JSON.stringify(line).slice(1, -1).replace(/\\(['"\\])/g, '$1').replace(/\\n/g, '\n'),
        logMeta);
    }
  };
}