//#####################################################################
// Copyright 2013.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
#include <Core/Log/LOG_PRINTF.h>
namespace PhysBAM{
namespace LOG{

namespace LOG_PRINTF
{
using std::max;
using std::min;
namespace{
// 1 = need width, 2 = need precision, 4 = need precision for int, 8 = type n
void fprintf_parse_flags(OUT_STATE& state)
{
    const char *format=state.format;
    int len=strspn(format+1,"0123456789#-+ 'I*.hlLqjzt")+2;
    state.opts=0;
    char *end=0;
    const char *last=format+len;

    bool is_int=false;
    switch(last[-1]){
        case 'd':
        case 'i':
        case 'u':
            is_int=true;
            break;
        case 's':
        case 'c':
        case 'p':
        case 'P':
            break;
        case 'F':
            state.out<<std::uppercase;
        case 'f':
            state.out.setf(std::ios_base::fixed);
            break;
        case 'E':
            state.out<<std::uppercase;
        case 'e':
            state.out.setf(std::ios_base::scientific);
            break;
        case 'G':
            state.out<<std::uppercase;
        case 'g':
            state.out<<std::defaultfloat;
            break;
        case 'X':
            state.out<<std::uppercase;
        case 'x':
            state.out<<std::hex;
            is_int=true;
            break;
        case 'o':
            state.out<<std::oct;
            is_int=true;
            break;
        case 'A':
            state.out<<std::uppercase;
        case 'a':
            state.out<<std::hexfloat;
            break;
        case 'n': state.opts|=8; break;
            break;
        default:
            throw std::runtime_error(std::string("invalid format string: ")+format);}

    format++; // skip %

    // Parse "flags"
    bool done=false; 
    while(!done)
        switch(*format++){
            case '+': state.out.setf(std::ios_base::showpos); break;
            case '-': state.out.setf(std::ios_base::left); break;
            case ' ': break; // TODO what do we do here?
            case '0': state.out<<std::setfill('0'); break;
            case '#':
                state.out<<std::showpoint;
                state.out<<std::showbase;
            // switch(last[-1]){
            //     case 'g':
            //     case 'G':
            //         state.out<<std::showpoint;
            //         break; // TODO what do we do here?   (For g and G types, trailing zeros are not removed.)
            //     case 'f':
            //     case 'F':
            //     case 'e':
            //     case 'E':
            //         state.out<<std::showpoint;
            //         break;
            //     case 'o':
            //     case 'x':
            //     case 'X':
            //         state.out<<std::showbase;
            //         break;
            // }
            break;
            default: done=true;break;}
    format--;

    // Parse width
    if(isdigit(*format)){
        state.out<<std::setw(strtol(format,&end,10));
        state.need_restore=true;
        format=end;}
    else if(*format=='*'){
        state.opts|=1;
        format++;}

    // Parse precision
    if(*format=='.'){
        if(format[1]=='*'){
            state.opts|=2<<is_int;
            format+=2;}
        else if(format[1]=='-' || isdigit(format[1])){
            int p=strtol(format+1,&end,10);
            if(p>=0){
                if(is_int){
                    state.opts|=32;
                    state.out<<std::setw(max((int)state.out.width(),p));
                    state.out<<std::setfill('0');}
                else state.out<<std::setprecision(p);}
            state.need_restore=true;
            format=end;}
        else{
            state.out<<std::setprecision(0);
            state.need_restore=true;
            format++;}}

    // Parse length
    switch(*format){
        case 'h':
        case 'l':
            format++;
        case 'L':
        case 'z':
        case 'j':
        case 't':
            format++;}

    if(format!=last-1)
        throw std::runtime_error(std::string("invalid format string: ")+format);

    state.format+=len;
}
}

void find_format(OUT_STATE& state)
{
    if(state.opts&7)
    {
        state.opts&=state.opts-1;
        return;
    }
    const char* str = state.format;
    while(1)
    {
        const char* s=strchr(str,'%');
        PHYSBAM_ASSERT(s);
        if(s[1]!='%')
        {
            state.out.write(str,s-str);
            state.format=s;
            fprintf_parse_flags(state);
            return;
        }
        state.out.write(str,s-str+1);
        str=s+2;
    }
}

void print_rest(OUT_STATE& state)
{
    PHYSBAM_ASSERT(!(state.opts&7));
    const char* str = state.format;
    while(const char* s=strchr(str,'%'))
    {
        PHYSBAM_ASSERT(s[1]=='%');
        state.out.write(str,s-str+1);
        str=s+2;
    }
    int len=strlen(str);
    state.out.write(str,len);
}

void print_width_precision(OUT_STATE& state,int data)
{
    state.need_restore=true;
    if(state.opts&1)
    {
        if(state.opts&32) state.out<<std::setw(max(data,(int)state.out.width()));
        else state.out<<std::setw(data);
    }
    else if(state.opts&2)
    {
        state.out<<std::setprecision(data);
    }
    else if(state.opts&4)
    {
        state.out<<std::setw(max((int)state.out.width(),data));
        state.out<<std::setfill('0');
    }
}

template<class T> 
void print_argument_helper(OUT_STATE& state,const T& data)
{
    if(state.opts&7) return print_width_precision(state,data);
    PHYSBAM_ASSERT(!(state.opts&8));
    state.out<<data;
    state.Restore();
}

void print_argument(OUT_STATE& state,int data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,unsigned int data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,short data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,unsigned short data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,char data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,unsigned char data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,long data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,unsigned long data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,long long data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,unsigned long long data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,bool data)
{
    print_argument_helper(state,data);
}

void print_argument(OUT_STATE& state,const char* data)
{
    PHYSBAM_ASSERT(!(state.opts&15));
    state.out<<data;
    state.Restore();
}

void print_argument(OUT_STATE& state,int* data)
{
    PHYSBAM_ASSERT(!(state.opts&7));
    if(state.opts&8)
    {
        *data=state.Number_Output();
        return;
    }
    state.out<<data;
    state.Restore();
}

OUT_STATE::OUT_STATE(std::ostream& out,const char *format)
    :out(out),format(format)
{
    width=out.width();
    precision=out.precision();
    opts=0;
    fill=out.fill();
    flags=out.flags();
    pos=out.tellp();
    need_restore=false;
}

void OUT_STATE::Restore()
{
    if(!need_restore) return;
    need_restore=false;
    out.width(width);
    out.precision(precision);
    out.fill(fill);
    out.flags(flags);
}

int OUT_STATE::Number_Output()
{
    return out.tellp()-pos;
}
}

int fputc(int c,std::ostream& out)
{
    if(out<<c) return (unsigned char)c;
    return EOF;
}

int fputs(const char *s,std::ostream& out)
{
    if(out<<s) return 0;
    return EOF;
}

int puts(const char *s)
{
    if(LOG::cout<<s<<std::endl) return 0;
    return EOF;
}
}
}
