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

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

//
//Include headers
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "chnlib.h"

//
//Define values
//

//
//Define static values
//
char *CHNLIB_String_Intenal_NullCString = "";

//
//Declare internal functions
//

CHNLIB_String *CHNLIB_String_Internal_Allocate(void);
void CHNLIB_String_Internal_Destruct(void **structure);
uint CHNLIB_String_Internal_GetHash(const void *structure);

//
//Define types
//

struct CHNLIB_STRING {
    //文字列を保持する
    CHNLIB_StructureHeader header;
    char *str;
};

//
//Functions(CHNLIB_String)
//

CHNLIB_String *CHNLIB_String_Initialize(const char str[])
{
    //指定された引数strと等価なStringを確保し返す。
    //str==NULLの時は、NULLポインタへの参照を持つStringを返す。
    CHNLIB_String *strtag;
    
    strtag = CHNLIB_String_Internal_Allocate();
    strtag->header.destructor = &CHNLIB_String_Internal_Destruct;
    strtag->header.getHash = &CHNLIB_String_Internal_GetHash;
    
    if(str != NULL){
        CHNLIB_String_SetStringFromCString(strtag, str);
    }
    return strtag;
}

CHNLIB_String *CHNLIB_String_InitializeWithFormat(const char format[], ...)
{
    //CHNLIB_String *strtag;
    va_list ap;
    char str[CHNLIB_MAX_STRING_LENGTH];
    
    va_start(ap, format);
    vsnprintf(str, sizeof(str), format, ap);
    va_end(ap);
    /*
    strtag = CHNLIB_String_Internal_Allocate();
    strtag->header.destructor = &CHNLIB_String_Internal_Destruct;
    
    if(str != NULL){
        CHNLIB_String_SetStringFromCString(strtag, str);
    }
     */
    return CHNLIB_String_Initialize(str);
}

void CHNLIB_String_Free(CHNLIB_String *strtag)
{
    //strtagを解放する。
    if(CHNLIB_StructureHeader_GetTypeID(strtag) != CHNLIB_STRUCT_ID_String){
        return;
    }
    
    if(strtag->str != NULL){
        CHNLIB_System_FreeMemory(strtag->str, CHNLIB_DEBUG_ARGUMENTS);
    }
    strtag->header.typeid = CHNLIB_STRUCT_ID_Null;
    strtag->header.signature = 0;
    CHNLIB_System_FreeMemory(strtag, CHNLIB_DEBUG_ARGUMENTS);
    
    return;
}

int CHNLIB_String_SetStringFromCString(CHNLIB_String *strtag, const char s[])
{
    //strtagにsと等価な文字列を代入する。
    //strtag==Invalid || s==NULLのときは何もしない。
    int i, size;
    
    if(CHNLIB_StructureHeader_GetTypeID(strtag) != CHNLIB_STRUCT_ID_String){
        return 0;
    }
    
    if(s == NULL){
        return 0;
    }
    
    size = CHNLIB_CString_GetByteLength(s) + 1;
    if(strtag->str != NULL){
        CHNLIB_System_FreeMemory(strtag->str, CHNLIB_DEBUG_ARGUMENTS);
    }
    strtag->str = CHNLIB_System_AllocateMemory_Strict(size, CHNLIB_DEBUG_ARGUMENTS);
    for(i = 0; i < size - 1; i++){
        strtag->str[i] = s[i];
    }
    strtag->str[i] = '\0';
    return size;
}

int CHNLIB_String_Print(CHNLIB_String *strtag)
{
    //strtagが格納している文字列を標準出力に出力する。
    if(CHNLIB_StructureHeader_GetTypeID(strtag) != CHNLIB_STRUCT_ID_String){
        return 0;
    }
    if(strtag->str == NULL){
        return 0;
    }
    
    return fputs(strtag->str, stdout);
}

const char *CHNLIB_String_GetReferencePointerOfCString(const CHNLIB_String *strtag)
{
    //strtagが格納している文字列へのポインタを返す。
    //strtag->str==NULLの場合、constな空文字へのポインタを返す。
    if(CHNLIB_StructureHeader_GetTypeID(strtag) != CHNLIB_STRUCT_ID_String){
        return NULL;
    }
    
    if(strtag->str == NULL){
        return CHNLIB_String_Intenal_NullCString;
    }
    
    return (const char *)strtag->str;
}

CHNLIB_String *CHNLIB_String_ExtractByLength(const CHNLIB_String *strtag, int start, int len)
{
    //strtagが格納している文字列sについて、
    //s[start]からs[start + len - 1]の文字を含む文字列と等価なStringを確保し返す。
    //lenに満たずにsが終端文字を迎えた場合は、sの終端文字直前までの文字列がコピーされる。
    //(len < 0)のときは、NULLを返す。また、結果として(len < 0)となった時も同様にNULLを返す。
    char *retstr;
    CHNLIB_String *retstrtag;
    
    if(strtag == NULL){
        return NULL;
    }
    
    retstr = CHNLIB_CString_ExtractByLength(strtag->str, start, len);
    
    if(retstr == NULL){
        return NULL;
    }
    
    retstrtag = CHNLIB_String_Initialize(NULL);
    retstrtag->str = retstr;
    
    return retstrtag;
}

CHNLIB_String *CHNLIB_String_Concatenate(const CHNLIB_String *s1, const CHNLIB_String *s2)
{
    //二つの文字列を0x00以外のバイトは全て文字と見なして連結する。
    //戻り値はs1,s2の順に結合された、新たな文字列へのポインタ。
    //どちらもNULLの場合はNULLポインタを、
    //どちらかが文字列の場合はその文字列のコピーを新たに作成して返す。
    char *retstr;
    CHNLIB_String *retstrtag;
    
    if(s1 == NULL || s2 == NULL){
        if(s1 != NULL){
            return CHNLIB_String_Copy(s1);
        }
        if(s2 != NULL){
            return CHNLIB_String_Copy(s2);
        }
    }
    
    retstr = CHNLIB_CString_Concatenate(s1->str, s2->str);
    
    if(retstr == NULL){
        return NULL;
    }
    
    retstrtag = CHNLIB_String_Initialize(NULL);
    retstrtag->str = retstr;
    
    return retstrtag;
}

int CHNLIB_String_GetLength(const CHNLIB_String *strtag)
{
    if(CHNLIB_StructureHeader_GetTypeID(strtag) != CHNLIB_STRUCT_ID_String){
        return 0;
    }
    
    return CHNLIB_CString_GetByteLength(strtag->str);
}

void CHNLIB_String_DeleteLastCRLF(CHNLIB_String *strtag)
{
    if(CHNLIB_StructureHeader_GetTypeID(strtag) != CHNLIB_STRUCT_ID_String){
        return;
    }
    
    CHNLIB_CString_DeleteLastCRLF(strtag->str);
    
    return;
}

int CHNLIB_String_CompareStringWithCString(const CHNLIB_String *s, const char search[])
{
    return CHNLIB_CString_CompareString(CHNLIB_String_GetReferencePointerOfCString(s), search);
}

int CHNLIB_String_CompareString_Strict(const CHNLIB_String *s, const CHNLIB_String *search)
{
    return CHNLIB_CString_CompareString_Strict(CHNLIB_String_GetReferencePointerOfCString(s), CHNLIB_String_GetReferencePointerOfCString(search));
}

uint CHNLIB_String_GetCountOfContain(const CHNLIB_String *s, const CHNLIB_String *search)
{
    return CHNLIB_UTF8_GetCountOfContain(CHNLIB_String_GetReferencePointerOfCString(s), CHNLIB_String_GetReferencePointerOfCString(search));
}

CHNLIB_String *CHNLIB_String_Copy(const CHNLIB_String *s)
{
    char *retstr;
    CHNLIB_String *retstrtag;
    
    if(s == NULL){
        return NULL;
    }
    
    retstr = CHNLIB_CString_Copy(s->str);
    
    if(retstr == NULL){
        return NULL;
    }
    
    retstrtag = CHNLIB_String_Initialize(NULL);
    retstrtag->str = retstr;
    
    return retstrtag;
}

//
//Functions(CString(char[]))
//

int CHNLIB_CString_GetByteLength(const char s[])
{
    //[CString]
    //終端文字を除いた、文字列本体のバイト数を返す。
    int i;
    
    if(s == NULL){
        return 0;
    }
    
    for(i = 0; s[i] != 0x00; i++){
        
    }
    
    return i;
}

void CHNLIB_CString_DeleteLastCRLF(char s[])
{
    //末尾のLF, CR/LFを\0に置換する
    int i, crlf;
    
    if(s == NULL){
        return;
    }
    
    crlf = -1;
    for(i = 0; s[i] != '\0'; i++){
        if(s[i] == '\n'){
            if(crlf == -1 || crlf + 1 != i){
                crlf = i;
            }
        }
        if(s[i] == '\r'){
            if(crlf == -1 || crlf + 1 != i){
                crlf = i;
            }
        }
    }
    if(crlf != -1){
        for(i = crlf; s[i] != '\0'; i++){
            s[i] = '\0';
        }
    }
    return;
}

char *CHNLIB_CString_ExtractByLength(const char s[], int start, int len)
{
    //s[start]からs[start + len - 1]の文字を含む文字列を、新たにメモリを確保して書き込み、その先頭アドレスを返す。
    //lenに満たずにsが終端文字を迎えた場合は、sの終端文字直前までの文字列がコピーされる。
    //(len < 0)のときは、NULLを返す。また、結果として(len < 0)となった時も同様にNULLを返す。
    char *str;
    int i;
    int utf8type;
    
    if(s == NULL){
        CHNLIB_ReportError("Null s[]\n", CHNLIB_DEBUG_ARGUMENTS);
        return NULL;
    }
    
    i = CHNLIB_CString_GetByteLength(s) + 1;
    
    if(i > (len + 1)){
        i = len + 1;
    }
    
    if(len == 0){
        return NULL;
    }
    
    str = CHNLIB_System_AllocateMemory_Strict(i, CHNLIB_DEBUG_ARGUMENTS);

    for(i = 0; s[i + start] != '\0'; i++){
        if(len <= 0){
            break;
        }
        //**UTF-8
        utf8type = CHNLIB_UTF8_GetCharacterType(s[i + start]);
        if(len < utf8type){
            break;
        }
        //****
        len--;
        str[i] = s[i + start];
    }
    str[i] = '\0';
    
    return str;
}

int CHNLIB_CString_CompareString(const char s[], const char search[])
{
    //s[]の先頭からsearch[]と比較し、searchの終端まで一致したらTrue, 一致しなかったらFalseを返す。
    //終端文字'\0'はカウントしない。
    //search[]に含まれる文字（終端文字除く）がすべて入っていれば一致とみなす。
    //どちらかがNULLであった場合は、Falseを返す。
    //一致文字数が0(search=="")だった場合もFalseを返す。
    int i;
    
    if(s == NULL || search == NULL){
#ifdef DEBUG_STRING_STRICT
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
#endif
        return False;
    }
    
    for(i = 0; search[i] != '\0'; i++){
        if(s[i] != search[i]){
            return False;
        }
    }
    if(i == 0){
        return False;
    }
    return True;
}

int CHNLIB_CString_CompareString_Strict(const char s[], const char search[])
{
    //二つの文字列が終端文字までを含めて完全に一致するかどうか調べる。一致していればTrueを返す。
    //StrictよりExactの方が適する？<関数名
    int i;
    
    if(s == NULL || search == NULL){
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
        return False;
    }
    
    for(i = 0; search[i] != '\0'; i++){
        if(s[i] != search[i]){
            return False;
        }
    }
    if(s[i] != '\0'){
        return False;
    }
    return True;
}

int CHNLIB_CString_CompareString_LeftHand(const char s[], const char search[])
{
    //二つの文字列がどの程度一致するか調べる。前方一致。
    //戻り値は、終端文字を除く、同一だったバイト数。
    int i;
    
    if(s == NULL || search == NULL){
        CHNLIB_ReportError("Null str.\n", CHNLIB_DEBUG_ARGUMENTS);
        return 0;
    }
    
    for(i = 0; search[i] != '\0'; i++){
        if(s[i] != search[i]){
            break;
        }
    }

    return i;
}

char *CHNLIB_CString_Copy(const char s[])
{
    //与えられた文字列と等価な文字列を作成して、そのポインタを返す。
    //有効な文字列が無い場合（NULLポインタも含む）、NULLを返す。
    char *copyString;
    int i, i_max;
    
    i_max = CHNLIB_CString_GetByteLength(s) + 1;
    if(i_max <= 1){
        return NULL;
    }
    
    copyString = CHNLIB_System_AllocateMemory_Strict(i_max, CHNLIB_DEBUG_ARGUMENTS);
    for(i = 0; i < i_max - 1; i++){
        copyString[i] = s[i];
    }
    copyString[i] = '\0';
    
    return copyString;
}

char *CHNLIB_CString_Concatenate(const char s1[], const char s2[])
{
    //二つの文字列を0x00以外のバイトは全て文字と見なして連結する。
    //戻り値はs1,s2の順に結合された、新たな文字列へのポインタ。
    //どちらもNULLの場合はNULLポインタを、
    //どちらかが文字列の場合はその文字列のコピーを新たに作成して返す。
    int s1_len, s2_len, i, concatenatedSize;
    char *concatenatedString;
    
    if(s1 == NULL || s2 == NULL){
        if(s1 != NULL){
            return CHNLIB_CString_Copy(s1);
        }
        
        if(s2 != NULL){
            return CHNLIB_CString_Copy(s2);
        }
    }
    
    s1_len = CHNLIB_CString_GetByteLength(s1);
    s2_len = CHNLIB_CString_GetByteLength(s2);
    
    concatenatedSize = s1_len + s2_len + 1;
    
    concatenatedString = CHNLIB_System_AllocateMemory_Strict(concatenatedSize, CHNLIB_DEBUG_ARGUMENTS);
    for(i = 0; i < s1_len; i++){
        concatenatedString[i] = s1[i];
    }
    for(i = 0; i < s2_len; i++){
        concatenatedString[i + s1_len] = s2[i];
    }
    concatenatedString[concatenatedSize - 1] = '\0';
    
    return concatenatedString;
}
    
//
//Internal functions
//

CHNLIB_String *CHNLIB_String_Internal_Allocate(void)
{
    CHNLIB_String *tag;
    
    tag = (CHNLIB_String *)CHNLIB_System_AllocateMemory_Strict(sizeof(CHNLIB_String), CHNLIB_DEBUG_ARGUMENTS);
    
    CHNLIB_StructureHeader_Initialize(&tag->header, CHNLIB_STRUCT_ID_String);
    
    return tag;
}

void CHNLIB_String_Internal_Destruct(void **structure)
{
    //デストラクタ（実際にRelease->freeされる時に呼ばれる）
    if(structure == NULL){
        return;
    }
    
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
    CHNLIB_Debug("Release(with free)[%p].", CHNLIB_DEBUG_ARGUMENTS, *structure);
#endif
    
    CHNLIB_String_Free(*structure);
    
    *structure = NULL;
    
    return;
}

uint CHNLIB_String_Internal_GetHash(const void *structure)
{
    uint hash;
    const char *p;
    
    hash = 0;
    
    if(CHNLIB_StructureHeader_GetTypeID(structure) == CHNLIB_STRUCT_ID_String){
        p = ((CHNLIB_String *)structure)->str;
        if(p != NULL){
            for(; *p != '\0'; p++){
                hash += *(unsigned char *)p;
            }
        }
    }
    
    return hash;
}
