/****************************************************************************
 * libs/libc/misc/lib_slcddecode.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/streams.h>
#include <nuttx/ascii.h>
#include <nuttx/lcd/slcd_codec.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Indices, counts, helper macros *******************************************/

#define NDX_ESC        0
#define NDX_BRACKET    1
#define NDX_CODE3      2
#define NDX_COUNTH     2
#define NDX_COUNTL     3
#define NDX_CODE5      4

#define NCH_ESC        1
#define NCH_BRACKET    2
#define NCH_CODE3      3
#define NCH_COUNTH     3
#define NCH_COUNTL     4
#define NCH_CODE5      5

#define IS_HEX(a)      ((((a) >= '0') && ((a) <= '9')) || \
                        (((a) >= 'a') && ((a) <= 'f')))
#define CODE_MIN       ('A' + FIRST_SLCDCODE)
#define CODE_MAX       ('A' + LAST_SLCDCODE)
#define IS_CODE(a)     (((a) >= CODE_MIN) && ((a) <= CODE_MAX))
#define CODE_RETURN(a) (enum slcdcode_e)((a) - 'A')

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: slcd_nibble
 *
 * Description:
 *   Convert a ASCII hexadecimal character (using only lower case alphabetics
 *   into a binary nibble
 *
 * Input Parameters:
 *   ascii - The nibble characgter.
 *
 * Returned Value:
 *   The binary value of the nibble.
 *
 ****************************************************************************/

static uint8_t slcd_nibble(uint8_t ascii)
{
  if (ascii >= '0' && ascii <= '9')
    {
      return ascii - '0';
    }
  else
    {
      return ascii - 'a' + 10;
    }
}

/****************************************************************************
 * Name: slcd_reget
 *
 * Description:
 *   We have unused characters from the last, unsuccessful decode attempt.
 *   Return one of these instead of the new character from the stream.
 *
 * Input Parameters:
 *   stream - An instance of lib_instream_s to do the low-level get
 *     operation.
 *   pch - The location character to save the returned value.  This may be
 *     either a normal, character code or a special command from enum
 *     slcd_keycode_e
 *
 * Returned Value:
 *   Always SLCDRET_CHAR
 *
 ****************************************************************************/

static enum slcdret_e slcd_reget(FAR struct slcdstate_s *state,
                                 FAR uint8_t *pch, FAR uint8_t *parg)
{
  /* Return the next character */

  *pch = state->buf[state->ndx];
  *parg = 0;

  /* Bump up the indices and return false (meaning a normal character) */

  state->ndx++;
  state->nch--;
  return SLCDRET_CHAR;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: slcd_decode
 *
 * Description:
 *   Get one byte of data or special command from the application provided
 *   input buffer.
 *
 * Input Parameters:
 *   stream - An instance of lib_instream_s to do the low-level get
 *     operation.
 *   state - A user provided buffer to support parsing.  This structure
 *     should be cleared the first time that slcd_decode is called.
 *   pch - The location to save the returned value.  This may be
 *     either a normal, character code or a special command from enum
 *     slcdcode_e, depending on the return value from slcd_decode()
 *   parg - The location to save the count argument that accompanies some
 *     special actions
 *
 * Returned Value:
 *
 *   false:  Normal character
 *   true:   Special SLCD action code with possible argument
 *
 ****************************************************************************/

enum slcdret_e slcd_decode(FAR struct lib_instream_s *stream,
                           FAR struct slcdstate_s *state, FAR uint8_t *pch,
                           FAR uint8_t *parg)
{
  enum slcdcode_e code;
  uint8_t count;
  int ch;

  DEBUGASSERT(stream && state && pch && parg);

  /* Are their ungotten characters from the last, failed parse? */

  if (state->nch > 0)
    {
      /* Yes, return the next ungotten character */

      return slcd_reget(state, pch, parg);
    }

  state->ndx = 0;

  /* No, ungotten characters.  Get the next character from the buffer. */

  ch = lib_stream_getc(stream);
  if (ch == EOF)
    {
      /* End of file/stream (or perhaps an I/O error) */

      return SLCDRET_EOF;
    }

  /* Save the character (whatever it is) in case we fail parsing later */

  state->buf[NDX_ESC] = (uint8_t)ch;
  state->nch = NCH_ESC;

  /* Check for the beginning of an escape sequence */

  if (ch != ASCII_ESC)
    {
      /* Not the beginning of an escape sequence.  Return the character. */

      return slcd_reget(state, pch, parg);
    }

  /* Get the next character from the buffer */

  ch = lib_stream_getc(stream);
  if (ch == EOF)
    {
      /* End of file/stream.  Return the escape character now.  We will
       * return the EOF indication next time.
       */

      return slcd_reget(state, pch, parg);
    }

  /* Save the character (whatever it is) in case we fail parsing later */

  state->buf[NDX_BRACKET] = ch;
  state->nch = NCH_BRACKET;

  /* Check for ESC-[ */

  if (ch != '[')
    {
      /* Not the beginning of an escape sequence.  Return the ESC now,
       * return the following characters later.
       */

      lcderr("ERROR: Parsing failed: ESC followed by %02x\n", ch);
      return slcd_reget(state, pch, parg);
    }

  /* Get the next character from the buffer */

  ch = lib_stream_getc(stream);
  if (ch == EOF)
    {
      /* End of file/stream.  Return the ESC now; return the following
       * characters later.
       */

      return slcd_reget(state, pch, parg);
    }

  /* If the next character is a hexadecimal value (with lower case
   * alphabetic characters), then we are parsing a 5-byte sequence.
   */

  if (!IS_HEX(ch))
    {
      /* Decode the value following the bracket */

      code  = CODE_RETURN(ch);
      count = 0;

      /* Verify the special CLCD action code */

      if (code < (int)FIRST_SLCDCODE || code > (int)LAST_SLCDCODE)
        {
          lcderr("ERROR: Parsing failed: ESC-L followed by %02x\n", ch);

          /* Not a special command code.. put the character in the reget
           * buffer.
           */

          state->buf[NDX_CODE3] = (uint8_t)ch;
          state->nch = NCH_CODE3;

          /* Return the ESC now and the next two characters later. */

          return slcd_reget(state, pch, parg);
        }
    }
  else
    {
      /* Save the first character of the two byte hexadecimal number */

      state->buf[NDX_COUNTH] = (uint8_t)ch;
      state->nch = NCH_COUNTH;

      /* Get the next character from the buffer */

      ch = lib_stream_getc(stream);
      if (ch == EOF)
        {
          /* End of file/stream.  Return the ESC now; return the following
           * characters later.
           */

          return slcd_reget(state, pch, parg);
        }

      /* We expect the next character to be the second byte of hexadecimal
       * count value.
       */

      if (!IS_HEX(ch))
        {
          /* Not a 5-byte escape sequence.  Return the ESC now; return the
           * following characters later.
           */

          lcderr("ERROR: Parsing failed: ESC-L-%c followed by %02x\n",
                 state->buf[NDX_COUNTH], ch);

          return slcd_reget(state, pch, parg);
        }

      /* Save the second character of the two byte hexadecimal number */

      state->buf[NDX_COUNTL] = (uint8_t)ch;
      state->nch = NCH_COUNTL;

      /* Get the next character from the buffer */

      ch = lib_stream_getc(stream);
      if (ch == EOF)
        {
          /* End of file/stream.  Return the ESC now; return the following
           * characters later.
           */

          return slcd_reget(state, pch, parg);
        }

      /* Put the character in the reget buffer because there is on more way
       * that we can fail.
       */

      state->buf[NDX_CODE5] = (uint8_t)ch;
      state->nch = NCH_CODE5;

      /* Get the code and the count values. All count values must be greater
       * than 0 or something is wrong.
       */

      code  = CODE_RETURN(ch);
      count = slcd_nibble(state->buf[NDX_COUNTH]) << 4 |
              slcd_nibble(state->buf[NDX_COUNTL]);

      /* Verify the special CLCD action code */

      if (code < (int)FIRST_SLCDCODE || code > (int)LAST_SLCDCODE)
        {
          /* Not a special command code. Return the ESC now and the rest
           * of the characters later.
           */

          lcderr("ERROR: Parsing failed: ESC-L-%c-%c followed by %02x\n",
                 state->buf[NDX_COUNTH], state->buf[NDX_COUNTL], ch);

          return slcd_reget(state, pch, parg);
        }
    }

  /* We have successfully parsed the entire escape sequence.  Return the
   * CLCD value in pch, return the count in parg, and an indication that this
   * is a special action.
   */

  *pch = code;
  *parg = count;
  state->nch = 0;
  return SLCDRET_SPEC;
}