/* Software License Agreement
 * 
 *     Copyright(C) 1994-2020 David Lindauer, (LADSoft)
 * 
 *     This file is part of the Orange C Compiler package.
 * 
 *     The Orange C Compiler package 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, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     The Orange C Compiler package 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 Orange C.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *     As a special exception, if other files instantiate templates or
 *     use macros or inline functions from this file, or you compile
 *     this file and link it with other works to produce a work based
 *     on this file, this file does not by itself cause the resulting
 *     work to be covered by the GNU General Public License. However
 *     the source code for this file must still be made available in
 *     accordance with section (3) of the GNU General Public License.
 *     
 *     This exception does not invalidate any other reasons why a work
 *     based on this file might be covered by the GNU General Public
 *     License.
 * 
 *     contact information:
 *         email: TouchStone222@runbox.com <David Lindauer>
 * 
 */

#include <time.h> 
#include <string.h>
#include <locale.h>
#include <wchar.h>
#include "_locale.h"
#include <limits.h>

static int weekbasedoffset(struct tm *t)
{
    struct tm t2 = *t;
    time_t tt;
    int rv = 0;
    t2.tm_mon = 0; // january 1
    t2.tm_mday = 1;
    tt = mktime(&t2);
    if (tt == -1)
        return INT_MAX;
    if (t2.tm_wday > 0 && t2.tm_wday < 5)
        rv++;
    rv += t->tm_yday / 7 + ((t->tm_yday % 7) > ((t->tm_wday + 6) % 7));
    return rv;
}
static int weekbasedyear(struct tm *t)
{
    int n = weekbasedoffset(t);
    if (n == INT_MAX)
        return 0 ;
    if (n < 1)
        return t->tm_year + 1900-1;
    if (n > 52)
    {
        struct tm t2 = *t;
        time_t tt;
        t2.tm_mon = 11; // december 31
        t2.tm_mday = 31;
        tt = mktime(&t2);
        if (tt == -1)
            return 0;
        if (t2.tm_wday < 4)
            return t->tm_year + 1900+1;
    }
    return t->tm_year + 1900;    
}
static int weekbasedyearweek(struct tm *t)
{
    int n = weekbasedoffset(t);
    if (n == INT_MAX)
        return 0 ;
    if (n < 1)
        return 53;
    if (n > 52)
    {
        struct tm t2 = *t;
        time_t tt;
        t2.tm_mon = 11; // december 31
        t2.tm_mday = 31;
        tt = mktime(&t2);
        if (tt == -1)
            return 0;
        if (t2.tm_wday < 4)
            return 1;
    }
    return n;
}
static int weeknum(struct tm *t, int startday)
{
    struct tm t2 = *t;
    int rv;
    time_t tt;
    tt = mktime(&t2);
    if (tt == -1)
        return 0;
    rv = t2.tm_yday / 7;
    if (!startday)
        rv += ((t2.tm_yday % 7) >= t2.tm_wday);
    else
        rv += ((t2.tm_yday % 7) >= ((t2.tm_wday + 6) % 7));
    return rv;
}
static int pstr(wchar_t *str, char *astr, int maxsize, int mode, int *cursize)
{
	int l;
	if (*cursize > maxsize)
		return 1;
	l = strlen(astr);
	str += *cursize;
	while (*cursize < maxsize-1 && l) {
		*str++ = *astr++;
		(*cursize)++;
		l--;
	}
	*str = 0;
	if (l)
		return 1;
	return 0;
}
static int pchar(wchar_t *str,char astr, int maxsize, int mode, int *cursize)
{
	if (*cursize > maxsize)
		return 1;
	str += *cursize;
	*str++ = astr;
	(*cursize)++;
	*str = 0;
	return 0;
}
static int pnum(wchar_t *str, int num, int fwidth, int maxsize, int mode, int *cursize)
{
	char buf[11],*p = &buf[9], *nm;
	int i;
    TIME_DATA *td = __locale_data[LC_TIME];
    nm = td->ths;
    buf[0] = 0;
    if (mode == 'O') {
        for (i=0; i < num; i++) {
            if (*nm == 0)
                break ;
            nm += strlen(nm);
        }
        if (*nm)
        	return(pstr(str,nm,maxsize,mode,cursize));
    }
	for (i=0; i < 10; i++)
    	buf[i] = '0';
    buf[10] = 0;
    while (num) {
    	*p-- = (char)(num %10)+'0';
	    num = num / 10;
    }
	return(pstr(str,buf+10-fwidth,maxsize,mode,cursize));
}
static int basic_format(wchar_t *restrict format, const struct tm * restrict t,
        wchar_t *restrict str, size_t maxsize, int mode, int *restrict cursize)
{
        int temp ;
        TIME_DATA *td = __locale_data[LC_TIME];
			switch(*format) {
				case 'a':
					if (pstr(str,td->shortdays[t->tm_wday],maxsize,mode,cursize))
						return 0;
					break;
				case 'A':
					if (pstr(str,td->longdays[t->tm_wday],maxsize,mode,cursize))
						return 0;
					break;
				case 'b':
                case 'h':
					if (pstr(str,td->shortmonths[t->tm_mon],maxsize,mode,cursize))
						return 0;
					break;
				case 'B':
					if (pstr(str,td->longmonths[t->tm_mon],maxsize,mode,cursize))
						return 0;
					break;
                case 'C':
					if (pnum(str,(t->tm_year+1900)/100,2,maxsize,mode,cursize))
						return 0;
                    break;
				case 'd':
					if (pnum(str,t->tm_mday,2,maxsize,mode,cursize))
						return 0;
					break;
                case 'e':
                    if (t->tm_mday < 10) {
    					if (pstr(str," ",maxsize,mode,cursize))
	    					return 0;
    					if (pnum(str,t->tm_mday,1,maxsize,mode,cursize))
	    					return 0;
                    } else
    					if (pnum(str,t->tm_mday,2,maxsize,mode,cursize))
	    					return 0;
                    break;
                case 'g':
    			    if (pnum(str,weekbasedyear(t)%100,2,maxsize,mode,cursize))
	    					return 0;
                    break ;
                case 'G':
    			    if (pnum(str,weekbasedyear(t),4,maxsize,mode,cursize))
	    					return 0;
                    break ;                    
				case 'H':
					if (pnum(str,t->tm_hour,2,maxsize,mode,cursize))
						return 0;
					break;
				case 'I':
                case 'i':
                    temp = t->tm_hour % 12;
                    if (temp == 0) temp = 12;
					if (pnum(str,temp,2,maxsize,mode,cursize))
						return 0;
					break;
				case 'j':
					if (pnum(str,t->tm_yday,3,maxsize,mode,cursize))
						return 0;
					break;
				case 'm':
					if (pnum(str,t->tm_mon+1,2,maxsize,mode,cursize))
						return 0;
					break;
				case 'M':
					if (pnum(str,t->tm_min,2,maxsize,mode,cursize))
						return 0;
					break;
                case 'n':
					if (pchar(str,'\n',maxsize,mode,cursize))
						return 0;
                    break;
				case 'P':
                case 'p':
					{
					char buf[sizeof(td->am)];
					strcpy(buf, td->am + (t->tm_hour/12) * sizeof(td->am));
					if (*format == 'p')
						strupr(buf);
					else
						strlwr(buf);
					if (pstr(str,buf,maxsize,mode,cursize))
						return 0;
					}
					break;
				case 'S':
					if (pnum(str,t->tm_sec,2,maxsize,mode,cursize))
						return 0;
					break;
                case 't':
					if (pchar(str,'\t',maxsize,mode,cursize))
						return 0;
                    break ;
				case 'U':
					if (pnum(str,weeknum(t,0),2,maxsize,mode,cursize))
						return 0;
					break;
                case 'u':
                    if (t->tm_wday == 0)
                        temp = 6 ;
                    else
                        temp = t->tm_wday-1;
					if (pnum(str,temp,1,maxsize,mode,cursize))
						return 0;
					break;
                case  'V':
					if (pnum(str,weekbasedyearweek(t),2,maxsize,mode,cursize))
						return 0;
					break;
				case 'w':
					if (pnum(str,t->tm_wday,1,maxsize,mode,cursize))
						return 0;
					break;
				case 'W':
					if (pnum(str,weeknum(t,1),2,maxsize,mode,cursize))
						return 0;
					break;
				case 'y':
					if (pnum(str,t->tm_year %100,2,maxsize,mode,cursize))
						return 0;
					break;
				case 'Y':
					if (pnum(str,t->tm_year+1900,4,maxsize,mode,cursize))
						return 0;
					break;
				case 'z':
					if (pstr(str,__getTzName(),maxsize,mode,cursize))
						return 0;
					break;
                case 'Z':
                    temp = *__getTimezone()/60;
                    if (temp > 0)
    					if (pchar(str,'-',maxsize,mode,cursize))
	    					return 0;
					if (pnum(str,temp/60,2,maxsize,mode,cursize))
						return 0;
					if (pnum(str,temp%60,2,maxsize,mode,cursize))
						return 0;
					break;
				default: // also catch %%
					if (pchar(str,*format,maxsize,mode,cursize))
						return 0;
                    break;
					
			}
        return 1;
}
static int advanced_format(wchar_t *restrict format, const struct tm * restrict t,
        wchar_t *restrict str, size_t maxsize, int *restrict cursize)
{
	int i,size,xsize;
    int mode,index ;
    TIME_DATA *td = __locale_data[LC_TIME];
	for (i=0; i < wcslen(format); i++) {
		if (format[i] == '%') {
            if (!format[++i])
                return cursize;
            mode = 0;
            if (format[i] == 'O' || format[i] == 'E') {
                mode = format[i];
                if (!format[++i])
                    return cursize;
            }
            switch(format[i]) {
				case 'c':
                    index = mode == 'E';
                    if (!strcmp(td->qualname,"C")) {
                        if (!advanced_format(L"%A %B %d %T %Y",t,str,maxsize,cursize))
                            return 0;                    
                    } else {
                        int l, i, c;
                        wchar_t buf[32], wc;
                        mbstate_t st;
                        memset(&st,0,sizeof(st));
                        for (l = 1, i = 0; l > 0; i++) {
                            c = td->dates[index][i];
                            l = mbrtowc(&wc, &c, 1, &st);
                            if (l >= 0)
                                buf[i] = wc;
                        }
                        if (!advanced_format(buf,t,str,maxsize,cursize))
                            return 0;                    
    					if (pchar(str,' ',maxsize,mode,cursize))
	    					return 0;
                        memset(&st,0,sizeof(st));
                        for (l = 1, i = 0; l > 0; i++) {
                            c = td->times[index][i];
                            l = mbrtowc(&wc, &c, 1, &st);
                            if (l >= 0)
                                buf[i] = wc;
                        }
                        if (!advanced_format(buf,t,str,maxsize,cursize))
                            return 0;                    
                    }
                    break ;
                case 'D':
                    if (!advanced_format(L"%m/%d/%y",t,str,maxsize,cursize))
                        return 0;                    
                    break ;
                case 'F':
                    if (!advanced_format(L"%Y-%m-%d",t,str,maxsize,cursize))
                        return 0;                    
                    break ;                    
                case 'r':
                    {
                    int l, i, c;
                    wchar_t buf[32], wc;
                    mbstate_t st;
                    memset(&st,0,sizeof(st));
                    for (l = 1, i = 0; l > 0; i++) {
                        c = td->times[1][i];
                        l = mbrtowc(&wc, &c, 1, &st);
                        if (l >= 0)
                            buf[i] = wc;
                    }
                    if (!advanced_format(buf,t,str,maxsize,cursize))
                        return 0;                    
                    }
                    break ;                    
                case 'R':
                    if (!advanced_format(L"%H:%M",t,str,maxsize,cursize))
                        return 0;                    
                    break ;                    
                case 'T':
                    if (!advanced_format(L"%H:%M:%S",t,str,maxsize,cursize))
                        return 0;                    
                    break ;                    
				case 'x':
					{
                        int l, i, c;
                        wchar_t buf[32], wc;
                        mbstate_t st;
                    index = mode == 'E';
                        memset(&st,0,sizeof(st));
                        for (l = 1, i = 0; l > 0; i++) {
                            c = td->dates[index][i];
                            l = mbrtowc(&wc, &c, 1, &st);
                            if (l >= 0)
                                buf[i] = wc;
                        }
                    if (!advanced_format(buf,t,str,maxsize,cursize))
                        return 0;
					}
					break;
				case 'X':
					{
                        int l, i, c;
                        wchar_t buf[32], wc;
                        mbstate_t st;
                    index = mode == 'E';
                        memset(&st,0,sizeof(st));
                        for (l = 1, i = 0; l > 0; i++) {
                            c = td->times[index][i];
                            l = mbrtowc(&wc, &c, 1, &st);
                            if (l >= 0)
                                buf[i] = wc;
                        }
                    if (!advanced_format(buf,t,str,maxsize,cursize))
                        return 0;
					}
					break;
                default:
                    if (!basic_format(format + i,t,str,maxsize,mode,cursize))
                        return 0;
                    break ;					
			}
		}
		else {
			if (pchar(str,format[i],maxsize,mode,cursize))
				return 0;
		}
	}
	return 1;
}
size_t _RTL_FUNC wcsftime(wchar_t *restrict str, size_t maxsize, 
                const wchar_t *restrict format, const struct tm * restrict t)
{
	int rv = 0;
    str[0] = 0;
    if (!advanced_format(format,t,str,maxsize,&rv))
        return 0;                    
	return rv;
}