/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#include "itkInternationalizationIOHelpers.h"

#include <cstring>

// Some utility functions for the test
#if defined(ITK_SUPPORTS_WCHAR_T_FILENAME_CSTYLEIO) && \
  (defined(ITK_SUPPORTS_WCHAR_T_FILENAME_IOSTREAMS_CONSTRUCTORS) || defined(ITK_SUPPORTS_FDSTREAM_HPP))
#  define LOCAL_USE_WIN32_WOPEN 1
#else
#  define LOCAL_USE_WIN32_WOPEN 0
#endif

// Check if alpha.txt exists using _wfopen with a wstring on MSVC and mingw
// and fopen with a UTF-8 char * otherwise
bool
checkAlphaExists()
{
#if LOCAL_USE_WIN32_WOPEN
  // mingw has _wfopen
  std::wstring wstr;
  wstr.push_back((wchar_t)(0x03B1));
  wstr += L".txt";
  FILE * tmp = _wfopen(wstr.c_str(), L"r");
#else
  std::string utf8_str;
  utf8_str.append(1, '\xCE');
  utf8_str.append(1, '\xB1');
  utf8_str += ".txt";
  FILE *      tmp = fopen(utf8_str.c_str(), "r");
#endif
  if (tmp != nullptr)
  {
    // closing is required at least for mingw to be able to delete the file afterwards
    fclose(tmp);
    return true;
  }
  return false;
}

// Try to delete alpha.txt using wunlink with a wstring on MSVC
// and unlink with UTF-8 char * otherwise
bool
removeAlpha()
{
#if LOCAL_USE_WIN32_WOPEN
  // mingw has _wunlink
  std::wstring wstr;
  wstr.push_back((wchar_t)(0x03B1));
  wstr += L".txt";
  return (_wunlink(wstr.c_str()) != -1);
#else
  std::string utf8_str;
  utf8_str.append(1, '\xCE');
  utf8_str.append(1, '\xB1');
  utf8_str += ".txt";
  return (unlink(utf8_str.c_str()) != -1);
#endif
}

// Temporarily use its own main to start by sorting out compilation problems
// without too much annoyance to the rest of the world
// main will then become
// int itkUnicodeIOTest( int , char * [] )
int
main(int, char *[])
{
  std::cout << "Starting unicode IO test." << std::endl;

  int nberror = 0;

  // Put alpha.txt encoded in utf8 within a std::string
  std::string utf8_str;
  utf8_str.append(1, '\xCE');
  utf8_str.append(1, '\xB1');
  utf8_str += ".txt";

  // Check if we actually find it is a valid string
  if (!itk::i18n::IsStringEncodingValid(utf8_str))
  {
    std::cout << "Wrongly detected invalid utf8 string." << std::endl;
    ++nberror;
  }

#if LOCAL_USE_WIN32_WOPEN
  // Check that the string to wide string conversion works
  std::wstring utf16_str;
  utf16_str.push_back((wchar_t)(0x03B1));
  utf16_str += L".txt";
  const std::wstring fromutf8_utf16_str = itk::i18n::Utf8StringToWString(utf8_str);

  if (fromutf8_utf16_str != utf16_str)
  {
    std::cout << "Utf-8 to utf-16 conversion failed" << std::endl;
    ++nberror;
  }

  // Create a non utf8 std::string
  std::string bad_utf8_str;
  bad_utf8_str.push_back('\xCE');
  bad_utf8_str.push_back('\xCE');
  bad_utf8_str += ".txt";

  // Check if we actually find it is a non-valid utf-8 string
  if (itk::i18n::IsStringEncodingValid(bad_utf8_str))
  {
    std::cout << "Did not detect invalid utf8 string using windows API." << std::endl;
    ++nberror;
  }
#endif

  // Start by removing alpha.txt if it exists
  removeAlpha();

  // Create alpha.txt using utf8fopen
  FILE * wfile = itk::i18n::I18nFopen(utf8_str, "wb");

  if (!checkAlphaExists())
  {
    std::cout << "alpha.txt does not exist after utf8fopen." << std::endl;
    ++nberror;
  }

  if (wfile != nullptr)
  {
    fputs("test", wfile);
    fclose(wfile);
  }
  else
  {
    std::cout << "wfile is null." << std::endl;
    ++nberror;
  }

  FILE * rfile = itk::i18n::I18nFopen(utf8_str, "rb");

  if (rfile != nullptr)
  {
    char   teststring[10];
    char * retptr = fgets(teststring, 10, rfile);
    if (retptr != nullptr)
    {
      std::cout << "teststring=" << teststring << std::endl;

      if (strcmp(teststring, "test") != 0)
      {
        std::cout << "teststring is not equal to test." << std::endl;
        ++nberror;
      }
    }
    else
    {
      std::cout << "Could not read from file after I18nFopen." << std::endl;
      ++nberror;
    }
    fclose(rfile);
  }
  else
  {
    std::cout << "rfile is null." << std::endl;
    ++nberror;
  }

  if (!removeAlpha())
  {
    std::cout << "Could not remove alpha.txt after I18nFopen." << std::endl;
    ++nberror;
  }


  // Create alpha.txt using open and write to it using streams
  itk::i18n::I18nOfstream wstream(utf8_str.c_str(), std::ios::binary | std::ios::out);
  if (!checkAlphaExists())
  {
    std::cout << "alpha.txt does not exist after I18nOfstream creation." << std::endl;
    ++nberror;
  }

  if (wstream.is_open())
  {
    wstream << "teststream" << std::flush;
  }
  else
  {
    std::cout << "wstream is not open." << std::endl;
    ++nberror;
  }
  wstream.close();


  itk::i18n::I18nIfstream rstream(utf8_str.c_str(), std::ios::binary | std::ios::in);

  if (rstream.is_open())
  {
    std::string teststring;
    std::getline(rstream, teststring);
    std::cout << "teststring=" << teststring << std::endl;

    if (teststring != std::string("teststream"))
    {
      std::cout << "teststring is not equal to teststream." << std::endl;
      ++nberror;
    }

    rstream.close();
  }
  else
  {
    std::cout << "rstream is not open." << std::endl;
    ++nberror;
  }


  if (!removeAlpha())
  {
    std::cout << "Could not remove alpha.txt after I18nOfstream creation." << std::endl;
    ++nberror;
  }


  // Check number of errors
  if (nberror > 0)
  {
    std::cout << "Test failed with " << nberror << " errors." << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "Test passed." << std::endl;
  return EXIT_SUCCESS;
}
