/*
  Message
*/

#include "message.h"

#include "config.h"
#include "head.h"
#include "mem.h"
#include "os.h"
#include "print.h"

/*
**      Coded Message Routine Global Variables
*/

#if EARLY
/* Bind at runtime */
static char *table;
static void (*letter) (char);
static word(*find_mode) (char);
#else
/* Only one option, so do it at link time */
#define table          table_2
#define letter         (letter_v3)
#define find_mode      (find_mode_v3)
#endif

static int print_mode;
static int single_mode;
static word word_bank;

/*
**      Character Table
*/

#if EARLY
static char table_1[] = {
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
  'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
  'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
  'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
  'W', 'X', 'Y', 'Z', ' ', '0', '1', '2', '3', '4', '5', '6',
  '7', '8', '9', '.', ',', '!', '?', '_', '#', '\'', '\"',
  '/', '\\', '<', '-', ':', '(', ')', '\0', '\0'
};
#endif

static char table_2[] = {
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
  'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
  'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
  'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
  'W', 'X', 'Y', 'Z', ' ', ' ', '0', '1', '2', '3', '4', '5',
  '6', '7', '8', '9', '.', ',', '!', '?', '_', '#', '\'',
  '\"', '/', '\\', '-', ':', '(', ')', '\0', '\0'
};

#if EARLY
static void letter_v1(char ch)
{
  extern void (*PrintChar) (word);

  switch(ch)
  {
    case 0:
      (*PrintChar) ((word) ' ');
      single_mode = print_mode;
      break;
    case 1:
      new_line();
      single_mode = print_mode;
      break;
    case 2:
      single_mode = (single_mode + 1) % 3;
      break;
    case 3:
      single_mode = (single_mode + 2) % 3;
      break;
    case 4:
      print_mode  = (single_mode + 1) % 3;
      single_mode = print_mode;
      break;
    case 5:
      print_mode  = (single_mode + 2) % 3;
      single_mode = print_mode;
      break;
    case 6:
      if(single_mode == 2)
      {
        single_mode = 3;
        break;
      }
      /* else fall through */
    default:
      (*PrintChar) ((word) table[(single_mode * 26) + ch - 6]);
      single_mode = print_mode;
      break;
  }
}
#endif

#if EARLY
static void letter_v2(char ch)
{
  extern void (*PrintChar) (word);

  switch(ch)
  {
    case 0:
      (*PrintChar) ((word) ' ');
      single_mode = print_mode;
      break;
    case 1:
      single_mode |= 0x80;
      word_bank = (ch - 1) << 6;
      break;
    case 2:
      single_mode = (single_mode + 1) % 3;
      break;
    case 3:
      single_mode = (single_mode + 2) % 3;
      break;
    case 4:
      print_mode  = (single_mode + 1) % 3;
      single_mode = print_mode;
      break;
    case 5:
      print_mode  = (single_mode + 2) % 3;
      single_mode = print_mode;
      break;
    case 6:
    case 7:
      if(single_mode == 2)
      {
        if(ch == 6)
        {
          single_mode = 3;
        }
        else
        {
          new_line();
          single_mode = print_mode;
        }
        break;
      }
      /* else fall through */
    default:
      (*PrintChar) ((word) table[(single_mode * 26) + ch - 6]);
      single_mode = print_mode;
      break;
  }
}
#endif

static void letter_v3(char ch)
{
  extern void (*PrintChar) (word);

  switch(ch)
  {
    case 0:
      (*PrintChar) ((word) ' ');
      single_mode = print_mode;
      break;
    case 1:
    case 2:
    case 3:
      single_mode |= 0x80;
      word_bank = (ch - 1) << 6;
      break;
    case 4:
    case 5:
      if(single_mode == 0)
      {
        single_mode = ch - 3;
      }
      else
      {
        if(single_mode != ch - 3)
          single_mode = 0;
        print_mode = single_mode;
      }
      break;
    case 6:
    case 7:
      if(single_mode == 2)
      {
        if(ch == 6)
        {
          single_mode = 3;
        }
        else
        {
          new_line();
          single_mode = print_mode;
        }
        break;
      }
      /* else fall through */
    default:
      (*PrintChar) ((word) table[(single_mode * 26) + ch - 6]);
      single_mode = print_mode;
      break;
  }
}

#if EARLY
static word find_mode_v1(char ch)
{
  if(ch == 0)
  {
    return 3 + 3;
  }
  if('a' <= ch && ch <= 'z')
  {
    return 0;
  }
  if('A' <= ch && ch <= 'Z')
  {
    return 1 + 1;
  }
  return 2 + 1;
}
#endif

static word find_mode_v3(char ch)
{
  if(ch == 0)
  {
    return 3 + 3;
  }
  if('a' <= ch && ch <= 'z')
  {
    return 0;
  }
  if('A' <= ch && ch <= 'Z')
  {
    return 1 + 3;
  }
  return 2 + 3;
}

static word convert(char ch)
{
  int i = os_strpos(table, ch);

  if(i >= 0)
  {
    word code = i + 6;
    while(code >= 0x20)
      code -= 0x1A;
    return code;
  }
  else
  {
    return 0;
  }
}

static void decode_char(char ch)
{
  extern void (*PrintChar) (word);
  if(single_mode & 0x80)
  {
    int save_mode = print_mode;
    long_word a   = hd_common() + (word_bank + 2L * ch);
    word page     = rd_byte_addr(a);
    word offset   = rd_byte_addr(a+1) << 1;
    print_coded(&page, &offset);
    single_mode = print_mode = save_mode;
  }
  else if(single_mode & 0x40)
  {
    (*PrintChar) (ch + ((single_mode & 0x03) << 5));
    single_mode = print_mode;
  }
  else if(single_mode < 3)
  {
    (*letter) (ch);
  }
  else if(single_mode == 3)
  {
    single_mode = 0x40 + ch;
  }
}

static void decode(word data)
{
  decode_char((data >> (5*2)) & 0x1F);
  decode_char((data >> (5*1)) & 0x1F);
  decode_char((data >> (5*0)) & 0x1F);
}

/*
**      Decode Routines
*/

void init_message(void)
{
#if EARLY
  switch(hd_version())
  {
    case VERSION_1:
      table     = table_1;
      letter    = letter_v1;
      find_mode = find_mode_v1;
      break;
    case VERSION_2:
      table     = table_2;
      letter    = letter_v2;
      find_mode = find_mode_v1;
      break;
    default:
      table     = table_2;
      letter    = letter_v3;
      find_mode = find_mode_v3;
      break;
  }
#endif
}

void print_coded(word *page, word *offset)
{
  word data;
  word p = *page;
  word o = *offset;

  /*
    Print mode
    0    : Lower Case Letter
    1    : Upper Case Letter
    2    : Number or Symbol
    3    : ASCII Letter - first byte
    0x40 : ASCII Letter - second byte
    0x80 : Common Word
  */

  print_mode  = 0;
  single_mode = 0;

  do
  {
    data = rd_word_seg(p, o);
    o += 2;
    if(o >= BLOCK_SIZE)
    {
      o -= BLOCK_SIZE;
      p += 1;
    }
    decode(data);
  } while((data & 0x8000) == 0);
  *page   = p;
  *offset = o;
}

/*
**      Encode Routines
*/

void encode(byte *the_word, word coded[])
{
  int plus = hd_plus();
  int cpw = plus ? PLUS_CHARS_PER_WORD : STD_CHARS_PER_WORD;
  int esz = plus ? PLUS_ENCODED_SIZE : STD_ENCODED_SIZE;
  word data[max(STD_CHARS_PER_WORD, PLUS_CHARS_PER_WORD)];
  int count = 0;
  while(count < cpw)
  {
    byte ch = *the_word++;
    if(ch == 0)
    {
      /* Finished, so fill with blanks */
      while(count < cpw)
        data[count++] = 5;
    }
    else
    {
      /* Get Character Print-Mode */
      word mode = (*find_mode) ((char) ch);
      if(mode != 0) data[count++] = mode;
      /* Get offset of character in Table[] */
      if(count < cpw)
      {
        word offset = convert((char) ch);
        if(offset == 0)
        {
          /* Character not in Table[], so use ASCII */
          data[count++] = 6;
          if(count < cpw) data[count++] = ch >> 5;
          if(count < cpw) data[count++] = ch & 0x1F;
        }
        else
        {
          data[count++] = offset;
        }
      }
    }
  }
  /* Encrypt */
  for(count = 0; count < esz; count++)
    coded[count] = (data[count * 3 + 0] << 10)
                 | (data[count * 3 + 1] << 5)
                 | (data[count * 3 + 2] << 0);
  coded[esz - 1] |= 0x8000;
}
