/*
    This file is part of libtermui.

    libtermui 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.

    libtermui 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 libtermui.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2010, Alexandre Becoulet <alexandre.becoulet@free.fr>

    based on Paul Williams' DEC compatible state machine
      -> http://vt100.net/emu/dec_ansi_parser
    based on Joshua Haberman public domain implementation
*/

#include <stdio.h>
#include <stdint.h>

#include "vtemu_pv.h"

/* parser states */
static TERMUI_VTEMU_STATE(termui_vtemu_ground);

static TERMUI_VTEMU_STATE(termui_vtemu_esc);
static TERMUI_VTEMU_STATE(termui_vtemu_esc_intermediate);

static TERMUI_VTEMU_STATE(termui_vtemu_csi_entry);
static TERMUI_VTEMU_STATE(termui_vtemu_csi_ignore);
static TERMUI_VTEMU_STATE(termui_vtemu_csi_param);
static TERMUI_VTEMU_STATE(termui_vtemu_csi_intermediate);

static TERMUI_VTEMU_STATE(termui_vtemu_sos_pm_apc_string);

#ifdef CONFIG_LIBTERMUI_VTEMU_DCS
static TERMUI_VTEMU_STATE(termui_vtemu_dcs_entry);
static TERMUI_VTEMU_STATE(termui_vtemu_dcs_intermediate);
static TERMUI_VTEMU_STATE(termui_vtemu_dcs_ignore);
static TERMUI_VTEMU_STATE(termui_vtemu_dcs_param);
static TERMUI_VTEMU_STATE(termui_vtemu_dcs_passthrough);
#endif

#ifdef CONFIG_LIBTERMUI_VTEMU_OSC
static TERMUI_VTEMU_STATE(termui_vtemu_osc_string);
#endif

#ifdef CONFIG_LIBTERMUI_VTEMU_DEBUG
# define TERMUI_VTEMU_SET_STATE(f)  \
   ctx->st = &f;
#else
# define TERMUI_VTEMU_SET_STATE(f)  \
   ctx->st = &f;                    \
   ctx->func = __func__;
#endif

void
termui_vtemu_parser_init(struct termui_vtemu_s *ctx)
{
  TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
}

static inline void
termui_vtemu_ignore(struct termui_vtemu_s *ctx, uint8_t c)
{
}

static void
termui_vtemu_collect(struct termui_vtemu_s *ctx, uint8_t c)
{
  /* Append the character to the intermediate params */

  if (ctx->intermediate_count < TERMUI_VTEMU_INTERCHAR)
    ctx->intermediate[ctx->intermediate_count++] = c;
}

static void
termui_vtemu_param(struct termui_vtemu_s *ctx, uint8_t c)
{
  /* process the param character */
  if (c == ';')
    {
      if (++ctx->params_count > TERMUI_VTEMU_MAXPARAMS)
	return;

      ctx->params[ctx->params_count - 1] = 0;
    }
  else
    {
      if (ctx->params_count == 0)
	{
	  ctx->params_count = 1;
	  ctx->params[0]  = 0;
	}
      else if (ctx->params_count > TERMUI_VTEMU_MAXPARAMS)
	return;

      ctx->params[ctx->params_count - 1] *= 10;
      ctx->params[ctx->params_count - 1] += c - '0';
    }
}

static void
termui_vtemu_clear(struct termui_vtemu_s *ctx, uint8_t c)
{
  ctx->params_count = 0;
  ctx->intermediate_count = 0;
}

/***************************************** state machine functions */

static TERMUI_VTEMU_STATE(termui_vtemu_anywhere)
{
  switch (c)
    {
    case 0x1b:
      termui_vtemu_clear(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_esc);
      break;

    case 0x18:
    case 0x1a:
#ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x80 ... 0x8f:
    case 0x91 ... 0x97:
    case 0x99:
    case 0x9a:
    case 0x9c:
#endif
      termui_vtemu_execute(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

#ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x98:
    case 0x9e:
    case 0x9f:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_sos_pm_apc_string);
      break;

    case 0x90:
    case 0x9b:
# ifdef CONFIG_LIBTERMUI_VTEMU_DCS
      termui_vtemu_clear(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_entry);
# else
      termui_vtemu_ignore(ctx, c);
# endif
      break;

    case 0x9d:
# ifdef CONFIG_LIBTERMUI_VTEMU_OSC
      termui_vtemu_osc_start(ctx);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_osc_string);
# else
      termui_vtemu_ignore(ctx, c);
# endif
      break;
#endif

#ifdef CONFIG_LIBTERMUI_VTEMU_DEBUG
    default:
      fprintf(stderr, "vtemu: Unhandled char 0x%02x in %s state\n", c, ctx->func);
      break;
#endif
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_ground)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
#ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x80 ... 0x8f:
    case 0x91 ... 0x9a:
    case 0x9c:
#endif
      termui_vtemu_execute(ctx, c);
      break;

    case 0x20 ... 0x7f:
      termui_vtemu_print(ctx, c);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_esc)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_execute(ctx, c);
      break;

    case 0x5d:
# ifdef CONFIG_LIBTERMUI_VTEMU_OSC
      termui_vtemu_osc_start(ctx);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_osc_string);
# else
      termui_vtemu_ignore(ctx, c);
# endif
      break;

    case 0x50:
# ifdef CONFIG_LIBTERMUI_VTEMU_DCS
      termui_vtemu_clear(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_entry);
      break;
# endif

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_esc_intermediate);
      break;

    case 0x30 ... 0x4f:
    case 0x51 ... 0x57:
    case 0x59:       
    case 0x5a:       
    case 0x5c:       
    case 0x60 ... 0x7e: 
      termui_vtemu_esc_dispatch(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

    case 0x5b:
      termui_vtemu_clear(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_entry);
      break;

    case 0x58:
    case 0x5e:
    case 0x5f:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_sos_pm_apc_string);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_esc_intermediate)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_execute(ctx, c);
      break;

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      break;

    case 0x30 ... 0x7e:
      termui_vtemu_esc_dispatch(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_csi_entry)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_execute(ctx, c);
      break;

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_intermediate);
      break;

    case 0x3a:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_ignore);
      break;

    case 0x30 ... 0x39:
    case 0x3b:
      termui_vtemu_param(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_param);
      break;

    case 0x3c ... 0x3f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_param);
      break;

    case 0x40 ... 0x7e:
      termui_vtemu_csi_dispatch(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_csi_ignore)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_execute(ctx, c);
      break;

    case 0x20 ... 0x3f:
    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x40 ... 0x7e:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_csi_param)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_execute(ctx, c);
      break;

    case 0x30 ... 0x39:
    case 0x3b:
      termui_vtemu_param(ctx, c);
      break;

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x3c ... 0x3f:
    case 0x3a:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_ignore);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_intermediate);
      break;

    case 0x40 ... 0x7e:
      termui_vtemu_csi_dispatch(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_csi_intermediate)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_execute(ctx, c);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      break;

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x30 ... 0x3f:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_csi_ignore);
      break;

    case 0x40 ... 0x7e:
      termui_vtemu_csi_dispatch(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

#ifdef CONFIG_LIBTERMUI_VTEMU_DCS

static TERMUI_VTEMU_STATE(termui_vtemu_dcs_entry)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x3a:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_ignore);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_intermediate);
      break;

    case 0x30 ... 0x39:
    case 0x3b:
      termui_vtemu_param(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_param);
      break;

    case 0x3c ... 0x3f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_param);
      break;

    case 0x40 ... 0x7e:
      termui_vtemu_hook(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_passthrough);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_dcs_intermediate)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      break;

    case 0x30 ... 0x3f:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_ignore);
      break;

    case 0x40 ... 0x7e:
      termui_vtemu_hook(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_passthrough);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_dcs_ignore)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
    case 0x20 ... 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

#ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x9c:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;
#endif

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_dcs_param)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x30 ... 0x39:
    case 0x3b:
      termui_vtemu_param(ctx, c);
      break;

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x3c ... 0x3f:
    case 0x3a:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_ignore);
      break;

    case 0x20 ... 0x2f:
      termui_vtemu_collect(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_intermediate);
      break;

    case 0x40 ... 0x7e:
      termui_vtemu_hook(ctx, c);
      TERMUI_VTEMU_SET_STATE(termui_vtemu_dcs_passthrough);
      break;

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

static TERMUI_VTEMU_STATE(termui_vtemu_dcs_passthrough)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
    case 0x20 ... 0x27:
      termui_vtemu_put(ctx, c);
      break;

    case 0x7f:
      termui_vtemu_ignore(ctx, c);
      break;

#ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x9c:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;
#endif

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }

  if (ctx->st != termui_vtemu_dcs_passthrough)
    termui_vtemu_unhook(ctx, c);
}

#endif

static TERMUI_VTEMU_STATE(termui_vtemu_sos_pm_apc_string)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
    case 0x20 ... 0x27:
      termui_vtemu_ignore(ctx, c);
      break;

#ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x9c:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;
#endif

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }
}

#ifdef CONFIG_LIBTERMUI_VTEMU_OSC

static TERMUI_VTEMU_STATE(termui_vtemu_osc_string)
{
  switch (c)
    {
    case 0x00 ... 0x17:
    case 0x19:
    case 0x1c ... 0x1f:
      termui_vtemu_ignore(ctx, c);
      break;

    case 0x20 ... 0x7f:
      termui_vtemu_osc_put(ctx, c);
      break;

# ifdef CONFIG_LIBTERMUI_VTEMU_8BITS
    case 0x9c:
      TERMUI_VTEMU_SET_STATE(termui_vtemu_ground);
      break;
# endif

    default:
      termui_vtemu_anywhere(ctx, c);
      break;
    }

  if (ctx->st != termui_vtemu_osc_string)
    termui_vtemu_osc_end(ctx, c);
}

#endif

