/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   Mupen64plus - util.c                                                  *
 *   Mupen64Plus homepage: http://code.google.com/p/mupen64plus/           *
 *   Copyright (C) 2012 CasualJames                                        *
 *   Copyright (C) 2002 Hacktarux                                          *
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 * Provides common utilities to the rest of the code:
 *  -String functions
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <limits.h>

#include "rom.h"
#include "util.h"
#include "osal/preproc.h"

/**********************
   Byte swap utilities
 **********************/
void swap_buffer(void *buffer, size_t length, size_t count)
{
   size_t i;
   if (length == 2)
   {
      uint16_t *pun = (uint16_t*)buffer;
      for (i = 0; i < count; i++)
         pun[i] = m64p_swap16(pun[i]);
   }
   else if (length == 4)
   {
      uint32_t *pun = (uint32_t*)buffer;
      for (i = 0; i < count; i++)
         pun[i] = m64p_swap32(pun[i]);
   }
   else if (length == 8)
   {
      uint64_t *pun = (uint64_t*)buffer;
      for (i = 0; i < count; i++)
         pun[i] = m64p_swap64(pun[i]);
   }
}

void to_little_endian_buffer(void *buffer, size_t length, size_t count)
{
#ifdef MSB_FIRST
   swap_buffer(buffer, length, count);
#endif
}

void to_big_endian_buffer(void *buffer, size_t length, size_t count)
{
#ifndef MSB_FIRST
   swap_buffer(buffer, length, count);
#endif
}

/**********************
     GUI utilities
 **********************/
void countrycodestring(char countrycode, char *string)
{
   switch (countrycode)
   {
      case '\0':    /* Demo */
         strcpy(string, "Demo");
         break;

      case '7':  /* Beta */
         strcpy(string, "Beta");
         break;

      case 'A': /* Japan / USA */
         strcpy(string, "USA/Japan");
         break;

      case 'D': /* Germany */
         strcpy(string, "Germany");
         break;

      case 'E': /* USA */
         strcpy(string, "USA");
         break;

      case 'F': /* France */
         strcpy(string, "France");
         break;

      case 'I':  /* Italy */
         strcpy(string, "Italy");
         break;

      case 'J': /* Japan */
         strcpy(string, "Japan");
         break;

      case 'S':  /* Spain */
         strcpy(string, "Spain");
         break;

      case 'U': case 'Y':  /* Australia */
         sprintf(string, "Australia (%c)", countrycode);
         break;

      case 0x50: case 0x58: case 0x20:
      case 0x21: case 0x38: case 0x70:
         sprintf(string, "Europe (%c)", countrycode);
         break;

      default:
         sprintf(string, "Unknown (0x%02X)", countrycode);
         break;
   }
}

void imagestring(unsigned char imagetype, char *string)
{
   switch (imagetype)
   {
      case Z64IMAGE:
         strcpy(string, ".z64 (native)");
         break;
      case V64IMAGE:
         strcpy(string, ".v64 (byteswapped)");
         break;
      case N64IMAGE:
         strcpy(string, ".n64 (wordswapped)");
         break;
      default:
         string[0] = '\0';
   }
}

/**********************
     Path utilities
 **********************/

/* Looks for an instance of ANY of the characters in 'needles' in 'haystack',
 * starting from the end of 'haystack'. Returns a pointer to the last position
 * of some character on 'needles' on 'haystack'. If not found, returns NULL.
 */
static const char* strpbrk_reverse(const char* needles, const char* haystack)
{
   size_t stringlength = strlen(haystack), counter;

   for (counter = stringlength; counter > 0; --counter)
   {
      if (strchr(needles, haystack[counter-1]))
         break;
   }

   if (counter == 0)
      return NULL;

   return haystack + counter - 1;
}

const char* namefrompath(const char* path)
{
   const char* last_separator_ptr = strpbrk_reverse(OSAL_DIR_SEPARATORS, path);

   if (last_separator_ptr != NULL)
      return last_separator_ptr + 1;
   return path;
}

static int is_path_separator(char c)
{
   return strchr(OSAL_DIR_SEPARATORS, c) != NULL;
}

char* combinepath(const char* first, const char *second)
{
   size_t len_first = strlen(first), off_second = 0;

   if (first == NULL || second == NULL)
      return NULL;

   while (is_path_separator(first[len_first-1]))
      len_first--;

   while (is_path_separator(second[off_second]))
      off_second++;

   return formatstr("%.*s%c%s", (int) len_first, first, OSAL_DIR_SEPARATORS[0], second + off_second);
}

/**********************
    String utilities
 **********************/
char *trim(char *str)
{
   char *start = str, *end = str + strlen(str);

   while (start < end && isspace((unsigned char)(*start)))
      start++;

   while (end > start && isspace((unsigned char)(*(end-1))))
      end--;

   memmove(str, start, end - start);
   str[end - start] = '\0';

   return str;
}

int string_to_int(const char *str, int *result)
{
   char *endptr;
   long int n;
   if (*str == '\0' || isspace((unsigned char)(*str)))
      return 0;
   errno = 0;
   n = strtol(str, &endptr, 10);
   if (*endptr != '\0' || errno != 0 || n < INT_MIN || n > INT_MAX)
      return 0;
   *result = (int)n;
   return 1;
}

static unsigned char char2hex(char c)
{
   c = tolower(c);
   if(c >= '0' && c <= '9')
      return c - '0';
   else if(c >= 'a' && c <= 'f')
      return c - 'a' + 10;
   return 0xFF;
}

int parse_hex(const char *str, unsigned char *output, size_t output_size)
{
   size_t i, j;
   for (i = 0; i < output_size; i++)
   {
      output[i] = 0;
      for (j = 0; j < 2; j++)
      {
         unsigned char h = char2hex(*str++);
         if (h == 0xFF)
            return 0;

         output[i] = (output[i] << 4) | h;
      }
   }

   if (*str != '\0')
      return 0;

   return 1;
}

char *formatstr(const char *fmt, ...)
{
   va_list args;
   int  size = 128;
   char *str = (char *)malloc(size), *newstr;

   /* There are two implementations of vsnprintf we have to deal with:
    * C99 version: Returns the number of characters which would have been written
    *              if the buffer had been large enough, and -1 on failure.
    * Windows version: Returns the number of characters actually written,
    *                  and -1 on failure or truncation.
    * NOTE: An implementation equivalent to the Windows one appears in glibc <2.1.
    */
   while (str != NULL)
   {
      int ret;

      va_start(args, fmt);
      ret = vsnprintf(str, size, fmt, args);
      va_end(args);

      // Successful result?
      if (ret >= 0 && ret < size)
         return str;

      // Increment the capacity of the buffer
      if (ret >= size)
         size = ret + 1; // C99 version: We got the needed buffer size
      else
         size *= 2; // Windows version: Keep guessing

      newstr = (char *)realloc(str, size);
      if (newstr == NULL)
         free(str);
      str = newstr;
   }

   return NULL;
}

ini_line ini_parse_line(char **lineptr)
{
   char *line = *lineptr, *endline = strchr(*lineptr, '\n'), *equal;
   ini_line l;

   // Null terminate the current line and point to the next line
   if (endline != NULL)
      *endline = '\0';
   *lineptr = line + strlen(line) + 1;

   // Parse the line contents
   trim(line);

   if (line[0] == '#' || line[0] == ';')
   {
      line++;

      l.type = INI_COMMENT;
      l.name = NULL;
      l.value = trim(line);
   }
   else if (line[0] == '[' && line[strlen(line)-1] == ']')
   {
      line[strlen(line)-1] = '\0';
      line++;

      l.type = INI_SECTION;
      l.name = trim(line);
      l.value = NULL;
   }
   else if ((equal = strchr(line, '=')) != NULL)
   {
      char *name = line, *value = equal + 1;
      *equal = '\0';

      l.type = INI_PROPERTY;
      l.name = trim(name);
      l.value = trim(value);
   }
   else
   {
      l.type = (*line == '\0') ? INI_BLANK : INI_TRASH;
      l.name = NULL;
      l.value = NULL;
   }

   return l;
}
