//
//  chnlib04.c
//  AI003
//
//  Created by 西田　耀 on 13/02/10.
//  Copyright (c) 2013年 Hikaru Nishida. All rights reserved.
//

//UTF-8関連のうち、このソースファイルで完結する関数群

//
//Include headers
//

#include <stdio.h>
#include "chnlib.h"

//
//Functions(UTF-8)
//

int CHNLIB_UTF8_GetCharacterType(char c)
{
    //[UTF-8]
    //UTF-8文字列中の1バイトcが、UTF-8文字列中でどのような役割を持つのかを返す。
    //0:マルチバイト文字の後続バイト
    //1:1バイト文字
    //2以上の数値n:nバイト文字の最初のバイト。後続のn個のマルチバイト後続バイトと組み合わせて一つの文字を示す。
    //判断不能な場合は-1を返す。
    if(((c >> 6) & 3) == 2){
        //マルチバイト後続バイト
        //10xxxxxx
        return 0;
    } else if(((c >> 7) & 1) == 0){
        //1Byte
        //7bit
        //0xxxxxxx
        return 1;
    } else if(((c >> 5) & 7) == 6){
        //2Byte
        //11bit
        //110xxxxx
        return 2;
    } else if(((c >> 4) & 15) == 14){
        //3Byte
        //16bit
        //1110xxxx
        return 3;
    } else if(((c >> 3) & 31) == 30){
        //4Byte
        //21bit
        //11110xxx
        return 4;
    }
    
    return -1;
}

int CHNLIB_UTF8_GetStringLengthByCharacter(const char s[])
{
    //[UTF-8]
    //sをUTF-8文字列として解釈し、文字としては何文字で構成された文字列かを返す。
    //無効な（マルチバイトの一部が欠けているような）文字はカウントしない。
    int i, mbsize, n;
    int count;
    
    if(s == NULL){
        return 0;
    }
    
    count = 0;
    mbsize = 0;
    for(i = 0; s[i] != '\0'; i++){
        n = CHNLIB_UTF8_GetCharacterType(s[i]);
        switch(n){
            case -1:
                //無効なバイト
                mbsize = 0;
                break;
            case 0:
                //マルチバイト後続バイト
                if(mbsize > 0){
                    mbsize--;
                    if(mbsize == 0){
                        count++;
                    }
                }
                break;
            case 1:
                //1バイト文字
                count++;
                if(mbsize > 0){
                    mbsize = 0;
                }
                break;
            default:
                //nバイト文字の最初のバイト
                mbsize = n - 1;
                break;
        }
    }
    return count;
}

uint CHNLIB_UTF8_GetNextUnicodeOfCharacter(const char s[], const char **next)
{
    //[UTF-8]
    //sをUTF-8文字列として解釈し、その文字列の最初の文字のUnicodeを返す。
    //また、nextがNULLでないとき、*nextに、この文字列中で次の文字にあたる部分へのポインタを格納する。
    //s==NULLの時、*nextの値は変更されない。
    //次の文字が存在しない場合は、*nextはs中の終端文字へのポインタとなる。また、戻り値は0となる。
    //無効な（マルチバイトの一部が欠けているような）文字は無視する。
    int i, n, mbsize;
    uint unicode;
    
    if(s == NULL){
        return 0;
    }
    
    unicode = 0;
    mbsize = 0;
    for(i = 0; s[i] != '\0'; i++){
        n = CHNLIB_UTF8_GetCharacterType(s[i]);
        switch(n){
            case -1:
                //無効なバイト
                mbsize = 0;
                break;
            case 0:
                //マルチバイト後続バイト
                if(mbsize > 0){
                    mbsize--;
                    unicode <<= 6;
                    unicode |= (0x3f & s[i]);
                    if(mbsize == 0){
                        return unicode;
                    }
                }
                break;
            case 1:
                //1バイト文字
                if(next != NULL){
                    *next = (char *)(s + 1);
                }
                return s[i];
            default:
                //nバイト文字の最初のバイト
                unicode = (((s[i]) << (n + 1)) >> n + 1);
                mbsize = n - 1;
                if(next != NULL){
                    *next = (char *)(s + n);
                }
                break;
        }
    }
    if(next != NULL){
        *next = &s[i];
    }
    return 0;
}

int CHNLIB_UTF8_CompareString(const char s[], const char search[])
{
    //[UTF-8]
    //s,searchをUTF-8文字列として解釈し、s[]の先頭からsearch[]と比較し、searchの終端まで一致したらTrue, 一致しなかったらFalseを返す。
    //終端文字は比較せず、search[]に含まれる文字（終端文字除く）がすべて入っていれば一致とみなす。
    //ただし、searchの先頭文字が終端文字、つまり空文字列だった場合は、常にFalseを返す。
    int i, i_max;
    
    if(s == NULL || search == NULL){
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
        return False;
    }
    if(search[0] == '\0'){
        //空文字はいかなる文字列とも一致しない。
        return False;
    }
    
    i_max = CHNLIB_UTF8_GetStringLengthByCharacter(search);
    for(i = 0; i < i_max; i++){
        if(CHNLIB_UTF8_GetNextUnicodeOfCharacter(s, &s) != CHNLIB_UTF8_GetNextUnicodeOfCharacter(search, &search)){
            return False;
        }
    }
    return True;
}

int CHNLIB_UTF8_CompareString_LeftHand(const char s[], const char search[])
{
    //[UTF-8]
    //s,searchをUTF-8文字列として解釈し、二つの文字列がどの程度一致するか調べる。前方一致。
    //戻り値は、終端文字を除く、同一だった文字数。
    int i, i_max;
    
    if(s == NULL || search == NULL){
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
        return 0;
    }
    
    i_max = CHNLIB_UTF8_GetStringLengthByCharacter(search);
    for(i = 0; i < i_max; i++){
        if(CHNLIB_UTF8_GetNextUnicodeOfCharacter(s, &s) != CHNLIB_UTF8_GetNextUnicodeOfCharacter(search, &search)){
            break;
        }
    }
    return i;
}

int CHNLIB_UTF8_GetByteSizeFromLengthByCharacter(const char s[], int start, int end)
{
    //[UTF-8]
    //sをUTF-8文字列として解釈し、start番目の文字からend番目の文字までの占めるバイト数を返す。
    //start番目の文字が終端文字以降の文字を指している場合は0を返す。
    //end番目の文字が終端文字以降の文字を指していても、endが文字列終端を指している場合と同値になる。
    int i;
    const char *p;
    
    if(s == NULL){
#ifdef DEBUG_STRING_STRICT
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
#endif
        return 0;
    }
    if(end < start){
#ifdef DEBUG_STRING_STRICT
        CHNLIB_ReportError("Invalid index.\n", CHNLIB_DEBUG_ARGUMENTS);
#endif
        return 0;
    }
    
    for(i = 0; i < start; i++){
        if(CHNLIB_UTF8_GetNextUnicodeOfCharacter(s, &s) == 0){
            return 0;
        }
    }
    p = s;
    for(; i <= end; i++){
        CHNLIB_UTF8_GetNextUnicodeOfCharacter(p, &p);
    }
    
    return (int)(p - s);
}

uint CHNLIB_UTF8_GetCountOfContain(const char s[], const char search[])
{
    //[UTF-8]
    //文字列s中に、文字列searchがいくつ含まれているかを返す。
    uint count;
    
    if(s == NULL || search == NULL){
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
        return 0;
    }
    
    count = 0;
    for(;;){
        if(CHNLIB_UTF8_CompareString(s, search)){
            count++;
        }
        if(CHNLIB_UTF8_GetNextUnicodeOfCharacter(s, &s) == 0){
            break;
        }
    }
    return count;
}
