/* poked.pk - GNU poke daemon */

/* Copyright (C) 2022, 2023 Mohammad-Reza Nabipoor */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

load "pk-dump-minimal.pk";

// FIXME be endian-correct
// FIXME offset<long,N> -> offset<ulong,N>

// `poked.c' will initialize these to the right value.
var __poked_pdap_version = -1;
var __poked_libpoke_version = "";

fun poked_pdap_version = int<32>: { return __poked_pdap_version; }
fun poked_libpoke_version = string: { return __poked_libpoke_version; }

var __poked_restart_p = 0;
var __poked_exit_p = 0;

fun poked_restart = void:
  {
    for (fd in iolist)
      try { close (fd); }
      catch {}
    __poked_restart_p = 1;
    exit;
  }
fun poked_exit = void: { __poked_exit_p = 1; exit; }
fun poked_defer = void: {}

type Poked_Hook = () void;
var poked_after_cmd_hook = Poked_Hook[] ();

fun __poked_run_after_cmd_hooks = void:
  {
    for (h in poked_after_cmd_hook)
      h;
  }

//--- send over channels

var __poked_chan_send_p = 0;
var __poked_chan_send_chan = uint<7>[] ();
var __poked_chan_send_buf = byte[][] ();

fun __poked_chan_send_reset = void:
  {
    __poked_chan_send_p = 0;
    __poked_chan_send_chan = uint<7>[] ();
    __poked_chan_send_buf = byte[][] ();
  }

fun poked_chan_send = (uint<7> chan, byte[] data) void:
  {
    assert (0 < data'length && data'length <= 0xffff);
    assert (0 < chan && chan <= 120);

    apush (__poked_chan_send_chan, chan);
    apush (__poked_chan_send_buf, data + byte[0] ()); // Force a copy
    __poked_chan_send_p = 1;
  }

//--- default exception handler

fun __err_send = (string s) void:
  {
    var c = byte[s'length] ();

    stoca (s, c);
    poked_chan_send (/*USOCK_CHAN_OUT_OUT*/ 1, [/*OUTCMD_ERR*/ 7UB] + c);
  }

fun poked_ehandler = (Exception exception) void:
  {
    var msg = "unhandled ";

    if (exception.code != EC_exit && exception.code != EC_signal)
      {
        msg += (exception.name == "" ? "unknown" : exception.name)
               + " exception";

        if (exception.location != "" || exception.msg != "")
          {
            msg += "\n";
            if (exception.location != "")
              msg += exception.location + " ";
            msg += exception.msg;
          }
      }
    __err_send (msg);
    if (exception.code == EC_exit)
      __poked_exit_p = 1;
  }

//--- vu

var __poked_dots = [[0L#N,0L#N]];

fun plet_vu_dots = offset<long,N>[2]:
  { return __poked_dots[__poked_dots'length - 1]; }
fun plet_vu_dot = offset<long,N>:
  { return plet_vu_dots[0]; }
fun plet_vu_dot2 = offset<long,N>:
  { return plet_vu_dots[1]; }
fun plet_vu_dots_set = (offset<long,N> d1, offset<long,N> d2) void:
  {
    __poked_dots += [[d1, d2]]; // TODO put a limit
  }
fun plet_vu_dot_set_txtcoord = (int line1, int col1, int line2, int col2) void:
  {
/*
00000000: 2550 4446 2d31 2e34 0a25 c3a4 c3bc c3b6  %PDF-1.4.%......
00000010: c39f 0a32 2030 206f 626a 0a3c 3c2f 4c65  ...2 0 obj.<</Le
00000020: 6e67 7468 2033 2030 2052 2f46 696c 7465  ngth 3 0 R/Filte
00000030: 722f 466c 6174 6544 6563 6f64 653e 3e0a  r/FlateDecode>>.
00000040: 7374 7265 616d 0a78 9c6d 4fcb 0ac2 400c  stream.x.mO...@.
00000050: bcef 57e4 2cb8 cd3e 92b5 b02c d887 a0e0  ..W.,..>...,....
00000060: a1b0 e041 3c88 b652 10c1 52f0 f7dd 563c  ...A<..R..R...V<
00000070: 0832 1092 c964 92a0 54f0 12d9 7a18 fbee  .2...d..T...z...
*/

    assert (line1 <= line2);

    col1 -= 10 - 1;
    col2 -= 10 - 1;

    fun nibble_pos = (int col) int:
      {
        if (1 < col && col <= 35 && col % 5 == 0)
          col += 1;

        var iword = col / 5;
        var inibble = col % 5;

        return iword * 4 + inibble;
      }

    col1 = nibble_pos (col1) - 1;
    col2 = nibble_pos (col2) - 1;
    line1 -= 1;
    line2 -= 1;
    var d1 = (line1 * 32 + col1) as long;
    var d2 = (line2 * 32 + col2) as long;
    plet_vu_dots_set (d1#N, d2#N);

    assert (d1#N <= d2#N);

    // for (a in __plet_vu_dot_update_funcs)
      {
        // try-catch?
        //a.func;
      }
  }

type __Poked_VuArgs = struct
  {
    int ios;
    offset<long,B> from;
    offset<long,B> size;
    offset<long,B> group_by;
    int cluster_by;
    int ascii;
  };
var __plet_vu_do_p = 0,
    __plet_vu_args = __Poked_VuArgs {},
    __plet_vu_filter = "";

fun __plet_vu_dump = void:
  {
    // assert (__plet_vu_do_p);
    __plet_vu_do_p = 0;
    dump :ios __plet_vu_args.ios
         :from __plet_vu_args.from
         :size __plet_vu_args.size
         :group_by __plet_vu_args.group_by
         :cluster_by __plet_vu_args.cluster_by
         :ascii __plet_vu_args.ascii
         :ruler 0
         ;
  }
fun plet_vu = (string filt = __plet_vu_filter,
               string[] filts = string[] (),
               int<32> ios = get_ios,
               offset<long,B> from = pk_dump_get_offset (ios),
               offset<long,B> size = pk_dump_size) void:
  {
    __plet_vu_args = __Poked_VuArgs {
        ios = ios, from = from, size = size, group_by = 2#B,
        cluster_by = 8, ascii = 1,
      };
    if (filts'length != 0)
      {
        var fs = filts[0];

        for (f in filts)
          assert (strchr (f, ';') == f'length,
                  "Invalid filter name (unexpected semi-colon): " + f);
        for (var i = 1; i < filts'length; ++i)
          fs += ";" + filts[i];
        __plet_vu_filter = fs;
      }
    else if (filt != __plet_vu_filter)
      {
        assert (strchr (filt, ';') == filt'length,
                "Invalid filter name (unexpected semi-colon)");
        __plet_vu_filter = filt;
      }
    __plet_vu_do_p = 1;
  }
fun plet_vu_highlight = (offset<long,N> from = plet_vu_dot,
                         offset<long,N> size = plet_vu_dot2 - plet_vu_dot) void:
  {
    var f = from'magnitude;
    var l = (from + size)'magnitude;
    var line1 = f / 32 + 1;
    var line2 = l / 32 + 1;
    var col1 = f % 32;
    var col2 = l % 32;

    col1 += 10 + col1/4;
    col2 += 10 + col2/4;

    var l1 = line1,
        c11 = col1,
        c12 = line2 > line1 ? 49L : col2,
        c13 = 51 + ((f / 2) % 16),
        c14 = 51L + (line2 > line1 ? 16L : ((l / 2) % 16)),
        l2 = line2 - line1 > 1 ? line1 + 1 : 0L,
        l3 = line2 - line1 > 1 ? line2 - 1 : 0L,
        c21 = 10,
        c22 = 49,
        c23 = 51,
        c24 = 51 + 16,
        l4 = line2 > line1 ? line2 : 0L,
        c31 = 10,
        c32 = col2,
        c33 = 51,
        c34 = 51 + ((l / 2) % 16);

    var payload_str = format(
//  l1    , c11 , c12 , c13  , c14,
//                                , l2  , l3  , c21 , c22  , c23, c24 ,
//                                                                    , l4  , c31 , c32 , c33 , c34  ,
    "%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d,%u64d",
    l1, c11, c12, c13, c14, l2, l3, c21, c22, c23, c24, l4, c31, c32, c33, c34);
    var payload = byte[payload_str'size] ();

    stoca (payload_str, payload);
    poked_chan_send (2, [3UB] + payload);
  }

//--- treevu

/* WIP.  */

//--- disasm

type __Poked_Disasm = struct
  {
    string arch;
    little offset<uint64,B> adr;
    byte[] code;
  };

fun plet_disasm = (string arch, offset<uint64,B> adr, byte[] code) void:
  {
    assert (
      arch in ["x86_32", "x86_64", "arm", "arm_thumb", "aarch64", "m68k"]);

    var fd = open("*__poked_disasm*");
    var d = __Poked_Disasm{ arch = arch , adr = adr, code = code };

    __Poked_Disasm @ fd : 0#B = d;
    poked_chan_send (/*USOCK_CHAN_OUT_CDISAS*/ 6, byte[d'size] @ fd : 0#B);
    close (fd);
  }

//--- auto completion

var __poked_autocmpl_kind = uint[] (),
    __poked_autocmpl_string = string[] (),
    __poked_autocmpl_p = 0;
fun __poked_autocmpl_reset = void:
  {
    __poked_autocmpl_kind = uint[] ();
    __poked_autocmpl_string = string[] ();
    __poked_autocmpl_p = 0;
  }

var PLET_AUTOCMPL_IDENT = 1U, /* Identifier: variable, function, type.  */
    PLET_AUTOCMPL_IOS   = 2U; /* IO Space.  */

fun plet_autocomplete = (uint kind, string str) void:
  {
    assert (kind in [PLET_AUTOCMPL_IDENT, PLET_AUTOCMPL_IOS]);

    apush (__poked_autocmpl_kind, kind);
    apush (__poked_autocmpl_string, str);
    __poked_autocmpl_p = 1;
  }

//--- poke disassembly

var PLET_DISAS_KIND_FUNC = 1U,
    PLET_DISAS_KIND_FUNC_NATIVE = 2U,
    PLET_DISAS_KIND_EXPR = 3U,
    PLET_DISAS_KIND_EXPR_NATIVE = 4U;

type __Poked_Disas = struct
  {
    uint<8> kind : 0 < kind && kind <= 4U;
    string  str; /* Function name or Poke expression.  */
  };

var __poked_disas_data = __Poked_Disas[] (),
    __poked_disas_p = 0;
fun __poked_disas_reset = void:
  {
    __poked_disas_data = __Poked_Disas[] ();
    __poked_disas_p = 0;
  }

fun plet_disas_func = (string func_name, int native_p = 0) void:
  {
    var d = __Poked_Disas {
      kind=native_p ? PLET_DISAS_KIND_FUNC_NATIVE : PLET_DISAS_KIND_FUNC,
      str=func_name,
    };

    apush (__poked_disas_data, d);
    __poked_disas_p = 1;
  }

fun plet_disas_expr = (string expr, int native_p = 0) void:
  {
    var d = __Poked_Disas {
      kind=native_p ? PLET_DISAS_KIND_EXPR_NATIVE : PLET_DISAS_KIND_EXPR,
      str=expr,
    };

    apush (__poked_disas_data, d);
    __poked_disas_p = 1;
  }

//---

print "//---\nGNU poke in daemon mode!\n";
