/******************************************************************************
 * Copyright (c) 2000-2025 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors:
 *   Baranyi, Botond – initial implementation
 *
 ******************************************************************************/
module TtemplateHexstr {

type component templateHexstr_mycomp {};

type record templateHexstr_rec {
  hexstring x1,
  hexstring x2,
  hexstring x3 optional
}
with {
  encode "JSON";
  variant(x1) "JSON: name as first";
  variant(x2) "JSON: name as second";
  variant(x3) "JSON: name as third";
}

type record decmatch_rec {
  integer i,
  charstring s
}
with {
  encode "JSON";
}

type record of integer decmatch_list
with {
  encode "XML";
  variant "list";
}

type union decmatch_uni {
  integer i,
  charstring s
}
with {
  encode "RAW";
  variant(i) "FIELDLENGTH(16)";
}

template decmatch_uni decmatch_tUnion := { i := ? };

template decmatch_rec decmatch_tRecord := { i := (0..infinity), s := ? };

template templateHexstr_rec templateHexstr_tDecmatch := { // decoded content match
  x1 := decmatch decmatch_list: { (1..10), (11..20), (21..30) },
  x2 := decmatch decmatch_tUnion,
  x3 := decmatch modifies decmatch_tRecord := { s := "abc" }
};

template templateHexstr_rec templateHexstr_tDecmatchSelfRef := { // decoded content match with self-reference
  x1 := '01A'H,
  x2 := decmatch templateHexstr_tDecmatchSelfRef.x1,
  x3 := decmatch templateHexstr_rec: { x1 := templateHexstr_tDecmatchSelfRef.x1, x2 := ?, x3 := * }
};

testcase templateHexstrDecmatch() runs on templateHexstr_mycomp {
  var decmatch_rec bad_rec, good_rec;
  bad_rec := { i := 11, s := "xyz" };
  good_rec := { i := 3, s := "abc" };
  var decmatch_list bad_list, good_list;
  bad_list := { 4, 7, 10 };
  good_list := { 2, 15, 28 };
  var decmatch_uni bad_uni, good_uni;
  bad_uni := { s := "five" };
  good_uni := { i := 5 };
  var hexstring bad_rec_enc, good_rec_enc, bad_list_enc, good_list_enc, bad_uni_enc, good_uni_enc;
  bad_rec_enc := bit2hex(encvalue(bad_rec));
  good_rec_enc := bit2hex(encvalue(good_rec));
  bad_list_enc := bit2hex(encvalue(bad_list));
  good_list_enc := bit2hex(encvalue(good_list));
  bad_uni_enc := bit2hex(encvalue(bad_uni));
  good_uni_enc := bit2hex(encvalue(good_uni));
  var templateHexstr_rec r1, r2, r3, r4, r5;
  r1 := { x1 := good_list_enc, x2 := good_uni_enc, x3 := good_rec_enc };
  r2 := { x1 := bad_list_enc,  x2 := good_uni_enc, x3 := good_rec_enc };
  r3 := { x1 := good_list_enc, x2 := bad_uni_enc,  x3 := good_rec_enc };
  r4 := { x1 := good_list_enc, x2 := good_uni_enc, x3 := bad_rec_enc  };
  r5 := { x1 := good_list_enc, x2 := good_uni_enc, x3 := '00FF'H      };
  // match: all 3 are good
  if (match(r1, templateHexstr_tDecmatch)) { setverdict(pass); }
  else { setverdict(fail, 1); }
  // no match: decoded list does not match
  if (not match(r2, templateHexstr_tDecmatch)) { setverdict(pass); }
  else { setverdict(fail, 2); }
  // no match: decoded union does not match
  if (not match(r3, templateHexstr_tDecmatch)) { setverdict(pass); }
  else { setverdict(fail, 3); }
  // no match: decoded record does not match
  if (not match(r4, templateHexstr_tDecmatch)) { setverdict(pass); }
  else { setverdict(fail, 4); }
  // no match: x3 is not a valid encoded record value
  if (not match(r5, templateHexstr_tDecmatch)) { setverdict(pass); }
  else { setverdict(fail, 5); }
  // match r1 with the same template declared as an in-line template
  if (match(r1, templateHexstr_rec: {
    x1 := decmatch decmatch_list: { (1..10), (11..20), (21..30) },
    x2 := decmatch decmatch_tUnion,
    x3 := decmatch modifies decmatch_tRecord := { s := "abc" }
  })) { setverdict(pass); }
  else { setverdict(fail, 6); }
}

external function ef_enc_rec_x1(in templateHexstr_rec.x1 x) return octetstring
with { extension "prototype(convert) encode(JSON)" }

testcase templateHexstrDecmatchSelfRef() runs on templateHexstr_mycomp {
  // global self-referencing template
  var templateHexstr_rec.x1 bad_hs, good_hs;
  bad_hs := 'FE98'H;
  good_hs := '01A'H;
  var templateHexstr_rec bad_rec, good_rec;
  bad_rec := { x1 := 'D01'H, x2 := 'D02'H, x3 := 'D03'H };
  good_rec := { x1 := '01A'H, x2 := 'D02'H, x3 := 'D03'H };
  var hexstring bad_hs_enc, good_hs_enc, bad_rec_enc, good_rec_enc;
  bad_hs_enc := oct2hex(ef_enc_rec_x1(bad_hs));
  good_hs_enc := oct2hex(ef_enc_rec_x1(good_hs));
  bad_rec_enc := bit2hex(encvalue(bad_rec));
  good_rec_enc := bit2hex(encvalue(good_rec));
  var templateHexstr_rec r1, r2, r3;
  r1 := { x1 := '01A'H, x2 := good_hs_enc, x3 := good_rec_enc };
  r2 := { x1 := '01A'H, x2 := bad_hs_enc,  x3 := good_rec_enc };
  r3 := { x1 := '01A'H, x2 := good_hs_enc, x3 := bad_rec_enc  };
  // match: all 2 are good
  if (match(r1, templateHexstr_tDecmatchSelfRef)) { setverdict(pass); }
  else { setverdict(fail, 1); }
  // no match: decoded octetstring does not match
  if (not match(r2, templateHexstr_tDecmatchSelfRef)) { setverdict(pass); }
  else { setverdict(fail, 2); }
  // no match: decoded record does not match
  if (not match(r3, templateHexstr_tDecmatchSelfRef)) { setverdict(pass); }
  else { setverdict(fail, 3); }
  
  // local self-referencing template
  var template templateHexstr_rec t := { x1 := '01A'H, x2 := ?, x3 := ? };
  t.x1 := decmatch t;
  var templateHexstr_rec r4, r5;
  r4 := { x1 := good_rec_enc, x2 := 'A1'H, x3 := 'A2'H };
  r5 := { x1 := bad_rec_enc,  x2 := 'A1'H, x3 := 'A2'H };
  if (match(r4, t)) { setverdict(pass); }
  else { setverdict(fail, 4); }
  if (not match(r5, t)) { setverdict(pass); }
  else { setverdict(fail, 5); }
}

function f_is_negative(hexstring p_par) return boolean {
  // assume the first bit of the first byte is the sign bit
  if (lengthof(p_par) mod 2 != 0) {
    // the sign bit is in the zero nibble before the start of the hexstring
    return false;
  }
  var bitstring v_first_nibble := hex2bit(p_par[0]);
  return v_first_nibble[0] == '1'B;
}

template hexstring t_conjunction := conjunct(? length(1..2), @dynamic f_is_negative);

template hexstring t_implication := '*A'H implies @dynamic f_is_negative;

// matches if the number of letters ('A' to 'F') in the hexstring matches the template parameter
template hexstring t_dynamic(template integer pt_letters) := @dynamic {
  var integer v_num_letters := 0;
  for (var integer i := 0; i < lengthof(value); i := i + 1) {
    if (match(value[i], ('A'H, 'B'H, 'C'H, 'D'H, 'E'H, 'F'H))) {
      v_num_letters := v_num_letters + 1;
    }
  }
  return match(v_num_letters, pt_letters);
}

testcase templateHexstrConjunction() runs on templateHexstr_mycomp {
  var hexstring v1 := '80'H, v2 := 'E'H;
  if (not match(v1, t_conjunction)) {
    setverdict(fail, match(v1, t_conjunction));
  }
  if (match(v2, t_conjunction)) {
    setverdict(fail, match(v2, t_conjunction));
  }
  setverdict(pass);
}

testcase templateHexstrImplication() runs on templateHexstr_mycomp {
  var hexstring v1 := 'D0'H, v2 := '4A'H, v3 := 'EA'H;
  if (not match(v1, t_implication)) {
    setverdict(fail, match(v1, t_implication));
  }
  if (match(v2, t_implication)) {
    setverdict(fail, match(v2, t_implication));
  }
  if (not match(v3, t_implication)) {
    setverdict(fail, match(v3, t_implication));
  }
  setverdict(pass);
}

testcase templateHexstrDynamic() runs on templateHexstr_mycomp {
  var hexstring v1 := 'D65A1F'H, v2 := '27A009'H;
  var template integer vt1 := 3, vt2 := (3..4);
  if (not match(v1, t_dynamic(vt1))) {
    setverdict(fail, match(v1, t_dynamic(vt1)));
  }
  if (match(v2, t_dynamic(vt2))) {
    setverdict(fail, match(v2, t_dynamic(vt2)));
  }
  setverdict(pass);
}

control {
  execute(templateHexstrDecmatch());
  execute(templateHexstrDecmatchSelfRef());
  execute(templateHexstrConjunction());
  execute(templateHexstrImplication());
  execute(templateHexstrDynamic());
}

}
