#undef BIG_ENDIAN
#undef LITTLE_ENDIAN

#include "StdAfx.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>

#ifdef __APPLE_CC__
#define UInt32  macUIn32
#include <CoreFoundation/CoreFoundation.h>
#undef UInt32
#endif

#ifdef ENV_HAVE_WCHAR__H
#include <wchar.h>
#endif
#ifdef ENV_HAVE_LOCALE
#include <locale.h>
#endif

#include <windows.h>

#define NEED_NAME_WINDOWS_TO_UNIX
// #include "myPrivate.h"

#include "Common/StringConvert.h"
#include "Common/StdOutStream.h"

#undef NDEBUG
#include <assert.h>

#include "Common/StringConvert.cpp"
#include "Common/StdOutStream.cpp"
#include "Common/IntToString.cpp"
#include "Common/UTFConvert.cpp"

#include "Windows/Synchronization.cpp"
#include "Windows/FileFind.cpp"
#include "Windows/TimeUtils.cpp"
#include "Windows/System.cpp"
#include "../C/Threads.c"
#include "../../C/Ppmd.h"

int g_CodePage = -1;

int global_use_lstat = 0; // FIXME

/* FIXME */

LPSTR WINAPI CharNextA( LPCSTR ptr ) {
  if (!*ptr)
    return (LPSTR)ptr;
  return (LPSTR)(ptr + 1); // p7zip search only for ASCII characters like '/' so no need to worry about current locale
}


int MyStringCompare(const char *s1, const char *s2)
{
  while (true)
  {
    unsigned char c1 = (unsigned char)*s1++;
    unsigned char c2 = (unsigned char)*s2++;
    if (c1 < c2) return -1;
    if (c1 > c2) return 1;
    if (c1 == 0) return 0;
  }
}

int MyStringCompare(const wchar_t *s1, const wchar_t *s2)
{
  while (true)
  {
    wchar_t c1 = *s1++;
    wchar_t c2 = *s2++;
    if (c1 < c2) return -1;
    if (c1 > c2) return 1;
    if (c1 == 0) return 0;
  }
}



/* FIXME */

using namespace NWindows;

#if  defined(ENV_HAVE_WCHAR__H) && defined(ENV_HAVE_MBSTOWCS) && defined(ENV_HAVE_WCSTOMBS)
void test_mbs(void) {
  wchar_t wstr1[256] = {
                         L'e',
                         0xE8, // latin small letter e with grave
                         0xE9, // latin small letter e with acute
                         L'a',
                         0xE0, // latin small letter a with grave
                         0x20AC, // euro sign
                         L'b',
                         0 };
  wchar_t wstr2[256];
  char    astr[256];

  global_use_utf16_conversion = 1;

  size_t len1 = wcslen(wstr1);

  printf("wstr1 - %d - '%ls'\n",(int)len1,wstr1);

  size_t len0 = wcstombs(astr,wstr1,sizeof(astr));
  printf("astr - %d - '%s'\n",(int)len0,astr);

  size_t len2 = mbstowcs(wstr2,astr,sizeof(wstr2)/sizeof(*wstr2));
  printf("wstr - %d - '%ls'\n",(int)len2,wstr2);

  if (wcscmp(wstr1,wstr2) != 0) {
    printf("ERROR during conversions wcs -> mbs -> wcs\n");
    exit(EXIT_FAILURE);
  }

  char *ptr = astr;
  size_t len = 0;
  while (*ptr) {
    ptr = CharNextA(ptr);
    len += 1;
  }
  if ((len != len1) && (len != 12)) { // 12 = when locale is UTF8 instead of ISO8859-15
    printf("ERROR CharNextA : len=%d, len1=%d\n",(int)len,(int)len1);
    exit(EXIT_FAILURE);
  }

  UString ustr(wstr1);
  assert(ustr.Len() == (int)len1);

  AString  ansistr(astr);
  assert(ansistr.Len() == (int)len0);

  ansistr = UnicodeStringToMultiByte(ustr);
  assert(ansistr.Len() == (int)len0);

  assert(strcmp(ansistr,astr) == 0);
  assert(wcscmp(ustr,wstr1) == 0);

  UString ustr2 = MultiByteToUnicodeString(astr);
  assert(ustr2.Len() == (int)len1);
  assert(wcscmp(ustr2,wstr1) == 0);
}
#endif

static void test_astring(int num) {
  AString strResult;

  strResult = "first part : ";
  char number[256];
  sprintf(number,"%d",num);
  strResult += AString(number);

  strResult += " : last part";

  printf("strResult -%s-\n",(const char *)strResult);

}


extern void my_windows_split_path(const AString &p_path, AString &dir , AString &base);

static struct {
  const char *path;
  const char *dir;
  const char *base;
}
tabSplit[]=
  {
    { "",".","." },
    { "/","/","/" },
    { ".",".","." },
    { "//","/","/" },
    { "///","/","/" },
    { "dir",".","dir" },
    { "/dir","/","dir" },
    { "/dir/","/","dir" },
    { "/dir/base","/dir","base" },
    { "/dir//base","/dir","base" },
    { "/dir///base","/dir","base" },
    { "//dir/base","//dir","base" },
    { "///dir/base","///dir","base" },
    { "/dir/base/","/dir","base" },
    { 0,0,0 }
  };

static void test_split_astring() {
  int ind = 0;
  while (tabSplit[ind].path) {
    AString path(tabSplit[ind].path);
    AString dir;
    AString base;

    my_windows_split_path(path,dir,base);

    if ((dir != tabSplit[ind].dir) || (base != tabSplit[ind].base)) {
      printf("ERROR : '%s' '%s' '%s'\n",(const char *)path,(const char *)dir,(const char *)base);
    }
    ind++;
  }
  printf("test_split_astring : done\n");
}

 // Number of 100 nanosecond units from 1/1/1601 to 1/1/1970
#define EPOCH_BIAS  116444736000000000LL
static LARGE_INTEGER UnixTimeToUL(time_t tps_unx)
{
	LARGE_INTEGER ul;
	ul.QuadPart = tps_unx * 10000000LL + EPOCH_BIAS;
	return ul;
}

static LARGE_INTEGER FileTimeToUL(FILETIME fileTime)
{
	LARGE_INTEGER lFileTime;
	lFileTime.QuadPart = fileTime.dwHighDateTime;
	lFileTime.QuadPart = (lFileTime.QuadPart << 32) | fileTime.dwLowDateTime;
	return lFileTime;
}

static void display(const char *txt,SYSTEMTIME systime)
{
	FILETIME fileTime;
	BOOL ret = SystemTimeToFileTime(&systime,&fileTime);
	assert(ret == TRUE);
	LARGE_INTEGER ulFileTime = FileTimeToUL(fileTime);
	
	const char * day="";
	switch (systime.wDayOfWeek)
	{
        	case 0:day = "Sunday";break;
        	case 1:day = "Monday";break;
        	case 2:day = "Tuesday";break;
        	case 3:day = "Wednesday";break;
        	case 4:day = "Thursday";break;
        	case 5:day = "Friday";break;
        	case 6:day = "Saturday";break;
	}
	g_StdOut<< txt << day << " " 
		<< (int)systime.wYear << "/" <<  (int)systime.wMonth << "/" << (int)systime.wDay << " "
		<< (int)systime.wHour << ":" << (int)systime.wMinute << ":" <<  (int)systime.wSecond << ":" 
        	<<     (int)systime.wMilliseconds
		<< " (" << (UInt64)ulFileTime.QuadPart << ")\n";
}

static void test_time()
{
	time_t tps_unx = time(0);

	printf("Test Time (1):\n");
	printf("===========\n");
	SYSTEMTIME systimeGM;
	GetSystemTime(&systimeGM);
	
	LARGE_INTEGER ul = UnixTimeToUL(tps_unx);
	g_StdOut<<"  unix time = " << (UInt64)tps_unx << " (" << (UInt64)ul.QuadPart << ")\n";

	g_StdOut<<"  gmtime    : " << asctime(gmtime(&tps_unx))<<"\n";
	g_StdOut<<"  localtime : " << asctime(localtime(&tps_unx))<<"\n";

	display("  GetSystemTime : ", systimeGM);
}

static void test_time2()
{
        UInt32 dosTime = 0x30d0094C;
        FILETIME utcFileTime;
        FILETIME localFileTime;
        FILETIME localFileTime2;
        UInt32 dosTime2 = 0;

        printf("Test Time (2):\n");
        printf("===========\n");
        NTime::DosTimeToFileTime(dosTime, localFileTime);
        NTime::FileTimeToDosTime(localFileTime, dosTime2);
        assert(dosTime == dosTime2);

        printf("Test Time (3):\n");
	printf("===========\n");
	/* DosTime To utcFileTime */

	if (NTime::DosTimeToFileTime(dosTime, localFileTime)) /* DosDateTimeToFileTime */
	{
		if (!LocalFileTimeToFileTime(&localFileTime, &utcFileTime))
			utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;
	}

	printf("  - 0x%x => 0x%x 0x%x => 0x%x 0x%x\n",(unsigned)dosTime,
		(unsigned)localFileTime.dwHighDateTime,(unsigned)localFileTime.dwLowDateTime,
		(unsigned)utcFileTime.dwHighDateTime,(unsigned)utcFileTime.dwLowDateTime);


	/* utcFileTime to DosTime */

        FileTimeToLocalFileTime(&utcFileTime, &localFileTime2);
        NTime::FileTimeToDosTime(localFileTime2, dosTime2);  /* FileTimeToDosDateTime */

	printf("  - 0x%x <= 0x%x 0x%x <= 0x%x 0x%x\n",(unsigned)dosTime2,
		(unsigned)localFileTime2.dwHighDateTime,(unsigned)localFileTime2.dwLowDateTime,
		(unsigned)utcFileTime.dwHighDateTime,(unsigned)utcFileTime.dwLowDateTime);

	assert(dosTime == dosTime2);
	assert(localFileTime.dwHighDateTime == localFileTime2.dwHighDateTime);
	assert(localFileTime.dwLowDateTime  == localFileTime2.dwLowDateTime);
}

static void test_semaphore()
{
	g_StdOut << "\nTEST SEMAPHORE :\n";

	NWindows::NSynchronization::CSynchro sync;
	NWindows::NSynchronization::CSemaphoreWFMO sema;
	bool bres;
	DWORD waitResult;
	int i;

	sync.Create();
	sema.Create(&sync,2,10);

	g_StdOut << "   - Release(1)\n";
	for(i = 0 ;i < 8;i++)
	{
		// g_StdOut << "     - Release(1) : "<< i << "\n";
		bres = sema.Release(1);
		assert(bres == S_OK);
	}
	// g_StdOut << "     - Release(1) : done\n";
	bres = sema.Release(1);
	assert(bres == S_FALSE);

	g_StdOut << "   - WaitForMultipleObjects(INFINITE)\n";
	HANDLE events[1] = { sema };
	for(i=0;i<10;i++)
	{
		waitResult = ::WaitForMultipleObjects(1, events, FALSE, INFINITE);
		assert(waitResult == WAIT_OBJECT_0);
	}

	g_StdOut << "   Done\n";
}


/****************************************************************************************/


static int threads_count = 0;

static THREAD_FUNC_RET_TYPE thread_fct(void *  /* param */ ) {
	threads_count++;
	return 0;
}

#define MAX_THREADS 100000

int test_thread(void) {
	::CThread thread;

	Thread_Construct(&thread);

	threads_count = 0;
	
	printf("test_thread : %d threads\n",MAX_THREADS);

	for(int i=0;i<MAX_THREADS;i++) {
		Thread_Create(&thread, thread_fct, 0); 

		Thread_Wait(&thread);

		Thread_Close(&thread);
	}

	assert(threads_count == MAX_THREADS);

	return 0;
}


void dumpStr(const char *title,const char *txt)
{
  size_t i,len = strlen(txt);

  printf("%s - %d :",title,(int)len);

  for(i  = 0 ; i<len;i++) {
    printf(" 0x%02x",(unsigned)(txt[i] & 255)); 
  }

  printf("\n");
}


void dumpWStr(const char *title,const wchar_t *txt)
{
  size_t i,len = wcslen(txt);

  printf("%s - %d :",title,(int)len);

  for(i  = 0 ; i<len;i++) {
    printf(" 0x%02x",(unsigned)(txt[i])); 
  }

  printf("\n");
}

#ifdef __APPLE_CC__

void  testMaxOSX_stringConvert()
{
/*
                         0xE8, // latin small letter e with grave
                         0xE9, // latin small letter e with acute
                         L'a',
                         0xE0, // latin small letter a with grave
                         0x20AC, // euro sign
*/
   struct
   {
     char astr [256];
     wchar_t ustr [256];
   }
   tab [] =
   {
      {
      //   'a' , 'e with acute'       , 'e with grave'     ,  'a with grave'    ,  'u with grave'    ,  'b' , '.'  ,  't' , 'x'  , 't'  
         { 0x61,  0x65,  0xcc,  0x81  ,  0x65,  0xcc,  0x80,  0x61,  0xcc,  0x80,  0x75,  0xcc,  0x80,  0x62,  0x2e,  0x74,  0x78, 0x74,  0 },
         { 0x61,  0xe9,                  0xe8,                0xe0,                0xf9,                0x62,  0x2e,  0x74,  0x78, 0x74, 0 }
      },
      {
      //   'a' , 'euro sign'        ,  'b' , '.'  ,  't' , 'x'  , 't'  , '\n' 
         { 0x61,  0xe2,  0x82,  0xac,  0x62,  0x2e,  0x74,  0x78,  0x74,  0x0a, 0 },
         { 0x61,  0x20AC,              0x62,  0x2e,  0x74,  0x78,  0x74,  0x0a, 0 }  
      },
      {
         { 0 },
         { 0 }
      }
   };

   int i;

   printf("testMaxOSX_stringConvert : \n");

   i = 0;
   while (tab[i].astr[0])
   {
     printf("  %s\n",tab[i].astr);

     UString ustr = GetUnicodeString(tab[i].astr);

     // dumpWStr("1",&ustr[0]);

     assert(MyStringCompare(&ustr[0],tab[i].ustr) == 0);
     assert(ustr.Len() == wcslen(tab[i].ustr) );


     AString astr = GetAnsiString(ustr);
     assert(MyStringCompare(&astr[0],tab[i].astr) == 0);
     assert(astr.Len() == strlen(tab[i].astr) );

     i++;
   }
}

void  testMacOSX()
{
//  char texte1[]= { 0xc3 , 0xa9  , 0xc3, 0xa0, 0};

  wchar_t wpath1[4096] = {
                         0xE9, // latin small letter e with acute
                         0xE0,
                         0xc7,
                         0x25cc,
                         0x327,
                         0xe4,
                         0xe2,
                         0xc2,
                         0xc3,
                         0x2e,
                         0x74,
                         0x78,
                         0x74,
/*
                         L'e',
                         0xE8, // latin small letter e with grave
                         0xE9, // latin small letter e with acute
                         L'a',
                         0xE0, // latin small letter a with grave
                         0x20AC, // euro sign
                         L'b',
*/
                         0 };

    char utf8[4096];
    wchar_t wpath2[4096];



 // dumpStr("UTF8 standart",texte1);

    dumpWStr("UCS32 standard",wpath1);

// Translate into FS pathname
 {
    const wchar_t * wcs = wpath1;

    UniChar unipath[4096];

    long n = wcslen(wcs);

    for(long i =   0 ; i<= n ;i++) {
      unipath[i] = wcs[i];
    }

    CFStringRef cfpath = CFStringCreateWithCharacters(NULL,unipath,n);

    CFMutableStringRef cfpath2 = CFStringCreateMutableCopy(NULL,0,cfpath);
    CFRelease(cfpath);
    CFStringNormalize(cfpath2,kCFStringNormalizationFormD);
    
    CFStringGetCString(cfpath2,(char *)utf8,4096,kCFStringEncodingUTF8);

    CFRelease(cfpath2);  
  }

  dumpStr("UTF8 MacOSX",utf8);

// Translate from FS pathname
 {
    const char * path = utf8;

    long n = strlen(path);

    CFStringRef cfpath = CFStringCreateWithCString(NULL,path,kCFStringEncodingUTF8);

    if (cfpath)
    {

       CFMutableStringRef cfpath2 = CFStringCreateMutableCopy(NULL,0,cfpath);
       CFRelease(cfpath);
       CFStringNormalize(cfpath2,kCFStringNormalizationFormC);
    
       n = CFStringGetLength(cfpath2);
       for(long i =   0 ; i<= n ;i++) {
         wpath2[i] = CFStringGetCharacterAtIndex(cfpath2,i);
       }
       wpath2[n] = 0;

       CFRelease(cfpath2);  
    }
    else
    {
       wpath2[0] = 0;
    }
  }

  dumpWStr("UCS32 standard (2)",wpath2);

/*
 {
   CFStringRef cfpath;

    cfpath = CFStringCreateWithCString(kCFAllocatorDefault, texte1, kCFStringEncodingUTF8);

    // TODO str = null ?

    CFMutableStringRef cfpath2 = CFStringCreateMutableCopy(NULL,0,cfpaht);
    CFRealease(cfpath);

    

    
  }
*/


}
#endif // __APPLE_CC__


static const TCHAR *kMainDll = TEXT("7z.dll");

static CSysString ConvertUInt32ToString(UInt32 value)
{
  TCHAR buffer[32];
  ConvertUInt32ToString(value, buffer);
  return buffer;
}


void test_csystring(void)
{
	{
		const CSysString baseFolder = TEXT("bin/");
		const CSysString b2 = baseFolder + kMainDll;

		assert(MyStringCompare(&b2[0],TEXT("bin/7z.dll")) == 0);
	}

	{
		LPCTSTR dirPath=TEXT("/tmp/");
		LPCTSTR prefix=TEXT("foo");
		CSysString resultPath;

		UINT   number = 12345;
		UInt32 count  = 6789;
		
/*
		TCHAR * buf = resultPath.GetBuffer(MAX_PATH);
		::swprintf(buf,MAX_PATH,L"%ls%ls#%d@%d.tmp",dirPath,prefix,(unsigned)number,count);
		buf[MAX_PATH-1]=0;
		resultPath.ReleaseBuffer();
*/
		resultPath  = dirPath;
		resultPath += prefix;
		resultPath += TEXT('#');
		resultPath += ConvertUInt32ToString(number);
		resultPath += TEXT('@');
		resultPath += ConvertUInt32ToString(count);
		resultPath += TEXT(".tmp");

		// printf("##%ls##\n",&resultPath[0]);

		assert(MyStringCompare(&resultPath[0],TEXT("/tmp/foo#12345@6789.tmp")) == 0);
	}
	
}

static void  test_AString()
{
   AString a;

   a = "abc";
   assert(MyStringCompare(&a[0],"abc") == 0);
   assert(a.Len() == 3);

   a = GetAnsiString(L"abc");
   assert(MyStringCompare(&a[0],"abc") == 0);
   assert(a.Len() == 3);
}


const TCHAR kAnyStringWildcard = '*';

static void test_UString2(const UString &phyPrefix)
{
  UString tmp = phyPrefix + wchar_t(kAnyStringWildcard);
  printf("Enum(%ls-%ls-%lc)\n",&tmp[0],&phyPrefix[0],wchar_t(kAnyStringWildcard));
}



static void test_UString()
{
  UString us = L"7za433_tar";

   test_UString2(L"7za433_tar");

   UString u1(us);
   test_UString2(u1);
   u1 = L"";
   test_UString2(u1);
   u1 = us;
   test_UString2(u1);

   UString u2 = us;
   test_UString2(u2);
   u2 = L"";
   test_UString2(u2);
   u2 = u1;
   test_UString2(u2);

   u1 = L"abc";
   assert(MyStringCompare(&u1[0],L"abc") == 0);
   assert(u1.Len() == 3);

   u1 = GetUnicodeString("abc");
   assert(MyStringCompare(&u1[0],L"abc") == 0);
   assert(u1.Len() == 3);
}

/****************************************************************************************/
int main() {

  // return test_thread();


#ifdef ENV_HAVE_LOCALE
  setlocale(LC_ALL,"");
#endif

#if defined(BIG_ENDIAN)
  printf("BIG_ENDIAN : %d\n",(int)BIG_ENDIAN);
#endif
#if defined(LITTLE_ENDIAN)
  printf("LITTLE_ENDIAN : %d\n",(int)LITTLE_ENDIAN);
#endif

  printf("sizeof(Byte)   : %d\n",(int)sizeof(Byte));
  printf("sizeof(UInt16) : %d\n",(int)sizeof(UInt16));
  printf("sizeof(UInt32) : %d\n",(int)sizeof(UInt32));
  printf("sizeof(UINT32) : %d\n",(int)sizeof(UINT32));
  printf("sizeof(UInt64) : %d\n",(int)sizeof(UInt64));
  printf("sizeof(UINT64) : %d\n",(int)sizeof(UINT64));
  printf("sizeof(void *) : %d\n",(int)sizeof(void *));
  printf("sizeof(size_t) : %d\n",(int)sizeof(size_t));
  printf("sizeof(ptrdiff_t) : %d\n",(int)sizeof(ptrdiff_t));
  printf("sizeof(off_t) : %d\n",(int)sizeof(off_t));
  printf("sizeof(wchar_t) : %d\n",(int)sizeof(wchar_t));
#ifdef __APPLE_CC__
  printf("sizeof(UniChar) : %d\n",(int)sizeof(UniChar));
#endif
  printf("sizeof(CPpmd_See) : %d\n",(int)sizeof(CPpmd_See));
  printf("sizeof(CPpmd_State) : %d\n",(int)sizeof(CPpmd_State));

  // size tests
  assert(sizeof(Byte)==1);
  assert(sizeof(UInt16)==2);
  assert(sizeof(UInt32)==4);
  assert(sizeof(UINT32)==4);
  assert(sizeof(UInt64)==8);
  assert(sizeof(UINT64)==8);

  // alignement tests
  assert(sizeof(CPpmd_See)==4);
  assert(sizeof(CPpmd_State)==6);

  union {
	Byte b[2];
	UInt16 s;
  } u;
  u.s = 0x1234;

  if ((u.b[0] == 0x12) && (u.b[1] == 0x34)) {
    printf("CPU : big endian\n");
  } else if ((u.b[0] == 0x34) && (u.b[1] == 0x12)) {
    printf("CPU : little endian\n");
  } else {
    printf("CPU : unknown endianess\n");
  }

#if  defined(ENV_HAVE_WCHAR__H) && defined(ENV_HAVE_MBSTOWCS) && defined(ENV_HAVE_WCSTOMBS)
  test_mbs();
#endif

  test_astring(12345);
  test_split_astring();

  test_csystring();
  test_AString();
  test_UString();

  test_time();

  test_time2();

  test_semaphore();

#ifdef __APPLE_CC__
  testMacOSX();
  testMaxOSX_stringConvert();
#endif


{
	LANGID langID;
	WORD primLang;
	WORD subLang;

	langID = GetUserDefaultLangID();
	printf("langID=0x%x\n",langID);

	primLang = (WORD)(PRIMARYLANGID(langID));
	subLang = (WORD)(SUBLANGID(langID));

	printf("primLang=%d subLang=%d\n",(unsigned)primLang,(unsigned)subLang);  
}

  printf("\n### All Done ###\n\n");

  return 0;
}

