/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_list.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <string.h>
#include <sc_os.h>
#include <sc_error.h>
#include <sc_mmgr.h>
#include <sc_list.h>


/* ---------------------------------------------------------------------
 *  プロトタイプ宣言
 * ---------------------------------------------------------------------
 */
static void* SC_List_add   (SC_List*, int index, const void* obj, size_t size);
static char* SC_List_addStr(SC_List* list, int index, const char* obj);
static void* SC_List_get   (SC_List*, int index, size_t* size);
static char* SC_List_getStr(SC_List*, int index);
static void  SC_List_remove(SC_List*, int index);
static void  SC_List_clear (SC_List*);
static SC_List_Entry* SC_List_search(SC_List* list, int index);
static SC_List_Entry* SC_List_newEntry(const void* obj, size_t size);


SC_List* SC_List_new(void)
{
	SC_List* list;
	size_t   size;

	size = sizeof(SC_List) + (sizeof(SC_List_Entry) * 2);
	list = (SC_List*) malloc(size);
	if (list == NULL)
	{	/* メモリ確保失敗	*/
		return NULL;
	}
	list->_head = (SC_List_Entry*) (list + 1);
	list->_tail = list->_head + 1;

	/* 初期設定	*/
	list->size = 0;
	list->_head->next  = list->_head->prev  = list->_tail;
	list->_tail->next  = list->_tail->prev  = list->_head;
	list->_head->value = list->_tail->value = NULL;

	list->add          = SC_List_add;
	list->addStr       = SC_List_addStr;
	list->get          = SC_List_get;
	list->getStr       = SC_List_getStr;
	list->remove       = SC_List_remove;
	list->clear        = SC_List_clear;

	return list;
}


/**
 * リストを破棄します。
 * [注意] 参照していた値も破棄します。
 *
 * @param list リスト
 */
void SC_List_delete(SC_List* list)
{
	list->clear(list);
	free(list);
}


/**
 * リストの指定された index 位置にオブジェクトを挿入します。
 * オブジェクトはコピーされ、リストに保持されます。
 * 不正なインデックスが指定された場合、エラーコード SC_EINVAL が
 * メモリの確保に失敗した場合、エラーコード SC_ENOMEM がセットされ、
 * NULL を返します。
 *
 * @param list  リスト
 * @param index インデックス
 * @param obj   挿入するオブジェクト
 * @param size  オブジェクトのサイズ
 * @return 挿入したオブジェクトのコピー
 */
static
void* SC_List_add(SC_List* list, int index, const void* obj, size_t size)
{
	SC_List_Entry* entry;
	SC_List_Entry* insertEntry;

	/* 挿入位置取得		*/
	insertEntry = SC_List_search(list, index);
	if (insertEntry == NULL)
	{	/* 挿入位置不正		*/
		return NULL;
	}

	/* エントリ作成		*/
	entry = SC_List_newEntry(obj, size);
	if (entry == NULL)
	{	/* メモリ確保失敗	*/
		return NULL;
	}

	/* リストに追加 (insertEntry より１つ前に追加)	*/
	list->size++;
	entry->next             = insertEntry;
	entry->prev             = insertEntry->prev;
	insertEntry->prev->next = entry;
	insertEntry->prev       = entry;
	return entry->value;
}


/**
 * リストの指定された index 位置に文字列を挿入します。
 * 文字列はコピーされ、リストに保持されます。
 * 不正なインデックスが指定された場合、エラーコード SC_EINVAL が
 * メモリの確保に失敗した場合、エラーコード SC_ENOMEM がセットされ、
 * NULL を返します。
 *
 * @param list  リスト
 * @param index インデックス
 * @param obj   挿入する文字列
 * @return 挿入した文字列のコピー
 */
static
char* SC_List_addStr(SC_List* list, int index, const char* obj)
{
	return (char*) SC_List_add(list, index, obj, strlen(obj) + 1);
}


/**
 * 指定されたインデックス位置にある値を取得します。
 * size には、取得された値のサイズが格納されます。
 * (値のサイズが不要な場合、NULL を指定することが可能です。)
 * 不正なインデックスが指定された場合、エラーコード
 * SC_EINVAL がセットされ、NULL が返されます。
 *
 * @param list  リスト
 * @param index インデックス
 * @param size  値のサイズが格納される
 * @return 値
 */
static
void* SC_List_get(SC_List* list, int index, size_t* size)
{
	SC_List_Entry* entry;
	entry = SC_List_search(list, index);
	if ((entry == NULL) || (entry == list->_tail))
	{	/* インデックス不正	*/
		SC_setError(SC_EINVAL);
		return NULL;
	}

	if (size != NULL)
	{	/* サイズ格納		*/
		*size = entry->size;
	}
	return entry->value;
}


/**
 * 指定されたインデックス位置にある文字列を取得します。
 * size には、取得された値のサイズが格納されます。
 * (値のサイズが不要な場合、NULL を指定することが可能です。)
 * 不正なインデックスが指定された場合、エラーコード
 * SC_EINVAL がセットされ、NULL が返されます。
 *
 * @param list  リスト
 * @param index インデックス
 * @param size  値のサイズが格納される
 * @return 文字列
 */
static
char* SC_List_getStr(SC_List* list, int index)
{
	return (char*) SC_List_get(list, index, NULL);
}


/**
 * 指定されたインデックス位置にある値を削除します。
 * [注意] 参照していた値も削除されます。
 *
 * @param list  リスト
 * @param index インデックス
 */
static
void SC_List_remove(SC_List* list, int index)
{
	SC_List_Entry* entry;
	entry = SC_List_search(list, index);
	if ((entry == NULL) || (entry == list->_tail))
	{	/* インデックス不正	*/
		return;
	}
	entry->prev->next = entry->next;
	entry->next->prev = entry->prev;
	free(entry);
	list->size--;
}


/**
 * リストをクリアします。
 * [注意] 参照していた値も削除されます。
 *
 * @param list リスト
 */
static void SC_List_clear(SC_List* list)
{
	SC_List_Entry* entry;
	entry = list->_head->next;
	while (entry != list->_tail)
	{
		entry = entry->next;
		free(entry->prev);
	}
	list->size         = 0;
	list->_head->next  = list->_head->prev  = list->_tail;
	list->_tail->next  = list->_tail->prev  = list->_head;
	list->_head->value = list->_tail->value = NULL;
}


/**
 * リストより指定されたインデックスのエントリを取得します。
 * インデックスが -1 または、現在の size 位置の場合、
 * list->tail を返します。
 * インデックス位置が不正な場合、エラーコード SC_EINVAL が
 * セットされ、NULL を返します。
 *
 * @param list  リスト
 * @param index インデックス
 * @return エントリ
 */
static
SC_List_Entry* SC_List_search(SC_List* list, int index)
{
	SC_List_Entry* tmpEntry;
	int            i;
	int            halfSize;

	if (index > (int) list->size)
	{	/* インデックス位置不正	*/
		SC_setError(SC_EINVAL);
		return NULL;
	}

	if ((index == -1) || (index == (int) list->size))
	{	/* 最後尾	*/
		return list->_tail;
	}

	halfSize = (int) list->size / 2;
	if (index > halfSize)
	{	/* 前方より検索	*/
		tmpEntry = list->_head;
		for (i = 0; i <= index; i++)
		{
			tmpEntry = tmpEntry->next;
		}
	}
	else
	{	/* 後方より検索	*/
		tmpEntry = list->_tail;
		for (i = list->size; i > index; i--)
		{
			tmpEntry = tmpEntry->prev;
		}
	}
	return tmpEntry;
}


/**
 * 新たなエントリを生成します。
 * 生成に失敗した場合、エラーコード SC_ENOMEM が
 * セットされ、NULL が返されます。
 *
 * @param obj  オブジェクト
 * @param size サイズ
 * @return リストエントリ
 */
static
SC_List_Entry* SC_List_newEntry(const void* obj, size_t size)
{
	size_t         newSize = size + sizeof(SC_List_Entry);
	SC_List_Entry* entry   = (SC_List_Entry*) malloc (newSize);
	if (entry == NULL)
	{	/* メモリ確保失敗	*/
		SC_setError(SC_ENOMEM);
		return NULL;
	}
	entry->value = (entry + 1);
	entry->size  = size;
	memcpy(entry->value, obj, size);
	return entry;
}

