//
//  chnlib.c
//  AI003
//
//  Created by 西田　耀 on 13/01/28.
//  Copyright (c) 2013年 Hikaru Nishida. All rights reserved.
//

//  ライブラリ全体を支援する関数群

//
//Include headers
//

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

#include "chnlib.h"

//
//Define static values
//
int CHNLIB_Debug_PrintStructureData_RecursiveCounter;

//
//Declare internal functions
//
const char *CHNLIB_Debug_Internal_GetStructTypeNameByID(uint typeid);
void CHNLIB_Debug_Internal_PrintStructureDataSub(void *structure, uint level);
void CHNLIB_Debug_Internal_PrintIndent(int level);

//
//Define types
//

//
//Functions
//

void CHNLIB_Debug(const char format[], const char filename[], int line, const char funcname[], ...)
{
    //デバッグ用printf
    char s[CHNLIB_MAX_STRING_LENGTH];
    va_list varg;
    
    fprintf(stdout, "CHNLIB:Debug:%s@%s:%d:", funcname, filename, line);
    
    va_start(varg, funcname);
    vsnprintf(s, sizeof(s), format, varg);
    va_end(varg);
    
    fputs(s, stdout);
    fputs("\n", stdout);
	
	fflush(stdout);
	
    return;
}

void CHNLIB_Debug_PrintStructureData(void *structure, uint level)
{
    //ライブラリ定義の構造体の中身を表示する。
    //structureがUIPArrayを指していた場合、その中身も表示する。表示する階層はlevelで制限できる。
    //-level == 0:全階層
    //-level >= 1:level階層まで

    CHNLIB_Debug_PrintStructureData_RecursiveCounter = level - 1;
    
    CHNLIB_Debug_Internal_PrintStructureDataSub(structure, 0);
    
    return;
}

void CHNLIB_ReportError(const char format[], const char filename[], int line, const char funcname[], ...)
{
    //stderrにprintfの書式に従って出力する。
    char s[CHNLIB_MAX_STRING_LENGTH];
    va_list varg;
    
    fprintf(stderr, "CHNLIB:Error:%s@%s:%d:", funcname, filename, line);
    
    va_start(varg, funcname);
    vsnprintf(s, sizeof(s), format, varg);
    va_end(varg);
    
    fputs(s, stderr);
	fputs("\n", stderr);
    
    return;
}

void CHNLIB_StructureHeader_Initialize(CHNLIB_StructureHeader *header, uint typeid)
{
    //ライブラリ共通構造体ヘッダの初期化を行う。
    //各構造体のInitializeで呼ばれることを想定している。
    //referenceCountは1で初期化される（初期化を行った関数が所有すると考える）
    if(header == NULL){
        CHNLIB_ReportError("Null structure.", CHNLIB_DEBUG_ARGUMENTS);
        return;
    }
    
    header->signature = CHNLIB_STRUCTURE_SIGNATURE;
    header->typeid = typeid;
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
    CHNLIB_Debug("Init with referenceCount = 1 [%p].", CHNLIB_DEBUG_ARGUMENTS, header);
#endif
    header->referenceCount = 1;
    header->destructor = NULL;
    header->getHash = NULL;
    return;
}

uint CHNLIB_StructureHeader_GetTypeID(const void *structure)
{
    //structureが指す構造体のヘッダを確認して、その構造体のtypeidを返す。
    //ポインタの正当性の実証に利用するとよい。
    CHNLIB_StructureHeader *strhead;
    
    if(structure == NULL){
        return CHNLIB_STRUCT_ID_Null;
    }
    
    strhead = (CHNLIB_StructureHeader *)structure;
    if(strhead->signature == CHNLIB_STRUCTURE_SIGNATURE){
        return strhead->typeid;
    }
    return CHNLIB_STRUCT_ID_Null;
}

uint CHNLIB_StructureHeader_GetHash(const void *structure)
{
    //structureが指す構造体のヘッダを確認して、その構造体定義のハッシュ値を返す。
    CHNLIB_StructureHeader *strhead;
    
    if(structure == NULL){
        return 0;
    }
    
    strhead = (CHNLIB_StructureHeader *)structure;
    if(strhead->signature == CHNLIB_STRUCTURE_SIGNATURE){
        if(strhead->getHash != NULL){
            return strhead->getHash(structure);
        }
    }
    return 0;
}

void *CHNLIB_System_AllocateMemory_Strict(int size, const char filename[], int line, const char funcname[])
{
    //mallocを行い、確保が失敗した場合にメッセージを出力してプログラムを終了する。
    //また、この関数で取得されたメモリはゼロクリアされていることが保証される。
    void *p;
    
    p = malloc(size);
    if(p == NULL){
        CHNLIB_ReportError("An attempt to allocate memory %d bytes in %s@%s:%d: is failed. Abort.", CHNLIB_DEBUG_ARGUMENTS, size, filename, line, funcname);
        exit(EXIT_FAILURE);
    }
    
#ifdef DEBUG_MEMORY_ALLOCATION
    CHNLIB_Debug("Request allocation %d bytes ->[%p].", CHNLIB_DEBUG_ARGUMENTS, size, p);
#endif
    
    memset(p, 0, size);

    return p;
}

void CHNLIB_System_FreeMemory(void *p, const char filename[], int line, const char funcname[])
{
    //ポインタがNullでないことを確認して、Freeする。
    if(p == NULL){
        CHNLIB_ReportError("An attempt to free NULL memory in %s@%s:%d:. Ignore.", CHNLIB_DEBUG_ARGUMENTS, filename, line, funcname);
        return;
    }
    
#ifdef DEBUG_MEMORY_ALLOCATION
    CHNLIB_Debug("Request ƒree memory [%p].", CHNLIB_DEBUG_ARGUMENTS, p);
#endif
    
    free(p);
    
    return;
}

void CHNLIB_Retain(void **structure)
{
    //structureのreferenceCountをインクリメントする。
    //referenceCountの初期値は1
    
    CHNLIB_StructureHeader *strhead;
    
    if(structure == NULL || *structure == NULL){
        return;
    }
    
    strhead = (CHNLIB_StructureHeader *)*structure;
    if(strhead->signature == CHNLIB_STRUCTURE_SIGNATURE){
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
        CHNLIB_Debug("Retain [%p].", CHNLIB_DEBUG_ARGUMENTS, *structure);
#endif
        strhead->referenceCount++;
    }
    
    return;
}

void CHNLIB_Release(void **structure)
{
    //structureのreferenceCountをデクリメントする。
    //デクリメントした結果referenceCount==0となった時、そのstructureを解放する。
    //structureが内包しているオブジェクトについては、releaseを行う。
    
    CHNLIB_StructureHeader *strhead;
    
    if(structure == NULL || *structure == NULL){
        return;
    }
    
    strhead = (CHNLIB_StructureHeader *)*structure;
    if(strhead->signature == CHNLIB_STRUCTURE_SIGNATURE){
        if(strhead->referenceCount <= 1){
            if(strhead->destructor == NULL){
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
                CHNLIB_Debug("Release(with free)[%p].", CHNLIB_DEBUG_ARGUMENTS, *structure);
#endif
                free(*structure);
            } else{
                strhead->destructor(structure);
            }
        } else{
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
            CHNLIB_Debug("Release[%p].", CHNLIB_DEBUG_ARGUMENTS, *structure);
#endif
            strhead->referenceCount--;
            *structure = NULL;
        }
    }
    
    return;
}

void *CHNLIB_AutoRelease(void *structure)
{
    //structureのreferenceCountを、destructせずに0にする。
    //オブジェクト確保後に自分の所有権を放棄して他の関数へ渡すときに利用する。
    
    CHNLIB_StructureHeader *strhead;
    
    if(structure != NULL){
        strhead = (CHNLIB_StructureHeader *)structure;
        if(strhead->signature == CHNLIB_STRUCTURE_SIGNATURE){
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
            CHNLIB_Debug("Set AutoRelease [%p].", CHNLIB_DEBUG_ARGUMENTS, structure);
#endif
            strhead->referenceCount = 0;
        }
    }
    
    return structure;
}

//
//Internal functions
//

const char *CHNLIB_Debug_Internal_GetStructTypeNameByID(uint typeid)
{
    //デバッグ出力で利用するための構造体名を取得する。
    //このソース以外から呼び出してはならない。
    switch(typeid){
        case CHNLIB_STRUCT_ID_Null:
            return "Null";
        case CHNLIB_STRUCT_ID_UIPArray:
            return "UIPArray";
        case CHNLIB_STRUCT_ID_String:
            return "String";
    }
    return "Unknown";
}

void CHNLIB_Debug_Internal_PrintStructureDataSub(void *structure, uint level)
{
    //デバッグ出力のサブルーチン。
    //このソース以外から呼び出してはならない。
    int typeid, i, j;
    void *p;
    
    typeid = CHNLIB_StructureHeader_GetTypeID(structure);
    
    switch(typeid){
        case CHNLIB_STRUCT_ID_UIPArray:
            i = CHNLIB_UIPArray_GetNumberOfDatas(structure);
            CHNLIB_Debug_Internal_PrintIndent(level);
            CHNLIB_Debug("%s[%p]:Number of datas=%d", "", 0, "", CHNLIB_Debug_Internal_GetStructTypeNameByID(typeid), structure, i);
            for(j = 0; j < i; j++){
                p = CHNLIB_UIPArray_GetPointerByIndex(structure, j);
                CHNLIB_Debug_Internal_PrintIndent(level);
                CHNLIB_Debug("%s[%p]:[%d]=(%u,[%p])", "", 0, "", CHNLIB_Debug_Internal_GetStructTypeNameByID(typeid), structure, j, CHNLIB_UIPArray_GetData32ByIndex(structure, j), p);
                if(CHNLIB_Debug_PrintStructureData_RecursiveCounter != 0){
                    CHNLIB_Debug_Internal_PrintStructureDataSub(p, level + 1);
                }
            }
            break;
        case CHNLIB_STRUCT_ID_String:
            CHNLIB_Debug_Internal_PrintIndent(level);
            CHNLIB_Debug("%s[%p]:[%s]", "", 0, "", CHNLIB_Debug_Internal_GetStructTypeNameByID(typeid), structure, CHNLIB_String_GetReferencePointerOfCString(structure));
            break;
        default:
            CHNLIB_Debug_Internal_PrintIndent(level);
            CHNLIB_Debug("%s[%p]:NULL structure or not implemented.", "", 0, "", CHNLIB_Debug_Internal_GetStructTypeNameByID(typeid), structure);
            break;
    }
    return;
}

void CHNLIB_Debug_Internal_PrintIndent(int level)
{
    //デバッグ出力のサブルーチン。
    //このソース以外から呼び出してはならない。
    int i;
    
    for(i = 0; i < level; i++){
        fputs("    ", stdout);
    }
	
    return;
}
