/*
 * This file is part of the TINICONV Library.
 *
 * The TINICONV Library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Library General Public
 * License version 2 as published by the Free Software Foundation.
 */
// ----------------------------------------------------------------------------
// Copyright (C) 2014
//              David Freese, W1HKJ
//
// This file is part of fldigi
//
// fldigi 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/>.
// ----------------------------------------------------------------------------

#include "tiniconv.h"
#include "tiniconv_int.h"

#include <string.h>
#include <assert.h>

int tiniconv_init(int in_charset_id, int out_charset_id, int options, struct tiniconv_ctx_s *ctx)
{
  assert(ctx != NULL);
  
  if (in_charset_id < 0 || in_charset_id >= TINICONV_CHARSETSIZE)
    return TINICONV_INIT_IN_CHARSET_NA;
  if (out_charset_id < 0 || out_charset_id >= TINICONV_CHARSETSIZE)
    return TINICONV_INIT_OUT_CHARSET_NA;
  
  memset(ctx, 0, sizeof(*ctx));
  ctx->mb2wc = tiniconv_charset_map[in_charset_id].mb2wc;
  ctx->flushwc = tiniconv_charset_map[in_charset_id].flushwc;
  ctx->wc2mb = tiniconv_charset_map[out_charset_id].wc2mb;
  ctx->reset = tiniconv_charset_map[out_charset_id].reset;
  ctx->options = options;
  if (!TINICONV_OPTION_GET_OUT_ILSEQ_CHAR(options))
    ctx->options = ctx->options | TINICONV_OPTION_OUT_ILSEQ_CHAR('?');
  
  return TINICONV_INIT_OK;
}

int tiniconv_convert(struct tiniconv_ctx_s *ctx,
  unsigned char const *in_buf, int in_size, int *p_in_size_consumed,
  unsigned char *out_buf, int out_size, int *p_out_size_consumed)
{
  ucs4_t wc;
  int in_idx, out_idx;
  int result, last_result;
  conv_state_t last_istate;
  
  assert(ctx != NULL);
  assert(in_buf != NULL);
  assert(out_buf != NULL);
  
  for (in_idx = 0, out_idx = 0; in_idx < in_size && out_idx < out_size;)
  {
  	last_istate = ctx->istate;
    /* typedef int (*xxx_mb2wc_t) (conv_t conv, ucs4_t *pwc, unsigned char const *s, int n); */
    result = ctx->mb2wc(ctx, &wc, in_buf + in_idx, in_size - in_idx);
    assert(result <= in_size - in_idx);
    if (result < 0)
    {
      if (result == RET_ILSEQ)
      {
        if (ctx->options & TINICONV_OPTION_IGNORE_IN_ILSEQ)
        {
          ctx->istate = 0;
          in_idx ++;
          continue;
        }
        else
        {
          result = TINICONV_CONVERT_IN_ILSEQ;
          goto exit;
        }
      }
      else if (result == RET_TOOSMALL)
      {
        result = TINICONV_CONVERT_IN_TOO_SMALL;
        goto exit;
      }
      else
      {
      	in_idx += RET_TOOFEW(result);
      	continue;
      }
    }
    in_idx += last_result = result;
    
    /* typedef int (*xxx_wc2mb_t) (conv_t conv, unsigned char *r, ucs4_t wc, int n); */
    result = ctx->wc2mb(ctx, out_buf + out_idx, wc, out_size - out_idx);
    assert(result <= out_size - out_idx);
    if (result < 0)
    {
      if (result == RET_ILUNI)
      {
        if (ctx->options & TINICONV_OPTION_IGNORE_OUT_ILSEQ)
        {
          out_buf[out_idx ++] = TINICONV_OPTION_GET_OUT_ILSEQ_CHAR(ctx->options);
          ctx->ostate = 0;
          continue;
        }
        else
        {
          result = TINICONV_CONVERT_OUT_ILSEQ;
          in_idx -= last_result; /* discarding the last read sequence */
          ctx->istate = last_istate;
          goto exit;
        }
      }
      else if (result == RET_TOOSMALL)
      {
        result = TINICONV_CONVERT_OUT_TOO_SMALL;
        in_idx -= last_result; /* discarding the last read sequence */
        ctx->istate = last_istate;
        goto exit;
      }
    }
    out_idx += result;
  }
  result = TINICONV_CONVERT_OK;

exit:
  if (p_in_size_consumed)
    *p_in_size_consumed = in_idx;
  if (p_out_size_consumed)
    *p_out_size_consumed = out_idx;
  return result;
}
