/***********************************************************************************
 * Chaga string arithmetic library                                                 *
 * Copyright (C) 2026 Robert James Bruce                                           *
 *                                                                                 *
 * This file is part of the Chaga string arithmetic library.                       *
 *                                                                                 *
 * The Chaga string arithmetic library 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.                                                      *
 *                                                                                 *
 * The Chaga string arithmetic library 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 the Chaga compiler. If not, see <https://www.gnu.org/licenses/>.     *
 ***********************************************************************************/
#include "include/libchaga-general_functions.h"
#include "include/libchaga-packed_string_integer_functions.h"

digit_stack_node * push_digit_stack (digit_stack_node *digit_stack, unsigned char digit)
{
  digit_stack_node *new_element;
  unsigned char repacked_digits, nibble_lo, fourbit_digit;

  fourbit_digit = 0xf;
  switch (digit)
  {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      fourbit_digit = digit - '0';
    break;
    case '.':
      fourbit_digit = 0xa;
    break;
    case '+':
      fourbit_digit = 0xb;
    break;
    case '-':
      fourbit_digit = 0xc;
    break;
    default:
    break;
  }
  if ((fourbit_digit >= 0) && (fourbit_digit <= 0xc))
  {
    if (digit_stack != NULL) 
    {
      if ((digit_stack->stack_top_position >= 0) && (digit_stack->stack_top_position < MAX_DIGIT_STACK_BLOCK_SIZE))
      {
        if (digit_stack->stack_top_nibble_orientation == lo)
        {
          nibble_lo = digit_stack->digit[digit_stack->stack_top_position] & 0x0f;
          repacked_digits = (fourbit_digit << 4) + nibble_lo;
          digit_stack->digit[digit_stack->stack_top_position] = repacked_digits;
          digit_stack->stack_top_nibble_orientation = hi;
        }
        else
        {
          if (digit_stack->stack_top_nibble_orientation == hi)
          {
            if (digit_stack->stack_top_position + 1 < MAX_DIGIT_STACK_BLOCK_SIZE)
            {
              digit_stack->stack_top_position = digit_stack->stack_top_position + 1;
              digit_stack->digit[digit_stack->stack_top_position] = fourbit_digit;
              digit_stack->stack_top_nibble_orientation = lo;
            }
            else
            {
              new_element = (digit_stack_node *) malloc(sizeof(digit_stack_node));
              if (new_element != NULL)
              { 
                new_element->stack_top_position = 0;
                new_element->digit[0] = fourbit_digit;
                new_element->stack_top_nibble_orientation = lo;
                new_element->next = digit_stack;
                digit_stack = new_element;
              }
            }
          }
        }
      }
      else
      {
        new_element = (digit_stack_node *) malloc(sizeof(digit_stack_node));
        if (new_element != NULL)
        { 
          new_element->stack_top_position = 0;
          new_element->digit[0] = fourbit_digit;
          new_element->stack_top_nibble_orientation = lo;
          new_element->next = digit_stack;
          digit_stack = new_element;
        }
      }
    }
    else
    {
      new_element = (digit_stack_node *) malloc(sizeof(digit_stack_node));
      if (new_element != NULL)
      { 
        new_element->stack_top_position = 0;
        new_element->stack_top_nibble_orientation = lo;
        new_element->digit[0] = fourbit_digit;
        new_element->next = NULL;
        digit_stack = new_element;
      }
    }
  }
  return digit_stack;
}




digit_stack_node * pop_digit_stack (digit_stack_node *digit_stack)
{
  digit_stack_node *element;

  if (digit_stack != NULL)
  {
    if ((digit_stack->stack_top_position > 0) && (digit_stack->stack_top_position < MAX_DIGIT_STACK_BLOCK_SIZE))
    {
      if (digit_stack->stack_top_nibble_orientation == hi)
      {
        digit_stack->digit[digit_stack->stack_top_position] = digit_stack->digit[digit_stack->stack_top_position] & 0x0f; // erase the high nibble (pop).
        digit_stack->stack_top_nibble_orientation = lo;
      }
      else
      {
        if (digit_stack->stack_top_nibble_orientation == lo)
        {
          digit_stack->stack_top_position--;
          digit_stack->stack_top_nibble_orientation = hi;
        }
      }
    }
    else
    {
      if (digit_stack->stack_top_position == 0)
      {
        if (digit_stack->stack_top_nibble_orientation == hi)
        {
          digit_stack->digit[digit_stack->stack_top_position] = digit_stack->digit[digit_stack->stack_top_position] & 0x0f; // erase the high nibble (pop).
          digit_stack->stack_top_nibble_orientation = lo;
        }
        else
        {
          if (digit_stack->stack_top_nibble_orientation == lo)
          {
            element = digit_stack;
            digit_stack = digit_stack->next;
            free (element);
          } 
        }
      }
    }
  }
  return digit_stack;
}




unsigned char top_digit_stack (digit_stack_node *digit_stack)
{
  unsigned char byte, nibble;

  byte = 0; // NULL byte indicates an error occurred.
  if (digit_stack != NULL)
  {
    if ((digit_stack->stack_top_position >= 0) && (digit_stack->stack_top_position < MAX_DIGIT_STACK_BLOCK_SIZE))
    {
      if (digit_stack->stack_top_nibble_orientation == lo)
      {
        nibble = digit_stack->digit[digit_stack->stack_top_position] & 0x0f;
        switch (nibble)
        {
          case 0x0:
          case 0x1:
          case 0x2:
          case 0x3:
          case 0x4:
          case 0x5:
          case 0x6:
          case 0x7:
          case 0x8:
          case 0x9:
            byte = nibble + '0';
          break;
          case 0xa:
            byte = '.';
          break;
          case 0xb:
            byte = '+';
          break;
          case 0xc:
            byte = '-'; 
          break;
          default:
          break;
        }
      }
      else
      {
        if (digit_stack->stack_top_nibble_orientation == hi)
        {
          nibble = (digit_stack->digit[digit_stack->stack_top_position] & 0xf0) >> 4;
          switch (nibble)
          {
            case 0x0:
            case 0x1:
            case 0x2:
            case 0x3:
            case 0x4:
            case 0x5:
            case 0x6:
            case 0x7:
            case 0x8:
            case 0x9:
              byte = nibble + '0';
            break;
            case 0xa:
              byte = '.';
            break;
            case 0xb:
              byte = '+';
            break;
            case 0xc:
              byte = '-'; 
            break;
            default:
            break;
          }     
        }
      }
    }
  }
  return byte;
}




digit_queue_node * add_digit_queue (digit_queue_node *digit_queue, unsigned char digit)
{
  digit_queue_node *new_element, *temp_element;

  new_element = (digit_queue_node *) malloc(sizeof(digit_queue_node));
  if (new_element != NULL)
  {
    new_element->digit = digit;
    new_element->next = NULL;
  }
  if (digit_queue != NULL)
  {
    temp_element = digit_queue;
    while (temp_element->next != NULL)
    {
      temp_element = temp_element->next;
    }
    temp_element->next = new_element;
  }
  else
  {
    digit_queue = new_element;
  }
  return digit_queue;
}




digit_queue_node * free_digit_queue (digit_queue_node *digit_queue)
{
  digit_queue_node *temp_element;

  while (digit_queue != NULL)
  {
    temp_element = digit_queue;
    digit_queue = digit_queue->next;
    free (temp_element);
  }
  return digit_queue;
}




// ***************************************************************************************************
// * packed_string_number_component_copy_number                                                      *
// *                                                                                                 *
// * This function makes a duplicate copy of a packed string number component (psnc) data structure. *
// *                                                                                                 *
// * Input:  The string-based number component to copy.                                              *
// * Output: NULL on failure; pointer to a string-based number component (psnc) on success.          *
// ***************************************************************************************************
psnc * packed_string_number_component_copy_number (psnc *number_copy_head, psnc *number)
{
  psnc *new_number_block, *number_copy_previous, *temp_num;
  int state;
  unsigned char nibble_lo, packed_byte;
  long long int i;

// *************************************************************************
// * Release any previously used memory otherwise we'd have a memory leak. *
// *************************************************************************
  if (number_copy_head != NULL)
  {
    number_copy_head = packed_string_component_release_memory (number_copy_head);
  }
  if (number != NULL)
  {
    state = 0;
    temp_num = number;
    while (temp_num != NULL)
    {
      new_number_block = (psnc *) malloc(sizeof(psnc));
      if (new_number_block != NULL)
      {
        new_number_block->highest_digit_position  = temp_num->highest_digit_position;
        new_number_block->highest_nibble_position = temp_num->highest_nibble_position;
        new_number_block->first_number_block      = NULL;
        new_number_block->last_number_block       = NULL;
        new_number_block->previous_number_block   = NULL;
        new_number_block->next_number_block       = NULL;
        for (i = temp_num->highest_digit_position + 1; i < MAX_PACKED_NUMBER_BLOCK_SIZE; i++)
        {
          new_number_block->packed_number_block[i] = 0;
        }
        for (i = 0; i < temp_num->highest_digit_position; i++)
        {
          new_number_block->packed_number_block[i] = temp_num->packed_number_block[i];
        }
        if (temp_num->highest_nibble_position == hi)
        {
          new_number_block->packed_number_block[i] = temp_num->packed_number_block[i];
        }
        else
        {
          packed_byte = temp_num->packed_number_block[i];
          nibble_lo = packed_byte & 0x0f;
          new_number_block->packed_number_block[i] = nibble_lo;
        }
        switch (state)
        {
          case 0:
            number_copy_head = new_number_block;
            number_copy_head->first_number_block = new_number_block;
            number_copy_previous = NULL;
            state  = 10;
          break;
          case 10:
            if (number_copy_previous != NULL)
            {
              number_copy_previous->next_number_block = new_number_block;
            }
            new_number_block->first_number_block    = number_copy_head;
            new_number_block->previous_number_block = number_copy_previous; 
          break;
          default:
          break;
        }
        number_copy_previous = new_number_block;
      }
      temp_num = temp_num->next_number_block;
    }
    if (new_number_block != NULL)
    {
      temp_num = number_copy_head;
      while (temp_num != NULL)
      {
        temp_num->last_number_block = new_number_block;
        temp_num = temp_num->next_number_block;
      }
    }
  }
  return number_copy_head;
} 





// ********************************************************************************************************
// * packed_string_component_release_memory                                                               *
// *                                                                                                      *
// * This function releases memory formerly allocated for a packed, string-based number component (psnc). *
// *                                                                                                      *
// * Input:  pointer to a string number                                                                   *
// * Output: NULL                                                                                         *
// ********************************************************************************************************
psnc * packed_string_component_release_memory (psnc *number_component)
{
  psnc *temp_number_component;

  while (number_component != NULL)
  {
    temp_number_component = number_component;
    number_component = number_component->next_number_block;
    free (temp_number_component);
    temp_number_component = NULL;
  }
  return number_component;
}



// ***********************************************************
// * count_num_digits_in_packed_string_integer               *
// *                                                         *
// * This function returns the number of digits in a number. *
// *                                                         *
// * Input: a packed, string-based integer.                  *
// * Output: the number of digits in the input number.       *
// ***********************************************************
long long int count_num_digits_in_packed_string_integer (psint *number)
{
  psnc *temp_number_component;
  long long int number_of_digits;

  number_of_digits = 0;
  if (number != NULL)
  {
    if (number->left_of_decimal != NULL)
    {
      temp_number_component = number->left_of_decimal;
      while (temp_number_component != NULL)
      {
        if (temp_number_component->highest_nibble_position == hi)
        {
          number_of_digits = number_of_digits + (temp_number_component->highest_digit_position + 1) * 2; // memory starts at zero therefore add one. Two digits per byte.
        }         
        else
        {
          number_of_digits = number_of_digits + (temp_number_component->highest_digit_position + 1) * 2 - 1;
        }
        temp_number_component = temp_number_component->next_number_block;
      }
    }
  }
  return number_of_digits;
}




// ************************************************************************************************
// * add_psint_element_to_list                                                                    *
// *                                                                                              *
// * This function adds one packed, string-based integer (psint) to a linked list data structure. * 
// *                                                                                              *
// * Inputs: num1 (packed string-based integer) and num2 (packed string-based integer).           *
// * Output: a linked-list containing two packed, string-based integer elements.                  *
// ************************************************************************************************
//BORKER
psint_list_node * add_psint_element_to_list (psint_list_node *psint_list, psint *number)
{
  psint_list_node *new_element;
  psint *new_number;

  if (number != NULL)
  {
    new_element = (psint_list_node *) malloc(sizeof(psint_list_node));
    if (new_element != NULL)
    {
      new_number = NULL;
      new_number = packed_string_integer_copy_number (new_number, number);
      new_element->element = new_number;
      new_element->next_element = psint_list;
      psint_list = new_element;
    }
    else
    {
      printf ("Error in create_psint_list: malloc function returned NULL. Exiting now.\n");
      exit (EXIT_FAILURE);
    }
  }
  return psint_list;
}  





// *********************************************************************************************
// * create_two_element_psint_list                                                             *
// *                                                                                           *
// * This function creates a two-element packed, string-based integer (psint) linked list data * 
// * structure. It is useful interally to create a function return list with two string-based  *
// * integer parameters.                                                                       *
// *                                                                                           *
// * Inputs: num1 (packed string-based integer) and num2 (packed string-based integer).        *
// * Output: a linked-list containing two packed, string-based integer elements.               *
// *********************************************************************************************
psint_list_node * create_two_element_psint_list (psint *num1, psint *num2)
{
  psint_list_node *psint_list, *num1_element, *num2_element;

  psint_list = NULL;
  num1_element = NULL;
  num2_element = NULL;
  if ((num1 != NULL) && (num2 != NULL))
  {
    num2_element = (psint_list_node *) malloc(sizeof(psint_list_node));
    if (num2_element != NULL)
    {
      num2_element->element = num2;
      num2_element->next_element = NULL;
      num1_element = (psint_list_node *) malloc(sizeof(psint_list_node));
      if (num1_element != NULL)
      {
        num1_element->element = num1;
        num1_element->next_element = num2_element;
        psint_list = num1_element;
      }
      else
      {
        printf ("Error in create_two_element_psint_list(): malloc function returned NULL. Exiting now.\n");
        exit (EXIT_FAILURE);
      }
    }
    else
    {
      printf ("Error in create_two_element_psint_list(): malloc function returned NULL. Exiting now.\n");
      exit (EXIT_FAILURE);
    }
  }
  return psint_list;
}  

