/**
Copyright: 2024 Contrast Security, Inc
  Contact: support@contrastsecurity.com
  License: Commercial

  NOTICE: This Software and the patented inventions embodied within may only be
  used as part of Contrast Security’s commercial offerings. Even though it is
  made available through public repositories, use of this Software is subject to
  the applicable End User Licensing Agreement found at
  https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
  between Contrast Security and the End User. The Software may not be reverse
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
  way not consistent with the End User License Agreement.
*/
/* global Buffer*/

/**
 * @module lib/reporter/models/Finding/Event
 * EventDTM:
 * private EventActionDTM action;
 * private EventTypeDTM type;
 * private long time;
 * private String thread;
 * private String source;
 * private String target;
 *
 * private ObjectDTM object;
 * private List<ObjectDTM> args;
 * private ObjectDTM ret;
 * private List<StackDTM> stack;
 * private String fieldName;
 * private String context;
 * private String code;
 * private List<EventPropertyDTM> properties;
 * private String tags = ""; // Comma-separated list of tags
 *
 * // for legacy agents
 * @Deprecated private String bitset;
 *
 * // for post digital rain agents
 * private EventSignatureDTM signature;
 * private List<EventTaintRangeDTM> trackedRanges;
 *
 * private long objectId = UNDEFINED_OBJECT_ID;
 *
 * private Set<EventSourceDTM> eventSources;
 * private Set<ParentObjectIdDTM> parentObjectIds;
 */

'use strict';

const events = require('../../../assess/models');
const { SourceEvent } = events;
const EventTag = require('../../../reporter/models/event-tag');

const MAX_VALUE_LENGTH = '50';

/**
 * Object representation expected by TS
 */
class ObjectDTM {
  /**
   * @param {CallContext.obj|CallContext.args|CallContext.result} prop
   */
  constructor(obj, tracked) {
    this.hash = '0'; // Is there anything more useful we would put here?
    this.tracked = tracked;
    this.value = this.truncateAndEncode(obj);
  }

  truncateAndEncode(obj) {
    const truncated = truncateMiddle(obj, MAX_VALUE_LENGTH);
    return Buffer.from(truncated).toString('base64');
  }
}

/**
 * First pass at EventDTM; this is more or less the representation of an
 * event object expected by Teamserver.
 */
class Event {
  /**
   * Creates a serializable Event object for Teamserver
   *
   * @param {BaseEvent} event
   */
  constructor(event) {
    event.expand();

    this.action = event.action;
    this.type = event.type;
    this.time = event.time;
    this.thread = event.thread;
    this.source = event.source;
    this.target = event.target;
    this.signature = event.signature;
    this.stack = event.context.stack;
    this.object = new ObjectDTM(event.context.obj, event.context.objTracked);
    this.ret = new ObjectDTM(event.context.result, event.context.resultTracked);
    this.args = [];
    for (let i = 0; i < event.context.args.length; i++) {
      this.args.push(
        new ObjectDTM(event.context.args[i], event.context.argsTracked[i])
      );
    }

    
    const displayRanges = event.context.argsDisplayRanges.filter((tagRange) => Object.keys(tagRange).length);;

    if (displayRanges.length) {
      // If displayRanges is non-empty (=/= [{}]), use that instead
      // since it's more accurate when reporting
      event.tagRanges = displayRanges;
    }

    if (event.code) {
      this.context = truncateMiddle(event.code, MAX_VALUE_LENGTH);
    }

    this.trackedRanges = event.tagRanges.map(
      (tagRange) => new EventTag(tagRange)
    );

    this.__eventType = event.constructor.name; // FIXME(ehden) kind of a hack
    if (this.__eventType === 'SessionEvent') {
      this.__eventType = 'SinkEvent';
    }

    if (event instanceof SourceEvent) {
      this.eventSources = event.eventSources;
    }
  }
}

module.exports = Event;
module.exports.ObjectDTM = ObjectDTM;

/**
 * Truncates a string such that its length does not exceed
 * the given maximum. The string is shortened by removing the
 * middle section, preserving its beginning and ending.
 * @param   {String} val       The string to shorten
 * @param   {Number} maxLength Truncate if longer than this value
 * @returns {String}           The shortened string
 */
function truncateMiddle(val, maxLength) {
  const offset = maxLength / 2;
  const { length } = val;
  const isLong = maxLength < length;

  return !isLong
    ? val
    : [
        val.substring(0, offset),
        ' ... ',
        val.substring(val.length - offset)
      ].join('');
}
