#include <amsstring4/amsstring4.hpp>

namespace ams
{


    //UC codepoints
    //0x00 to 0x10FFFF (~24 bits, with the remainder being escape sequences and the like)

    //different processors order bytes differently (endianness)

    //UTF-8
    // 0x00 - 0x7F: 1 byte
    // 0x00: U+0000 - only when representing the null character

    //21 bit values
    // 0b0xxxxxxx                               0x00000000  0x0000007F
    // 0b110xxxxx 01xxxxxx                      0x00000080  0x000007FF
    // 0b1110xxxx 01xxxxxx 01xxxxxx             0x00000800  0x0000FFFF
    // 0b11110xxx 01xxxxxx 01xxxxxx 01xxxxxx    0x00010000  0x0010FFFF


    static void _intl_print_ui32bits(uint32_t q)
    {
        int I;
    
        for(I=32-1;I>=24;I--)
        {
            if((q & 1<<I)!=0)
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        printf(" ");
        for(I=24-1;I>=16;I--)
        {
            if((q & 1<<I)!=0)
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        printf(" ");
        for(I=16-1;I>=8;I--)
        {
            if((q & 1<<I)!=0)
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        printf(" ");
        for(I=8-1;I>=0;I--)
        {
            if((q & 1<<I)!=0)
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        return;
    }
    
    static void _intl_print_ui8bits(uint8_t q)
    {
        int I;
        for(I=8-1;I>=0;I--)
        {
            if((q & 1<<I)!=0)
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        return;
    }
    
    int string_to_uccodepoints(const amsstring &str, amsarray<uint32_t> &codepoints)
    {
        long I,J;
        ams_chartype c0;
        uint32_t cp0,cpw;
        
        int escmode;
        int escs;
    
        int correct = 1;
    
        codepoints.reserve(str.length);
        escmode = 0;
        escs = 0;
        for(I=0;I<str.length+1;I++)
        {
            c0 = str.cstring[I];
            if(c0=='\0')
            {
                //end of string, terminate search
                cp0 = 0;
                codepoints.append(cp0);
                break;
            }
    
            else if(escmode==0 && (c0 & 0b10000000)==0)
            {
                //normal ASCII character
                cp0 = (uint32_t)((unsigned char)c0);
                codepoints.append(cp0);
            }
            else if(escmode==0 && (c0 & 0b11100000)==0b11000000)
            {
                escmode = 1; escs = 1;
                cp0 = 0;
                cpw = (uint32_t)(c0 & 0b00011111);
                cp0 = cp0 + (cpw<<((uint32_t)6));
                //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
            }
            else if(escmode==0 && (c0 & 0b11110000)==0b11100000)
            {
                escmode = 2; escs = 2;
                cp0 = 0;
                cpw = (uint32_t)(c0 & 0b00001111);
                cp0 = cp0 + (cpw<<((uint32_t)12));
                //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
            }
            else if(escmode==0 && (c0 & 0b11111000)==0b11110000)
            {
                escmode = 3; escs = 3;
                cp0 = 0;
                cpw = (uint32_t)(c0 & 0b00000111);
                cp0 = cp0 + (cpw<<((uint32_t)18));
                //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
            }
    
            else if(escmode!=0 && escs>0)
            {
                if((c0 & 0b11000000)==0b10000000)
                {
                    cpw = (uint32_t)(c0 & 0b00111111);
                    cp0 = cp0 + (cpw<<((uint32_t)6*(escs-1)));
                    escs--;
                    //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
                }
                else
                {
                    //invalid escape character - this is bad UTF-8
                    correct = 0;
                    escs = 0;
                    escmode = 0;
                    //don't append anything
                    //printf("incorrect!\n");
                }
            }
    
            if(escmode!=0 && escs==0)
            {
                //end escape mode, push character, return to mode 0;
                escmode = 0;
                escs = 0;
                codepoints.append(cp0);
            }
    
        } //for chars in string
    
        codepoints.shrink_to_fit();
    
        return correct;
    }
    
    int string_to_uccodepoints(const amsstring *str, amsarray<uint32_t> *codepoints)
    {
        long I,J;
        ams_chartype c0;
        uint32_t cp0,cpw;
        
        int escmode;
        int escs;
    
        int correct = 1;
    
        codepoints->reserve(str->length);
        escmode = 0;
        escs = 0;
        for(I=0;I<str->length+1;I++)
        {
            c0 = str->cstring[I];
            if(c0=='\0')
            {
                //end of string, terminate search
                cp0 = 0;
                codepoints->append(cp0);
                break;
            }
    
            else if(escmode==0 && (c0 & 0b10000000)==0)
            {
                //normal ASCII character
                cp0 = (uint32_t)((unsigned char)c0);
                codepoints->append(cp0);
            }
            else if(escmode==0 && (c0 & 0b11100000)==0b11000000)
            {
                escmode = 1; escs = 1;
                cp0 = 0;
                cpw = (uint32_t)(c0 & 0b00011111);
                cp0 = cp0 + (cpw<<((uint32_t)6));
                //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
            }
            else if(escmode==0 && (c0 & 0b11110000)==0b11100000)
            {
                escmode = 2; escs = 2;
                cp0 = 0;
                cpw = (uint32_t)(c0 & 0b00001111);
                cp0 = cp0 + (cpw<<((uint32_t)12));
                //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
            }
            else if(escmode==0 && (c0 & 0b11111000)==0b11110000)
            {
                escmode = 3; escs = 3;
                cp0 = 0;
                cpw = (uint32_t)(c0 & 0b00000111);
                cp0 = cp0 + (cpw<<((uint32_t)18));
                //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
            }
    
            else if(escmode!=0 && escs>0)
            {
                if((c0 & 0b11000000)==0b10000000)
                {
                    cpw = (uint32_t)(c0 & 0b00111111);
                    cp0 = cp0 + (cpw<<((uint32_t)6*(escs-1)));
                    escs--;
                    //printf("debug: escs=%d cp0=",escs); _intl_print_ui32bits(cp0); printf("\n");
                }
                else
                {
                    //invalid escape character - this is bad UTF-8
                    correct = 0;
                    escs = 0;
                    escmode = 0;
                    //don't append anything
                    //printf("incorrect!\n");
                }
            }
    
            if(escmode!=0 && escs==0)
            {
                //end escape mode, push character, return to mode 0;
                escmode = 0;
                escs = 0;
                codepoints->append(cp0);
            }
    
        } //for chars in string
    
        codepoints->shrink_to_fit();
    
        return correct;
    }
    
    void uccodepoints_to_string(const amsarray<uint32_t> &codepoints, amsstring &str)
    {
        long I,J;
        uint8_t ch;
        uint32_t cp,bits1,bits2,bits3,bits4;
    
        str.resize(codepoints.length*4+1);
        str.cstring[str.length]='\0'; //guard against last char not being \0
    
        J = 0;
        for(I=0;I<codepoints.length;I++)
        {
            cp = codepoints[I];
            if(cp==0)
            {
                str.cstring[J] = '\0';
                J++;
                break;
            }
    
        //21 bit values
        // 0b0xxxxxxx                               0x00000000  0x0000007F
        // 0b110xxxxx 01xxxxxx                      0x00000080  0x000007FF
        // 0b1110xxxx 01xxxxxx 01xxxxxx             0x00000800  0x0000FFFF
        // 0b11110xxx 01xxxxxx 01xxxxxx 01xxxxxx    0x00010000  0x0010FFFF
    
            if(cp>0 && cp<=0x0000007F)
            {
                ch = (uint8_t) cp;
                str.cstring[J] = ((ams_chartype)((unsigned char)ch));
                J++;
            }
    
            if(cp>=0x00000080 && cp<=0x000007FF)
            {
                bits1 = (cp & (0b00111111))+0b10000000;
                bits2 = ((cp & (0b00011111<<6L))>>6)+0b11000000;
    
                //string.cstring[J] = ((ams_chartype)((unsigned char)bits2));
                str.cstring[J] = bits2;
                J++;
                //string.cstring[J] = ((ams_chartype)((unsigned char)bits1));
                str.cstring[J] = bits1;
                J++;
            }
    
            if(cp>=0x00000800 && cp<=0x0000FFFF)
            {
                bits1 = (cp & (0b00111111))+0b10000000;
                bits2 = ((cp & (0b00111111<<6))>>6)+0b10000000;
                bits3 = ((cp & (0b00001111<<12))>>12)+0b11100000;
    
                str.cstring[J] = ((ams_chartype)((unsigned char)bits3));
                J++;
                str.cstring[J] = ((ams_chartype)((unsigned char)bits2));
                J++;
                str.cstring[J] = ((ams_chartype)((unsigned char)bits1));
                J++;
            }
    
            if(cp>=0x00010000 && cp<=0x010FFFFF)
            {
                bits1 = (cp & (0b00111111))+0b10000000;
                bits2 = ((cp & (0b00111111<<6))>>6)+0b10000000;
                bits3 = ((cp & (0b00111111<<12))>>12)+0b10000000;
                bits4 = ((cp & (0b00000111<<18))>>18)+0b11110000;
    
                str.cstring[J] = ((ams_chartype)((unsigned char)bits4));
                J++;
                str.cstring[J] = ((ams_chartype)((unsigned char)bits3));
                J++;
                str.cstring[J] = ((ams_chartype)((unsigned char)bits2));
                J++;
                str.cstring[J] = ((ams_chartype)((unsigned char)bits1));
                J++;
            }
            
        }
    
        str.shrinktofit();
        return;
    }
    
    void uccodepoints_to_string(const amsarray<uint32_t> *codepoints, amsstring *str)
    {
        long I,J;
        uint8_t ch;
        uint32_t cp,bits1,bits2,bits3,bits4;
    
        str->resize(codepoints->length*4+1);
        str->cstring[str->length]='\0'; //guard against last char not being \0
    
        J = 0;
        for(I=0;I<codepoints->length;I++)
        {
            cp = codepoints->at(I);
            if(cp==0)
            {
                str->cstring[J] = '\0';
                J++;
                break;
            }
    
        //21 bit values
        // 0b0xxxxxxx                               0x00000000  0x0000007F
        // 0b110xxxxx 01xxxxxx                      0x00000080  0x000007FF
        // 0b1110xxxx 01xxxxxx 01xxxxxx             0x00000800  0x0000FFFF
        // 0b11110xxx 01xxxxxx 01xxxxxx 01xxxxxx    0x00010000  0x0010FFFF
    
            if(cp>0 && cp<=0x0000007F)
            {
                ch = (uint8_t) cp;
                str->cstring[J] = ((ams_chartype)((unsigned char)ch));
                J++;
            }
    
            if(cp>=0x00000080 && cp<=0x000007FF)
            {
                bits1 = (cp & (0b00111111))+0b10000000;
                bits2 = ((cp & (0b00011111<<6L))>>6)+0b11000000;
    
                //string.cstring[J] = ((ams_chartype)((unsigned char)bits2));
                str->cstring[J] = bits2;
                J++;
                //string.cstring[J] = ((ams_chartype)((unsigned char)bits1));
                str->cstring[J] = bits1;
                J++;
            }
    
            if(cp>=0x00000800 && cp<=0x0000FFFF)
            {
                bits1 = (cp & (0b00111111))+0b10000000;
                bits2 = ((cp & (0b00111111<<6))>>6)+0b10000000;
                bits3 = ((cp & (0b00001111<<12))>>12)+0b11100000;
    
                str->cstring[J] = ((ams_chartype)((unsigned char)bits3));
                J++;
                str->cstring[J] = ((ams_chartype)((unsigned char)bits2));
                J++;
                str->cstring[J] = ((ams_chartype)((unsigned char)bits1));
                J++;
            }
    
            if(cp>=0x00010000 && cp<=0x010FFFFF)
            {
                bits1 = (cp & (0b00111111))+0b10000000;
                bits2 = ((cp & (0b00111111<<6))>>6)+0b10000000;
                bits3 = ((cp & (0b00111111<<12))>>12)+0b10000000;
                bits4 = ((cp & (0b00000111<<18))>>18)+0b11110000;
    
                str->cstring[J] = ((ams_chartype)((unsigned char)bits4));
                J++;
                str->cstring[J] = ((ams_chartype)((unsigned char)bits3));
                J++;
                str->cstring[J] = ((ams_chartype)((unsigned char)bits2));
                J++;
                str->cstring[J] = ((ams_chartype)((unsigned char)bits1));
                J++;
            }
            
        }
    
        str->shrinktofit();
        return;
    }
    
    
    void test_unicode_ascii_int_conv()
    {
        int I;
        char c;
        unsigned char uc;
        int J1,J2;
    
        for(I=0;I<255;I++)
        {
            c = (char)I;
            uc = (unsigned char) c;
            J1 = (int)c;
            J2 = (int)uc;
    
            printf("%d\t%c\t%c\t%d\t%d\n",I,c,uc,J1,J2);
        }
    
    
        return;
    }
    
    static void test_unicode_conv1_sub(uint32_t codept)
    {
        long I;
        amsarray<uint32_t> codepts1;
        amsarray<uint32_t> codepts2;
        amsstring s1,s2;
    
        codepts1.resize(1);
        codepts1[0] = codept;
    
        uccodepoints_to_string(codepts1,s1);
    
        printf("UCC: "); _intl_print_ui32bits(codepts1[0]); printf("\n");
        printf("STR: ");
        for(I=0;I<s1.length;I++)
        {
            _intl_print_ui8bits((uint8_t)(unsigned char)s1.cstring[I]);
            printf(" ");
        }
        printf("\n");
    
        string_to_uccodepoints(s1,codepts2);
        printf("UCC: "); _intl_print_ui32bits(codepts2[0]); printf("\n");
    
        uccodepoints_to_string(codepts2,s2);
        printf("STR: ");
        for(I=0;I<s2.length;I++)
        {
            _intl_print_ui8bits(s2.cstring[I]);
            printf(" ");
        }
        printf("\n");
    
        if(s1==s2)
        {
            printf("STR: PASS\t");
        }
        else
        {
            printf("STR: FAIL\t");
        }
        if(codepts1[0]==codepts2[0])
        {
            printf("UCC: PASS\n");
        }
        else
        {
            printf("UCC: FAIL\n");
        }
    
        //printf("Can I print the char to terminal?: %s\n",s1.cstring);
    
    
        return;
    }
    
    void test_unicode_conv1()
    {
        uint32_t codept;
    
        //Test bounding cases
    
        codept = 0x7F;
        test_unicode_conv1_sub(codept);
        printf("\n\n");
    
        codept = 0x07FF;
        test_unicode_conv1_sub(codept);
        printf("\n\n");
    
        codept = 0xFFFF;
        test_unicode_conv1_sub(codept);
        printf("\n\n");
    
        codept = 0x0010FFFF;
        test_unicode_conv1_sub(codept);
        printf("\n\n");
    
        codept = 0x07FFFFFF;
        test_unicode_conv1_sub(codept);
        printf("\n\n");
    
        return;
    }
    
    
    static int test_unicode_conv2_sub(amsarray<uint32_t> *cp)
    {
        int I;
        int pass = 1;
        amsstring s1,s2;
        amsarray<uint32_t> cp2;
    
        uccodepoints_to_string(cp,&s1);
        string_to_uccodepoints(&s1,&cp2);
        uccodepoints_to_string(&cp2,&s2);
    
        if(s1==s2 && *cp==cp2)
        {
            pass = 1;
        }
        else
        {
            pass = 0;
            printf("FAIL: \n");
    
        }
    
    
        return pass;
    }
    
    static void test_gen_rand_codepts(int len, amsarray<uint32_t> *cp)
    {
        long I;
        cp->resize(len+1);
        for(I=0;I<len;I++)
        {
            cp->at(I) = ams::rand::randint(1,0x0010FFFF+1);
        }
        cp->at(len) = 0;
    
        return;
    }
    
    void test_unicode_conv2()
    {
        long I,J;
        int pass;
        amsarray<uint32_t> cp;
    
        long ntests = 10000;
        long passes = 0;
        long failures = 0;
        
        int len = 30;
    
        printf("Testing unicode to string conversion.\n");
        printf("%ld tests of %d random codepoints each.\n",ntests,len);
        
        for(I=0;I<ntests;I++)
        {
            test_gen_rand_codepts(10,&cp);
            if(I==0)
            {
                printf("ex cp string:");
                for(J=0;J<cp.length;J++)
                {
                    printf("%d,",cp[J]);
                }
                printf("\n");
            }
            pass = test_unicode_conv2_sub(&cp);
            if(pass==1)
            {
                passes++;
            }
            else
            {
                failures++;
            }
    
            if(I%(ntests/10)==0)
            {
                printf("Test %ld....\n",I);
            }
        }
    
        printf("%ld tests, %ld passes, %ld failures.\n",ntests,passes,failures);
    
        return;
    }
    

};