/************************************************************************************
   Copyright (C) 2014,2024 MariaDB Corporation plc
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   
   This 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
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; if not see <http://www.gnu.org/licenses>
   or write to the Free Software Foundation, Inc., 
   51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/


/* MariaDB ODBC driver helper functions for platforms other than windows(so far) */

/* NOTE If you change something in this program, please consider if other platform's version 
        of the function you are changing, needs to be changed accordingly */

#include <ma_odbc.h>
#include <stdarg.h>
#include "ma_conv_charset.h"

extern MARIADB_CHARSET_INFO *DmUnicodeCs;
extern Client_Charset utf8;

char LogFile[256];


#ifdef __cplusplus
extern "C" {
#endif 

/* Mimicking of VS' _snprintf */
int _snprintf(char *buffer, size_t count, const char *format, ...)
{
    va_list list;
    va_start(list, format);
    int result= vsnprintf(buffer, count, format, list);

    va_end(list);

    /* _snprintf returns negative number if buffer is not big enough */
    if (result > count)
    {
      return count - result - 1;
    }
    return result;
}


int strcpy_s(char *dest, size_t buffer_size, const char *src)
{
  size_t src_len;

  if (dest == NULL)
  {
    return EINVAL;
  }

  if (src == NULL)
  {
    *dest= '\0';
    return EINVAL;
  }

  src_len= strlen(src);

  if (buffer_size < src_len + 1)
  {
    *dest= 0;
    return ERANGE;
  }

  memcpy((void*)dest, (void*)src, src_len + 1);

  return 0;
}

#ifdef __cplusplus
}
#endif

int DSNPrompt_Lookup(MADB_Prompt *prompt, const char * SetupLibName)
{
  return MADB_PROMPT_NOT_SUPPORTED;
}


int DSNPrompt_Free  (MADB_Prompt *prompt)
{
  prompt->LibraryHandle= NULL;

  return 0;
}


const char* GetDefaultLogDir()
{
  const char *DefaultLogDir="/tmp";
  char *tmp= getenv("HOME");

  if (tmp)
  {
    DefaultLogDir= tmp;
  }

  _snprintf(LogFile, sizeof(LogFile), "%s/maodbc.log", DefaultLogDir);

  return LogFile;
}


/* CharLen < 0 - treat as NTS */
SQLINTEGER SqlwcsOctetLen(const SQLWCHAR *str, SQLINTEGER *CharLen)
{
  SQLINTEGER result= 0, inChars= *CharLen;

  if (str)
  {
    while (inChars > 0 || (inChars < 0 && *str))
    {
      result+= DmUnicodeCs->mb_charlen(*str);
      --inChars;
      str+= DmUnicodeCs->mb_charlen(*str)/sizeof(SQLWCHAR);
    }
  }

  if (*CharLen < 0)
  {
    *CharLen-= inChars;
  }
  return result;
}


SQLWCHAR *MADB_ConvertToWchar(const char *Ptr, SQLLEN PtrLength, Client_Charset* cc)
{
  SQLWCHAR *WStr= NULL;
  size_t Length= 0;

  if (!Ptr)
    return WStr;

  if (PtrLength == SQL_NTS)
  {
    PtrLength= -1;
    /* To copy terminating null as well */
    Length= 1;
  }

  if (!cc || !cc->CodePage)
    cc= &utf8;

  Length+= MbstrOctetLen(Ptr, &PtrLength, cc->cs_info);

  if ((WStr= (SQLWCHAR *)MADB_CALLOC(sizeof(SQLWCHAR) * (PtrLength + 1))))
  {
    size_t wstr_octet_len= sizeof(SQLWCHAR) * (PtrLength + 1);
    /* TODO: Need error processing. i.e. if MADB_ConvertString returns -1 */
    MADB_ConvertString(Ptr, &Length, cc->cs_info, (char*)WStr, &wstr_octet_len, DmUnicodeCs, NULL);
  }

  return WStr;
}


/* {{{ MADB_ConvertFromWChar */
char *MADB_ConvertFromWChar(const SQLWCHAR *Ptr, SQLINTEGER PtrLength, SQLULEN *Length, Client_Charset *cc,
                            BOOL *Error, bool mustBeNullTerminated)
{
  char *AscStr;
  size_t AscLen= PtrLength, PtrOctetLen;
  BOOL dummyError= 0;
  
  if (Error)
  {
    *Error= 0;
  }
  else
  {
    Error= &dummyError;
  }

  if (cc == NULL || cc->CodePage < 1)
  {
    cc= &utf8;
  }

  if (PtrLength == SQL_NTS)
  {
    /*-1 - to calculate length as of nts */
    SQLINTEGER InCharLen= -1;
    PtrOctetLen= SqlwcsOctetLen(Ptr, &InCharLen);
    /* Allocating +1 character for terminating symbol */
    AscLen= (InCharLen+1)*cc->cs_info->char_maxlen;
  }
  else
  {
    /* PtrLength is in characters. MADB_ConvertString(iconv) needs bytes */
    PtrOctetLen= SqlwcsOctetLen(Ptr, &PtrLength);
    AscLen= (PtrLength + (mustBeNullTerminated ? 1 : 0))*cc->cs_info->char_maxlen;
  }

  if (!(AscStr = (char *)MADB_CALLOC(AscLen)))
    return NULL;

  AscLen= MADB_ConvertString((char*)Ptr, &PtrOctetLen, DmUnicodeCs, AscStr, &AscLen, cc->cs_info, Error);

  if (AscLen != (size_t)-1)
  {
    if (PtrLength == -1 && AscLen > 0)
    {
      --AscLen;
    }
    if (mustBeNullTerminated)
      AscStr[AscLen]= '\0';
  }
  else
  {
    MADB_FREE(AscStr);
    AscLen= 0;
  }
  if (Length)
    *Length= (SQLINTEGER)AscLen;

  return AscStr;
}
/* }}} */

/* {{{ MADB_ConvertAnsi2Unicode
       @AnsiLength[in]    - number of bytes to copy, negative if AnsiString is Null terminated and the terminating blank has to be copied
       @UnicodeLength[in] - size of output buffer in chars, that effectively mean in SQLWCHAR units
       @LengthIndicator[out] - number of available characters not counting terminating null(unless it was included in AnsiLength, and IsNull
                            is FALSE) 
       @IsNull[in]        - whether to copy terminating blank. The value has to be 1 or 0(TRUE/FALSE)
                            If AnsiString is negative, its value is neglected(null is copied)
       @returns 1 in case of error, 0 otherwise */
int MADB_ConvertAnsi2Unicode(Client_Charset *cc, const char *AnsiString, SQLLEN AnsiLength, 
                             SQLWCHAR *UnicodeString, SQLLEN UnicodeLength, 
                             SQLLEN *LengthIndicator, BOOL IsNull, MADB_Error *Error)
{
  SQLINTEGER  RequiredLength;
  SQLWCHAR   *Tmp= UnicodeString;
  int         rc= 0, error;
  size_t      SrcOctetLen, DestOctetLen;

  if (LengthIndicator)
    *LengthIndicator= 0;

  if (Error)
    MADB_CLEAR_ERROR(Error);

  if (!AnsiLength || UnicodeLength < 0)
  {
    if (Error)
      MADB_SetError(Error, MADB_ERR_HY090, NULL, 0);
    return 1;
  }

  if (AnsiLength == SQL_NTS || AnsiLength == -1)
  {
    IsNull= 1;
    AnsiLength= strlen(AnsiString);
  }

  /* calculate required length */
  RequiredLength= MbstrCharLen(AnsiString, AnsiLength, cc->cs_info) + IsNull;

  /* Set LengthIndicator */
  if (LengthIndicator)
    *LengthIndicator= RequiredLength - IsNull;
  /* No buffer length, no need to copy - got length and run */
  if (!UnicodeLength)
    return 0;

  if (RequiredLength > UnicodeLength)
  {
    Tmp= (SQLWCHAR *)malloc(RequiredLength * sizeof(SQLWCHAR));
    if (Tmp == NULL)
    {
      if (Error)
      {
        MADB_SetError(Error, MADB_ERR_HY001, NULL, 0);
      }
      return 1;
    }
  }
  else
  {
    RequiredLength= UnicodeLength;
  }

  SrcOctetLen= AnsiLength + IsNull;
  DestOctetLen= sizeof(SQLWCHAR) * RequiredLength;

  RequiredLength= MADB_ConvertString(AnsiString, &SrcOctetLen, cc->cs_info, 
                                        (char*)Tmp, &DestOctetLen, DmUnicodeCs, &error);

  if (RequiredLength < 1)
  {
    if (Error)
      MADB_SetError(Error, MADB_ERR_HY000, "Ansi to Unicode conversion error occurred", error);
    rc= 1;
    goto end;
  }

  if (LengthIndicator)
    *LengthIndicator= RequiredLength / sizeof(SQLWCHAR);

  /* Truncation */
  if (Tmp != UnicodeString)
  {
    memcpy((void*)UnicodeString, (void*)Tmp, (UnicodeLength-1)*sizeof(SQLWCHAR));
    *(UnicodeString + UnicodeLength - 1)= 0;

    if (Error)
      MADB_SetError(Error, MADB_ERR_01004, NULL, 0);
  }
end:
  if (Tmp != UnicodeString)
    free(Tmp);
  return rc;
}
/* }}} */

/* Returns required length for result string without terminating NULL.
   If cc is NULL, or not initialized(CodePage is 0), then simply SrcLength is returned. 
   If Dest is not NULL, and DestLenth is 0, then error */
SQLLEN MADB_SetString(Client_Charset* cc, void *Dest, SQLULEN DestLength,
                      const char *Src, SQLLEN SrcLength/*bytes*/, MADB_Error *Error)
{
  char  *p= (char *)Dest;
  SQLLEN Length= 0;

  if (SrcLength == SQL_NTS)
  {
    if (Src != nullptr)
    {
      /* Thinking about utf8 - Should be probably len in characters */
      SrcLength= strlen(Src);
    }
    else
    {
      SrcLength= 0;
    }
  }

  /* Not enough space */
  if (!DestLength || !Dest)
  {
    if (Dest)
      MADB_SetError(Error, MADB_ERR_01004, nullptr, 0);
    if (!cc || !cc->CodePage)
      return SrcLength;
    else
    {
      Length= MbstrCharLen(Src, SrcLength, cc->cs_info);

      /* In case of !DestLength || !Dest(application didn't give buffer and probably wants to know required length)
       * we most probably have empty Src, and Length will be equal 0 in this case.
       * Taking source length as character length. MultiByteToWideChar on windows does that for us */
      return Length == 0 && SrcLength > 0 ? SrcLength : Length;
    }
  }

  if (!Src || !SrcLength || !*Src)
  {
    memset(p, 0, cc ? sizeof(SQLWCHAR) : sizeof(SQLCHAR));
    return 0;
  }

  if (!cc)
  {
    strncpy_s(p, DestLength, Src ? Src : "", _TRUNCATE);
    /* strncpy does not write null at the end */
    *((char *)Dest + MIN(SrcLength, DestLength - 1))= '\0';

    if (Error && SrcLength >= DestLength)
      MADB_SetError(Error, MADB_ERR_01004, NULL, 0);
    return SrcLength;
  }
  else
  {
    // If there was the error - we can't do anything else. Otherwise
    if (!MADB_ConvertAnsi2Unicode(cc, Src, SrcLength, (SQLWCHAR *)Dest, DestLength, &Length, 0, Error))
    {
      SQLULEN nullPosition= static_cast<SQLULEN>(Length);
      if (nullPosition >= DestLength)
      {
        if (Error)
        {
          MADB_SetError(Error, MADB_ERR_01004, nullptr, 0);
        }
        nullPosition= DestLength - 1;
      }
      ((SQLWCHAR *)Dest)[nullPosition]= 0;
    }
    return Length;
  }
}
/* }}} */


/* Stub - needed on windows only */
int GetSourceAnsiCs(Client_Charset *cc)
{
  CopyClientCharset(&utf8, cc);
  return cc->CodePage;
}

/* {{{ MADB_DSN_PossibleConnect(MADB_Dsn *) */
BOOL MADB_DSN_PossibleConnect(MADB_Dsn *Dsn)
{
  return Dsn->Socket || (Dsn->ServerName && Dsn->Port > 0 && Dsn->IsTcpIp);
}


/* Stub - atm it looks like we don't need to do anything here */
char* MADB_GetDefaultPluginsDir(char* Buffer, size_t Size)
{
  return NULL;
}
