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

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

//
//Include headers
//

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

//
//Define values
//
#define CHNLIB_UIPArray_INTERNAL_PACKDATAS   7

//
//Declare internal functions
//
CHNLIB_UIPArray *CHNLIB_UIPArray_Internal_Allocate(void);
const CHNLIB_UIPArray **CHNLIB_UIPArray_Internal_CheckIndex(const CHNLIB_UIPArray **array, int *index);
void CHNLIB_UIPArray_Internal_Destruct(void **structure);
uint CHNLIB_UIPArray_Internal_GetHash(const void *structure);

//
//Define types
//
typedef struct CHNLIB_UIPARRAY_INTERNAL_TAG CHNLIB_UIPArray_Internal_Tag;
struct CHNLIB_UIPARRAY_INTERNAL_TAG {
    //UIPArrayの一つの添字に対応するデータを格納する構造体
    uint data32;
    void *pointer;
};

struct CHNLIB_UIPARRAY {
    //UnsignedIntとPointerを格納するArray構造体
    //headerはすべてのpackにあるが、ReferenceCountはその特性上、先頭のものだけが有効である。
    CHNLIB_StructureHeader header;
    
    int using_tags;    //Number of using tags in this pack.
    CHNLIB_UIPArray_Internal_Tag tag[CHNLIB_UIPArray_INTERNAL_PACKDATAS];
    CHNLIB_UIPArray *next;
};

//
//Functions
//
CHNLIB_UIPArray *CHNLIB_UIPArray_Initialize(void)
{
    //ポインタを初期化する(NULLを返す)。
    return NULL;
}

int CHNLIB_UIPArray_AppendLast(CHNLIB_UIPArray **array, uint data32, void *pointer)
{
    //戻り値:追加されたタグのIndex
    //Arrayの末尾にデータを追加する。
    //追加された要素のpointerの参照先はArrayが所有する(Retain)
    int index;
    
    index = 0;
    for(; *array != NULL && (*array)->using_tags == CHNLIB_UIPArray_INTERNAL_PACKDATAS; array = &((*array)->next)){
        index += CHNLIB_UIPArray_INTERNAL_PACKDATAS;
    }
    
    if(*array == NULL){
        *array = CHNLIB_UIPArray_Internal_Allocate();
    }
    (*array)->tag[(*array)->using_tags].data32 = data32;
    (*array)->tag[(*array)->using_tags].pointer = pointer;
    (*array)->using_tags++;
    
    CHNLIB_Retain(&pointer);
    
    return index + (*array)->using_tags - 1;
}

int CHNLIB_UIPArray_AppendLast_ProtectFromDuplication(CHNLIB_UIPArray **array, uint data32, void *pointer, int (*IsDuplicated)(const void *listtag, const void *newtag))
{
    //戻り値:追加されたタグ、もしくは重複する既存のタグのIndex
    //int (*IsDuplicated)(void *listtag, void *newtag)は、重複を確認する関数へのポインタ。
    //listtagにarrayのすべてのpointerを与え、すべてがFalseだった場合追加する。
    //Trueが返った時点で、追加をしないで既存のタグに対するIndexを返す。
    int i, i_max;
    
    i_max = CHNLIB_UIPArray_GetNumberOfDatas(*array);
    for(i = 0; i < i_max; i++){
        if(IsDuplicated(CHNLIB_UIPArray_GetPointerByIndex(*array, i), pointer)){
            return i;
        }
    }
    
    return CHNLIB_UIPArray_AppendLast(array, data32, pointer);
}

int CHNLIB_UIPArray_RemoveByIndex(CHNLIB_UIPArray **array, int index)
{
    //戻り値:関数が失敗したか（False:成功,True:指定された添字の要素は存在しない）
    //indexが指し示す要素を削除し、後続の要素は前へつめる。
    //削除された要素のpointerの参照先の所有を放棄する(Release)。
    int i;
    
    array = (CHNLIB_UIPArray **)CHNLIB_UIPArray_Internal_CheckIndex((const CHNLIB_UIPArray **)array, &index);
    if(index == CHNLIB_UIPArray_INDEX_NOTFOUND){
        return True;
    }
    
    //参照先の所有を放棄
    release((*array)->tag[index].pointer);
    
    for(i = index; i < (*array)->using_tags - 1; i++){
        (*array)->tag[i] = (*array)->tag[i + 1];
    }
    for(;;){
        if((*array)->next != NULL){
            if((*array)->using_tags != CHNLIB_UIPArray_INTERNAL_PACKDATAS || (*array)->next->using_tags == 0){
                //タグ数とリンクの関係があわない。以下の状態になっている。
                //-次のパックがあるのに、間のパックが使い切られていない。
                //-次のパックがあるのに、次のパックに一つもデータがない。
                CHNLIB_ReportError("Internal data error.", CHNLIB_DEBUG_ARGUMENTS);
            }
            (*array)->tag[CHNLIB_UIPArray_INTERNAL_PACKDATAS - 1] = (*array)->next->tag[0];
            array = &(*array)->next;
            for(i = 0; i < (*array)->using_tags - 1; i++){
                (*array)->tag[i] = (*array)->tag[i + 1];
            }
        } else{
            (*array)->using_tags--;
            if((*array)->using_tags == 0){
                //もうこのパックにはデータが存在しないので、パックを削除。
                CHNLIB_System_FreeMemory(*array, CHNLIB_DEBUG_ARGUMENTS);
                *array = NULL;
            }
            break;
        }
    }
    return False;
}

void CHNLIB_UIPArray_FreeOnlyArray(CHNLIB_UIPArray **array)
{
    //ArrayそのものをFreeする。
    //Pointerの参照先については関知しない。
    CHNLIB_UIPArray *now, *next;
    
    now = *array;
    *array = NULL;
    
    for(; now != NULL; ){
        next = now->next;
        //free(now);
        CHNLIB_System_FreeMemory(now, CHNLIB_DEBUG_ARGUMENTS);
        now = next;
    }
    return;
}

void CHNLIB_UIPArray_FreeOnlyAllPointer(CHNLIB_UIPArray *array)
{
    //Pointerの参照先をすべてFree.
    //Array自体はFreeしない。
    int i;
    
    for(; array != NULL; array = array->next){
        for(i = 0; i < array->using_tags; i++){
            if(array->tag[i].pointer != NULL){
                //free(array->tag[i].pointer);
                CHNLIB_System_FreeMemory(array->tag[i].pointer, CHNLIB_DEBUG_ARGUMENTS);
                array->tag[i].pointer = NULL;
            }
        }
    }
    return;
}

void CHNLIB_UIPArray_FreeOnlySelectedPointer(CHNLIB_UIPArray *array)
{
    //Pointerの参照先で、data32==FalseのものをFree.
    //Array自体はFreeしない。
    int i;
    
    for(; array != NULL; array = array->next){
        for(i = 0; i < array->using_tags; i++){
            if(array->tag[i].pointer != NULL && !array->tag[i].data32){
                //free(array->tag[i].pointer);
                CHNLIB_System_FreeMemory(array->tag[i].pointer, CHNLIB_DEBUG_ARGUMENTS);
                array->tag[i].pointer = NULL;
            }
        }
    }
    return;
}

void CHNLIB_UIPArray_FreeAll(CHNLIB_UIPArray **array)
{
    //Array及びすべてのPointerをFree.
    CHNLIB_UIPArray_FreeOnlyAllPointer(*array);
    CHNLIB_UIPArray_FreeOnlyArray(array);
    return;
}

void CHNLIB_UIPArray_FreeSelectedAll(CHNLIB_UIPArray **array)
{
    //Array及びdata32==FalseのPointerをFree.
    CHNLIB_UIPArray_FreeOnlySelectedPointer(*array);
    CHNLIB_UIPArray_FreeOnlyArray(array);
    return;
}

int CHNLIB_UIPArray_GetNumberOfDatas(const CHNLIB_UIPArray *array)
{
    //Arrayで現在使用中のデータの個数を返す。
    int tags;
    
    tags = 0;
    
    for(; array != NULL; array = array->next){
        tags += array->using_tags;
    }
    return tags;
}

uint CHNLIB_UIPArray_GetData32ByIndex(const CHNLIB_UIPArray *array, int index)
{
    //index番目のdata32を返す。
    //無効なindex及びArrayを指定した場合は、0を返す。
    array = *CHNLIB_UIPArray_Internal_CheckIndex(&array, &index);
    if(index == CHNLIB_UIPArray_INDEX_NOTFOUND){
        return 0;
    }
    return array->tag[index].data32;
}

uint CHNLIB_UIPArray_SetData32ByIndex(CHNLIB_UIPArray *array, int index, uint data32)
{
    //変更前のdata32が返される。
    uint olddata;

    array = (CHNLIB_UIPArray *)*CHNLIB_UIPArray_Internal_CheckIndex((const CHNLIB_UIPArray **)&array, &index);
    if(index == CHNLIB_UIPArray_INDEX_NOTFOUND){
        return 0;
    }

    olddata = array->tag[index].data32;
    array->tag[index].data32 = data32;
    return olddata;
}

void *CHNLIB_UIPArray_GetPointerByIndex(const CHNLIB_UIPArray *array, int index)
{
    //retv:array[index]->pointer
    
    array = *CHNLIB_UIPArray_Internal_CheckIndex(&array, &index);
    if(index == CHNLIB_UIPArray_INDEX_NOTFOUND){
        return NULL;
    }
    
    return array->tag[index].pointer;
}

void *CHNLIB_UIPArray_GetPointerByData32(const CHNLIB_UIPArray *array, uint data32)
{
    //指定されたdata32を持つindexのデータのPointerを返す。
    return CHNLIB_UIPArray_GetPointerByIndex(array, CHNLIB_UIPArray_GetIndexByData32(array, data32));
}

int CHNLIB_UIPArray_GetIndexByData32(const CHNLIB_UIPArray *array, uint data32)
{
    //指定されたdata32を持つ最小のIndexを返す。
    int i, i_max;
    
    i_max = CHNLIB_UIPArray_GetNumberOfDatas(array);
    for(i = 0; i < i_max; i++){
        if(CHNLIB_UIPArray_GetData32ByIndex(array, i) == data32){
            return i;
        }
    }
    return CHNLIB_UIPArray_INDEX_NOTFOUND;
}

CHNLIB_UIPArray *CHNLIB_UIPArray_CopyArray(const CHNLIB_UIPArray *source)
{
    //sourceと等価な配列を新たに生成して返す。
    CHNLIB_UIPArray *destination;
    int i, i_max;
    
    destination = CHNLIB_UIPArray_Initialize();
    i_max = CHNLIB_UIPArray_GetNumberOfDatas(source);
    
    for(i = 0; i < i_max; i++){
        CHNLIB_UIPArray_AppendLast(&destination, CHNLIB_UIPArray_GetData32ByIndex(source, i), CHNLIB_UIPArray_GetPointerByIndex(source, i));
    }
    
    return destination;
}

CHNLIB_UIPArray *CHNLIB_UIPArray_GetSortedInDescendingOrderByData32(const CHNLIB_UIPArray *array)
{
    //タグをData32の値で降順（大きい方から小さい方）になるように整列した新たなArrayを返す。
    CHNLIB_UIPArray *sorted, *temp;
    int i, i_max;
    int candidateIndex, candidateData32, currentData32;
    
    sorted = CHNLIB_UIPArray_Initialize();
    temp = CHNLIB_UIPArray_CopyArray(array);
    i_max = CHNLIB_UIPArray_GetNumberOfDatas(temp);
    
    for(i = 0; i < i_max; i++){
        //tempのData32に1を足す（0を追加済みとマークするため）。
        CHNLIB_UIPArray_SetData32ByIndex(temp, i, CHNLIB_UIPArray_GetData32ByIndex(temp, i) + 1);
    }
    
    for(;;){
        candidateData32 = 0;
        for(i = 0; i < i_max; i++){
            currentData32 = CHNLIB_UIPArray_GetData32ByIndex(temp, i);
            if(candidateData32 < currentData32){
                candidateData32 = currentData32;
                candidateIndex = i;
            }
        }
        if(candidateData32 == 0){
            break;
        }
        for(i = candidateIndex; i < i_max; i++){
            currentData32 = CHNLIB_UIPArray_GetData32ByIndex(temp, i);
            if(candidateData32 == currentData32){
                //-1を忘れないように
                CHNLIB_UIPArray_AppendLast(&sorted, currentData32 - 1, CHNLIB_UIPArray_GetPointerByIndex(temp, i));
                CHNLIB_UIPArray_SetData32ByIndex(temp, i, 0);
            }
        }
    }
    
    release(temp);
    
    return sorted;
}

//
//Internal functions
//
CHNLIB_UIPArray *CHNLIB_UIPArray_Internal_Allocate(void)
{
    //Arrayの実体を確保する。
    //このソース以外から呼び出してはならない。
    CHNLIB_UIPArray *tag;
    
    tag = (CHNLIB_UIPArray *)CHNLIB_System_AllocateMemory_Strict(sizeof(CHNLIB_UIPArray), CHNLIB_DEBUG_ARGUMENTS);
    
    CHNLIB_StructureHeader_Initialize(&tag->header, CHNLIB_STRUCT_ID_UIPArray);
    tag->header.destructor = &CHNLIB_UIPArray_Internal_Destruct;
    tag->header.getHash = &CHNLIB_UIPArray_Internal_GetHash;
    
    return tag;

}

const CHNLIB_UIPArray **CHNLIB_UIPArray_Internal_CheckIndex(const CHNLIB_UIPArray **array, int *index)
{
    //*indexの指す要素の含まれるパックを指すポインタへのアドレスを返し、*indexにはそのパック内での添字を格納する。
    //*indexが配列の範囲外だった場合、*indexにCHNLIB_UIPArray_INDEX_NOTFOUNDを格納する。
    //内部関数なので、arrayおよびindexがNULLでないことを仮定する。
    
    if((*index) < 0){
        //添字がマイナスの要素は存在しない
        (*index) = CHNLIB_UIPArray_INDEX_NOTFOUND;
        return array;
    }
    if(*array == NULL){
        //次のPackが存在しない
        (*index) = CHNLIB_UIPArray_INDEX_NOTFOUND;
        return array;
    }
    for(; ((*index) / CHNLIB_UIPArray_INTERNAL_PACKDATAS) > 0; (*index) -= CHNLIB_UIPArray_INTERNAL_PACKDATAS){
        array = (const CHNLIB_UIPArray **)&(*array)->next;
        if(*array == NULL){
            //次のPackが存在しない
            (*index) = CHNLIB_UIPArray_INDEX_NOTFOUND;
            return array;
        }
    }

    if((*index) >= (*array)->using_tags){
        //詳細に見て、一つのパックの中で、配列の範囲外
        (*index) = CHNLIB_UIPArray_INDEX_NOTFOUND;
        return array;
    }
    return array;
}

void CHNLIB_UIPArray_Internal_Destruct(void **structure)
{
    //デストラクタ（実際にRelease->freeされる時に呼ばれる）
    //方針:すべてのオブジェクトをReleaseした後、すべてのパックをFreeする。
    int i;
    CHNLIB_UIPArray *array;
    
    if(structure == NULL){
        return;
    }
    if(CHNLIB_StructureHeader_GetTypeID(*structure) != CHNLIB_STRUCT_ID_UIPArray){
        return;
    }
    
#ifdef DEBUG_MEMORY_REFERENCE_COUNT
    CHNLIB_Debug("Release(with free)[%p].", CHNLIB_DEBUG_ARGUMENTS, *structure);
#endif
    
    array = *structure;
    
    for(; array != NULL; array = array->next){
        for(i = 0; i < array->using_tags; i++){
            if(array->tag[i].pointer != NULL){
                CHNLIB_Release(&array->tag[i].pointer);
            }
        }
    }

    CHNLIB_UIPArray_FreeOnlyArray((CHNLIB_UIPArray **)structure);

    return;
}

uint CHNLIB_UIPArray_Internal_GetHash(const void *structure)
{
    const CHNLIB_UIPArray *array;
    int i, i_max;
    uint hash;
    
    if(CHNLIB_StructureHeader_GetTypeID(structure) != CHNLIB_STRUCT_ID_UIPArray){
        return 0;
    }
    
    hash = 0;
    array = (const CHNLIB_UIPArray *)structure;
    i_max = CHNLIB_UIPArray_GetNumberOfDatas(array);
    for(i = 0; i < i_max; i++){
        hash += CHNLIB_StructureHeader_GetHash(CHNLIB_UIPArray_GetPointerByIndex(array, i));
    }
    
    return hash;
}


