/* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

   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; version 2 of the License.

   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 St, Fifth Floor, Boston, MA 02110-1301  USA */


#include "mysys_priv.h"
#include "my_sys.h"

#ifdef _WIN32


/* Windows console handling */

/*
  TODO : Find a relationship between the following
         two macros and get rid of one.
*/

/* Maximum line length on Windows console */
#define MAX_CONSOLE_LINE_SIZE 65535

/*
  Maximum number of characters that can be entered
  on single line in the console (including \r\n).
*/
#define MAX_NUM_OF_CHARS_TO_READ 24530

/**
  Determine if a file is a windows console

  @param file Input stream

  @return
  @retval  0 if file is not Windows console
  @retval  1 if file is Windows console
*/
my_bool
my_win_is_console(FILE *file)
{
  DWORD mode;
  if (GetConsoleMode((HANDLE) _get_osfhandle(_fileno(file)), &mode))
    return 1;
  return 0;
}


/**
  Read line from Windows console using Unicode API
  and translate input to session character set.
  Note, as Windows API breaks supplementary characters
  into two wchar_t pieces, we cannot read and convert individual
  wchar_t values separately. So let's use a buffer for
  Unicode console input, and then convert it to "cs" in a single shot.
  String is terminated with '\0' character.

  @param cs          [IN]  Character string to convert to.
  @param mbbuf       [OUT] Write input data here.
  @param mbbufsize   [IN]  Number of bytes available in mbbuf.
  @param nread       [OUT] Number of bytes read.

  @retval           Pointer to mbbuf, or NULL on I/0 error.
*/
char *
my_win_console_readline(const CHARSET_INFO *cs, char *mbbuf, size_t mbbufsize,
                        size_t *nread)
{
  uint dummy_errors;
  static wchar_t u16buf[MAX_CONSOLE_LINE_SIZE + 1];
  size_t mblen= 0;
  DWORD console_mode;
  DWORD nchars;

  HANDLE console= GetStdHandle(STD_INPUT_HANDLE);

  DBUG_ASSERT(mbbufsize > 0); /* Need space for at least trailing '\0' */
  GetConsoleMode(console, &console_mode);
  SetConsoleMode(console, ENABLE_LINE_INPUT |
                          ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT);

  if (!ReadConsoleW(console, u16buf, MAX_NUM_OF_CHARS_TO_READ, &nchars, NULL))
  {
    SetConsoleMode(console, console_mode);
    return NULL;
  }

  *nread= nchars;

  /* Set length of string */
  if (nchars >= 2 && u16buf[nchars - 2] == L'\r')
    nchars-= 2;
  else if ((nchars == MAX_NUM_OF_CHARS_TO_READ) &&
           (u16buf[nchars - 1] == L'\r'))
    /* Special case 1 - \r\n straddles the boundary */
    nchars--;
  else if ((nchars == 1) && (u16buf[0] == L'\n'))
    /* Special case 2 - read a single '\n'*/
    nchars--;

  SetConsoleMode(console, console_mode);

  /* Convert Unicode to session character set */
  if (nchars != 0)
    mblen= my_convert(mbbuf, mbbufsize - 1, cs,
                      (const char *) u16buf, nchars * sizeof(wchar_t),
                      &my_charset_utf16le_bin, &dummy_errors);

  DBUG_ASSERT(mblen < mbbufsize); /* Safety */
  mbbuf[mblen]= 0;
  return mbbuf;
}


/**
  Translate client charset to Windows wchars for console I/O.
  Unlike copy_and_convert(), in case of a wrong multi-byte sequence
  we don't print '?' character, we fallback to ISO-8859-1 instead.
  This gives a better idea how binary data (e.g. BLOB) look like.

  @param cs           Character set of the input string
  @param from         Input string
  @param from_length  Length of the input string
  @param to[OUT]      Write Unicode data here
  @param to_chars     Number of characters available in "to"
*/
static size_t
my_mbstou16s(const CHARSET_INFO *cs, const uchar * from, size_t from_length,
             wchar_t *to, size_t to_chars)
{
  const CHARSET_INFO *to_cs= &my_charset_utf16le_bin;
  const uchar *from_end= from + from_length;
  wchar_t *to_orig= to, *to_end= to + to_chars;
  my_charset_conv_mb_wc mb_wc= cs->cset->mb_wc;
  my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb;
  while (from < from_end)
  {
    int cnvres;
    my_wc_t wc;
    if ((cnvres= (*mb_wc)(cs, &wc, from, from_end)) > 0)
    {
      if (!wc)
        break;
      from+= cnvres;
    }
    else if (cnvres == MY_CS_ILSEQ)
    {
      wc= (my_wc_t) (uchar) *from; /* Fallback to ISO-8859-1 */
      from+= 1;
    }
    else if (cnvres > MY_CS_TOOSMALL)
    {
      /*
        A correct multibyte sequence detected
        But it doesn't have Unicode mapping. 
      */
      wc= '?';
      from+= (-cnvres); /* Note: cnvres is negative here */
    }
    else /* Incomplete character */
    {
      wc= (my_wc_t) (uchar) *from; /* Fallback to ISO-8859-1 */
      from+= 1;
    }
outp:
    if ((cnvres= (*wc_mb)(to_cs, wc, (uchar *) to, (uchar *) to_end)) > 0)
    {
      /* We can never convert only a part of wchar_t */
      DBUG_ASSERT((cnvres % sizeof(wchar_t)) == 0);
      /* cnvres returns number of bytes, convert to number of wchar_t's */
      to+= cnvres / sizeof(wchar_t);
    }
    else if (cnvres == MY_CS_ILUNI && wc != '?')
    {
      wc= '?';
      goto outp;
    }
    else
      break; /* Not enough space */
  }
  return to - to_orig;
}


/**
  Write a string in the given character set to Windows console. 
  As Window breaks supplementary characters into two parts,
  we cannot use a simple loop sending the result of
  cs->cset->mb_wc() to console.
  So we converts string from client charset to an array of wchar_t,
  then write the array to console in a single shot.

  @param cs       Character set of the string
  @param data     String to print
  @param datalen  Length of input string in bytes
*/
void
my_win_console_write(const CHARSET_INFO *cs, const char *data, size_t datalen)
{
  static wchar_t u16buf[MAX_CONSOLE_LINE_SIZE + 1];
  size_t nchars= my_mbstou16s(cs, (const uchar *) data, datalen,
                              u16buf, sizeof(u16buf));
  DWORD nwritten;
  WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
                u16buf, (DWORD) nchars, &nwritten, NULL);
}


/**
  Write a single-byte character to console.
  Note: one should not send parts of the same multi-byte character
  in separate consequent my_win_console_putc() calls.
  For multi-byte characters use my_win_colsole_write() instead.

  @param cs  Character set of the input character
  @param c   Character (single byte)
*/
void
my_win_console_putc(const CHARSET_INFO *cs, int c)
{
  char ch= (char) c;
  my_win_console_write(cs, &ch, 1);
}


/**
  Write a 0-terminated string to Windows console.

  @param cs    Character set of the string to print
  @param data  String to print
*/
void
my_win_console_fputs(const CHARSET_INFO *cs, const char *data)
{
  my_win_console_write(cs, data, strlen(data));
}


/*
  Handle formatted output on the Windows console.
*/
void
my_win_console_vfprintf(const CHARSET_INFO *cs, const char *fmt, va_list args)
{
  static char buff[MAX_CONSOLE_LINE_SIZE + 1];
  size_t len= vsnprintf(buff, sizeof(buff) - 1, fmt, args);
  my_win_console_write(cs, buff, len);
}


#include <shellapi.h>

/**
  Translate Unicode command line parameters to the given character set
  (Typically to utf8mb4).
  Translated parameters are allocated using my_once_alloc().

  @param      tocs    Character set to convert parameters to.
  @param[OUT] argc    Write number of parameters here
  @param[OUT] argv    Write pointer to allocated parameters here.
*/
int
my_win_translate_command_line_args(const CHARSET_INFO *cs, int *argc, char ***argv)
{
  int i, ac;
  char **av;
  wchar_t *command_line= GetCommandLineW();
  wchar_t **wargs= CommandLineToArgvW(command_line, &ac);
  size_t nbytes= (ac + 1) * sizeof(char *);

  /* Allocate new command line parameter */
  av= (char **) my_once_alloc(nbytes, MYF(MY_ZEROFILL));

  for(i= 0; i < ac; i++)
  {
    uint dummy_errors;
    size_t arg_len= wcslen(wargs[i]);
    size_t len, alloced_len= arg_len * cs->mbmaxlen + 1;
    av[i]= (char *) my_once_alloc(alloced_len, MYF(0));
    len= my_convert(av[i], alloced_len, cs,
                    (const char *) wargs[i], arg_len * sizeof(wchar_t),
                    &my_charset_utf16le_bin, &dummy_errors);
    DBUG_ASSERT(len < alloced_len);
    av[i][len]= '\0';
  }
  *argv= av;
  *argc= ac;
  /* Cleanup on exit */
  LocalFree((HLOCAL) wargs);
  return 0;
}

#endif /* _WIN32 */
