/** \file wutil.c
	Wide character equivalents of various standard unix
	functions.
*/
#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <wchar.h>
#include <wctype.h>
#include <string.h>
#include <dirent.h>
#include <stdarg.h>
#include <limits.h>
#include <libgen.h>
#include <pthread.h>
#include <string>
#include <map>


#if HAVE_LIBINTL_H
#include <libintl.h>
#endif

#include "fallback.h"
#include "util.h"

#include "common.h"
#include "wutil.h"

typedef std::string cstring;

/**
   Minimum length of the internal covnersion buffers
*/
#define TMP_LEN_MIN 256

#ifndef PATH_MAX
#ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN
#else
/**
   Fallback length of MAXPATHLEN. Just a hopefully sane value...
*/
#define PATH_MAX 4096
#endif
#endif

/**
   For wgettext: Number of string_buffer_t in the ring of buffers
*/
#define BUFF_COUNT 4

/* Lock to protect wgettext */
static pthread_mutex_t wgettext_lock;

/* Maps string keys to (immortal) pointers to string values */
typedef std::map<wcstring, wcstring *> wgettext_map_t;
static std::map<wcstring, wcstring *> wgettext_map;

void wutil_init()
{
}

void wutil_destroy()
{
}

bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir)
{
    struct dirent *d = readdir( dir );
    if ( !d ) return false;
    
    out_name = str2wcstring(d->d_name);
    if (out_is_dir) {
        bool is_dir;
        if (d->d_type == DT_DIR) {
            is_dir = true;
        } else if (d->d_type == DT_LNK) {
            /* We want to treat symlinks to directories as directories. Use stat to resolve it. */
            cstring fullpath = wcs2string(dir_path);
            fullpath.push_back('/');
            fullpath.append(d->d_name);
            struct stat buf;
            if (stat(fullpath.c_str(), &buf) != 0) {
                is_dir = false;
            } else {
                is_dir = !! (S_ISDIR(buf.st_mode));
            }
        } else {
            is_dir = false;
        }
        *out_is_dir = is_dir;
    }
    return true;
}

bool wreaddir(DIR *dir, std::wstring &out_name)
{
    struct dirent *d = readdir( dir );
    if ( !d ) return false;
    
    out_name = str2wcstring(d->d_name);
    return true;
}


wchar_t *wgetcwd( wchar_t *buff, size_t sz )
{
	char *buffc = (char *)malloc( sz*MAX_UTF8_BYTES);
	char *res;
	wchar_t *ret = 0;
		
	if( !buffc )
	{
		errno = ENOMEM;
		return 0;
	}
	
	res = getcwd( buffc, sz*MAX_UTF8_BYTES );
	if( res )
	{
		if( (size_t)-1 != mbstowcs( buff, buffc, sizeof( wchar_t ) * sz ) )
		{
			ret = buff;
		}	
	}
	
	free( buffc );
	
	return ret;
}

int wchdir( const wcstring &dir )
{
    cstring tmp = wcs2string(dir);
	return chdir( tmp.c_str() );
}

FILE *wfopen(const wcstring &path, const char *mode)
{
	cstring tmp = wcs2string(path);
	return fopen(tmp.c_str(), mode);
}

FILE *wfreopen(const wcstring &path, const char *mode, FILE *stream)
{
    cstring tmp = wcs2string(path);
    return freopen(tmp.c_str(), mode, stream);
}

int wopen(const wcstring &pathname, int flags, ...)
{
    cstring tmp = wcs2string(pathname);
	int res=-1;
	va_list argp;	
    if( ! (flags & O_CREAT) )
    {
        res = open(tmp.c_str(), flags);
    }
    else
    {
        va_start( argp, flags );
        res = open(tmp.c_str(), flags, va_arg(argp, int) );
        va_end( argp );
    }
    return res;
}

int wcreat(const wcstring &pathname, mode_t mode)
{
    cstring tmp = wcs2string(pathname);
    return creat(tmp.c_str(), mode);
}

DIR *wopendir(const wcstring &name)
{
    cstring tmp = wcs2string(name);
    return opendir(tmp.c_str());
}

int wstat(const wcstring &file_name, struct stat *buf)
{
    cstring tmp = wcs2string(file_name);
    return stat(tmp.c_str(), buf);
}

int lwstat(const wcstring &file_name, struct stat *buf)
{
   // fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
    cstring tmp = wcs2string(file_name);
    return lstat(tmp.c_str(), buf);
}


int waccess(const wcstring &file_name, int mode)
{
    cstring tmp = wcs2string(file_name);
    return access(tmp.c_str(), mode);
}

int wunlink(const wcstring &file_name)
{
    cstring tmp = wcs2string(file_name);
    return unlink(tmp.c_str());
}

void wperror(const wcstring &s)
{
	int e = errno;
	if( !s.empty() )
	{
		fwprintf( stderr, L"%ls: ", s.c_str() );
	}
	fwprintf( stderr, L"%s\n", strerror( e ) );
}

#ifdef HAVE_REALPATH_NULL

wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path)
{
	cstring tmp = wcs2string(pathname);
	char *narrow_res = realpath( tmp.c_str(), 0 );	
	wchar_t *res;	

	if( !narrow_res )
		return 0;
		
	if( resolved_path )
	{
		wchar_t *tmp2 = str2wcs( narrow_res );
		wcslcpy( resolved_path, tmp2, PATH_MAX );
		free( tmp2 );
		res = resolved_path;		
	}
	else
	{
		res = str2wcs( narrow_res );
	}

    free( narrow_res );

	return res;
}

#else

wchar_t *wrealpath(const wchar_t *pathname, wchar_t *resolved_path)
{
    cstring tmp = wcs2string(pathname);
	char narrow_buff[PATH_MAX];
	char *narrow_res = realpath( tmp.c_str(), narrow_buff );
	wchar_t *res;	

	if( !narrow_res )
		return 0;
		
	if( resolved_path )
	{
		wchar_t *tmp2 = str2wcs( narrow_res );
		wcslcpy( resolved_path, tmp2, PATH_MAX );
		free( tmp2 );
		res = resolved_path;		
	}
	else
	{
		res = str2wcs( narrow_res );
	}
	return res;
}

#endif


wcstring wdirname( const wcstring &path )
{
    char *tmp = wcs2str(path.c_str());
    char *narrow_res = dirname( tmp );
    wcstring result = format_string(L"%s", narrow_res);
    free(tmp);
    return result;
}

wcstring wbasename( const wcstring &path )
{
    char *tmp = wcs2str(path.c_str());
    char *narrow_res = basename( tmp );
    wcstring result = format_string(L"%s", narrow_res);
    free(tmp);
    return result;
}

/* Really init wgettext */
static void wgettext_really_init() {
    pthread_mutex_init(&wgettext_lock, NULL);
	bindtextdomain( PACKAGE_NAME, LOCALEDIR );
	textdomain( PACKAGE_NAME );
}

/**
   For wgettext: Internal init function. Automatically called when a translation is first requested.
*/
static void wgettext_init_if_necessary()
{
    static pthread_once_t once = PTHREAD_ONCE_INIT;
    pthread_once(&once, wgettext_really_init);
}

const wchar_t *wgettext( const wchar_t *in )
{
	if( !in )
		return in;
	
    wgettext_init_if_necessary();
    
	wcstring key = in;
    scoped_lock lock(wgettext_lock);
    
    wcstring *& val = wgettext_map[key];
    if (val == NULL) {
        cstring mbs_in = wcs2string(key);
        char *out = gettext(mbs_in.c_str());
        val = new wcstring(format_string(L"%s", out));
    }
	return val->c_str();
}

wcstring wgettext2(const wcstring &in) {
    wgettext_init_if_necessary();
    std::string mbs_in = wcs2string(in);	
	char *out = gettext( mbs_in.c_str() );
    wcstring result = format_string(L"%s", out);
    return result;
}

const wchar_t *wgetenv( const wcstring &name )
{
    ASSERT_IS_MAIN_THREAD();
    cstring name_narrow = wcs2string(name);
	char *res_narrow = getenv( name_narrow.c_str() );
	static wcstring out;

	if( !res_narrow )
		return 0;
	
    out = format_string(L"%s", res_narrow);
	return out.c_str();
	
}

int wmkdir( const wcstring &name, int mode )
{
	cstring name_narrow = wcs2string(name);
	return mkdir( name_narrow.c_str(), mode );
}

int wrename( const wcstring &old, const wcstring &newv )
{
    cstring old_narrow = wcs2string(old);
	cstring new_narrow =wcs2string(newv);
	return rename( old_narrow.c_str(), new_narrow.c_str() );
}