text: // Copyright © 2020 Hoani Bryson
  // License: MIT (https://mit-license.org/)
  //
  // Codec
  //
  // L3aP codec for encoding and decoding packets
  //
  
  const utf8 = require('utf8');
  const packet = require('./packet');
  const typeCodec = require('./typeCodec');
  const mapGenerator = require('./mapGenerator');
  const verify = require('./verify');
  
  
  exports.Codec = class Codec {
    constructor(input) {
      this.is_valid = false;
      this.encode_map = {};
      this.decode_map = {};
      this.start_to_category_map = {};
      this._config = {};
  
      if (verify.verify_quiet(input)) {
        this._config = input;
        this.is_valid = true;
      }
  
      if (this.is_valid) {
        [this.encode_map, this.decode_map] = mapGenerator.generate_maps(
          this._config
        );
        this._map_categories();
      }
    }
  
    valid() {
      return this.is_valid;
    }
  
    encode(packets){
      if (this._is_valid == false) {
        return utf8.encode("");
      }
      if ((packets instanceof packet.Packet) == true) {
        packets = [packets];
      }
      else if (typeof packets != "object") {
        return utf8.encode("");
      }
  
      let encoded = "";
  
      for (let i in packets) {
        if (encoded != "") {
          encoded += this._config["end"];
        }
        const encoded_packet = this._encode_packet(packets[i]);
        if (encoded_packet == null) {
          return utf8.encode('');
        }
        else {
          encoded += encoded_packet;
        }
      }
      encoded += this._config["end"];
      return utf8.encode(encoded);
    }
  
    _encode_packet(_packet) {
      let internal = "";
      const start = this._config["category"][_packet.category];
  
      if ((_packet.category in this._config.category) === false) {
        return null;
      }
  
      for (let j in _packet.paths) {
        const path = _packet.paths[j];
        const payload = _packet.payloads[j];
  
        if (path != null) {
          // Detect compound packet and add compound character
          if (internal != "") {
            internal += this._config["compound"];
          }
  
          const encoded_single = this._encode_single(path, payload);
          if (encoded_single != null) {
            internal += encoded_single;
          }
          else {
            return null;
          }
        }
      }
      return start + internal;
    }
  
    _encode_single(path, payload) {
      let encode_data;
      let encoded = "";
  
      if (path in this.encode_map) {
        encode_data = this.encode_map[path];
      }
      else {
        console.log(`Could not encode with invalid branch: ${path}`);
        return null;
      }
  
      encoded += encode_data.addr;
  
      if (payload != null) {
        const count = Math.min(encode_data.types.length, payload.length);
        for (let i = 0; i < count; i++) {
          encoded += this._config["separator"];
          encoded += typeCodec.encode_types(payload[i], encode_data.types[i]);
        }
      }
  
      return encoded;
    }
  
  
    decode(encoded) {
      if (this._is_valid == false) {
        return [encoded, []];
      }
  
      let strings = encoded.split(
        utf8.encode(this._config["end"])
      );
      const remainder = strings.slice(strings.length-1, strings.length);
      const packets = [];
  
      if (strings.length == 1) {
        return [remainder, []];
      }
  
      strings = strings.slice(0, strings.length - 1);
  
      for(let i in strings) {
        const p = this._decode_packet(strings[i]);
        if (p != null) {
          packets.push(p);
        }
      }
      return [remainder, packets];
    }
  
    _decode_packet(string) {
      string = utf8.decode(string);
      const start = string[0];
      const category = this._category_from_start(start);
      if (category == null) {
        return null;
      }
      else {
        let _packet = new packet.Packet(category);
  
        string = string.slice(1, string.length);
        const subpackets = string.split(this._config["compound"]);
  
        for (let j in subpackets) {
          const data = this._decode_subpacket(subpackets[j]);
          if (data != null) {
            const [path, payload] = data;
            _packet.add(path, payload);
          }
          else {
            _packet = null;
          }
        }
        return _packet;
      }
    }
  
    _decode_subpacket(subpacket) {
      const parts = subpacket.split(this._config["separator"]);
  
      if (parts != ['']) {
        const payload = [];
        const addr = parts[0];
        if (addr in this.decode_map) {
          const decode_data = this.decode_map[addr];
          const encoded_payload = parts.slice(1, parts.length);
  
          for (let k = 0; k < encoded_payload.length; k++) {
            const item = encoded_payload[k];
            const type = decode_data.types[k];
            payload.push(typeCodec.decode_types(item, type));
          }
  
          return [decode_data.path, payload]
        }
      }
      return null;
    }
  
    unpack(_packet) {
      const result = {};
      for (let i = 0; i < _packet.paths.length; i++) {
        const path = _packet.paths[i];
        const payload = _packet.payloads[i];
        const unpack_data = this.encode_map[path];
  
        const value_count = Math.min(
          payload.length,
          unpack_data.data_branches.length
          );
  
        for (let j = 0; j < value_count; j++) {
          const branch = unpack_data.data_branches[j];
          const value = payload[j];
          result[branch] = value;
        }
      }
      return result;
    }
  
    _map_categories() {
      const categories = this._config['category'];
      const keys = Object.keys(categories);
  
      for (i in keys) {
        const key = keys[i];
        const start = categories[key];
  
        this.start_to_category_map[start] = key;
      }
    }
  
    _category_from_start(start) {
      if (start in this.start_to_category_map) {
        return this.start_to_category_map[start];
      }
      return null;
    }
  
  }
  