/*$
mlk in apdtool
Copyright (c) 2020 Azel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
$*/

/*****************************************
 * mList (双方向リスト)
 *****************************************/

#include "mlk.h"
#include "mlk_list.h"



/**@ アイテムを確保
 *
 * @d:サイズ分ゼロクリアされる
 * 
 * @p:size 構造体の全サイズ。\
 * mListItem + データのサイズを確保する。\
 * mListItem のサイズ以下の場合は、自動で mListItem のサイズ。 */

mListItem *mListItemNew(int size)
{
	mListItem *p;

	if(size < sizeof(mListItem))
		size = sizeof(mListItem);
	
	p = (mListItem *)mMalloc0(size);
	if(!p) return NULL;
	
	return p;
}

/**@ mList 初期化 */

void mListInit(mList *list)
{
	mMemset0(list, sizeof(mList));
}


//====================
// 追加/取り外し
//====================


/**@ アイテムを新規作成して末尾に追加
 * 
 * @p:size アイテム構造体のサイズ */

mListItem *mListAppendNew(mList *list,int size)
{
	mListItem *pi;
	
	pi = mListItemNew(size);

	mListAppendItem(list, pi);

	return pi;
}

/**@ アイテムを新規作成して挿入
 *
 * @p:pos 挿入位置。このアイテムの前に挿入される。 */

mListItem *mListInsertNew(mList *list,mListItem *pos,int size)
{
	mListItem *pi;
	
	pi = mListItemNew(size);

	mListInsertItem(list, pos, pi);

	return pi;
}

/**@ アイテムをリストの末尾に追加 */

void mListAppendItem(mList *list,mListItem *item)
{
	if(item)
	{
		mListLinkAppend(list, item);
		list->num++;
	}
}

/**@ アイテムをリストに挿入 */

void mListInsertItem(mList *list,mListItem *pos,mListItem *item)
{
	if(item)
	{
		mListLinkInsert(list, item, pos);
		list->num++;
	}
}

/**@ アイテムを削除せずにリストから取り外す
 *
 * @d:mList::num の値も変化する。 */

void mListRemoveItem(mList *list,mListItem *item)
{
	if(item)
	{
		mListLinkRemove(list, item);
		list->num--;
	}
}


//=========================


/**@ リスト全体を複製
 *
 * @d:※各アイテムは固定サイズであること。
 *
 * @p:itemsize 各アイテムのサイズ
 * @r:メモリが足りなかった場合 FALSE */

mlkbool mListDup(mList *dst,mList *src,int itemsize)
{
	mListItem *ps,*pd;

	if(itemsize < sizeof(mListItem))
		itemsize = sizeof(mListItem);

	for(ps = src->top; ps; ps = ps->next)
	{
		pd = (mListItem *)mMemdup(ps, itemsize);
		if(!pd)
		{
			mListDeleteAll(dst);
			return FALSE;
		}

		mListLinkAppend(dst, pd);
	}

	dst->item_destroy = src->item_destroy;
	dst->num = src->num;

	return TRUE;
}


//========================
// 削除
//========================


/**@ アイテムを全て削除 */

void mListDeleteAll(mList *list)
{
	mListItem *pi,*next;
	void (*destroy)(mList *,mListItem *) = list->item_destroy;
	
	for(pi = list->top; pi; pi = next)
	{
		next = pi->next;
		
		if(destroy)
			(destroy)(list, pi);
		
		mFree(pi);
	}
	
	list->top = list->bottom = NULL;
	list->num = 0;
}

/**@ リストからアイテムを削除
 *
 * @p:item NULL の場合は何もしない */

void mListDelete(mList *list,mListItem *item)
{
	if(item)
	{
		if(list->item_destroy)
			(list->item_destroy)(list, item);

		mListLinkRemove(list, item);
		
		mFree(item);
		
		list->num--;
	}
}

/**@ リストからアイテムを削除
 * 
 * @d:item_destroy ハンドラは呼ばない。 */

void mListDelete_no_handler(mList *list,mListItem *item)
{
	mListLinkRemove(list, item);
	
	mFree(item);
	
	list->num--;
}

/**@ インデックス位置からアイテム削除
 * 
 * @r:FALSE で位置が範囲外 */

mlkbool mListDelete_index(mList *list,int index)
{
	mListItem *p = mListGetItemAtIndex(list, index);
	
	if(!p)
		return FALSE;
	else
	{
		mListDelete(list, p);
		return TRUE;
	}
}

/**@ 先頭から指定個数を削除 */

void mListDelete_tops(mList *list,int num)
{
	mListItem *p,*next;

	for(p = list->top; p && num > 0; num--, p = next)
	{
		next = p->next;

		mListDelete(list, p);
	}
}

/**@ 終端から指定個数を削除 */

void mListDelete_bottoms(mList *list,int num)
{
	mListItem *p,*next;

	for(p = list->bottom; p && num > 0; num--, p = next)
	{
		next = p->prev;

		mListDelete(list, p);
	}
}


//========================
// アイテム位置移動
//========================


/**@ アイテムの位置を移動
 *
 * @p:src 移動元アイテム
 * @p:dst 移動先。dst の前に挿入される。NULL で最後尾へ。
 * @r:FALSE で位置が変わらなかった */

mlkbool mListMove(mList *list,mListItem *src,mListItem *dst)
{
	if(src == dst) return FALSE;
	
	if(!dst && list->bottom == src) return FALSE;
	
	mListLinkRemove(list, src);
	mListLinkInsert(list, src, dst);

	return TRUE;
}

/**@ アイテムを先頭へ移動
 *
 * @r:FALSE ですでに先頭 */

mlkbool mListMoveToTop(mList *list,mListItem *item)
{
	if(list->top == item)
		return FALSE;
	else
	{
		mListMove(list, item, list->top);
		return TRUE;
	}
}

/**@ アイテムを上下に移動
 *
 * @p:up TRUE で上へ、FALSE で下へ
 * @r:移動したか */

mlkbool mListMoveUpDown(mList *list,mListItem *item,mlkbool up)
{
	if(!item) return FALSE;
	
	if(up)
	{
		if(item->prev)
		{
			mListMove(list, item, item->prev);
			return TRUE;
		}
	}
	else
	{
		if(item->next)
		{
			mListMove(list, item->next, item);
			return TRUE;
		}
	}

	return FALSE;
}

/**@ アイテムの位置を入れ替える */

void mListSwapItem(mList *list,mListItem *item1,mListItem *item2)
{
	mListItem *p;

	if(item1 != item2)
	{
		p = item1->next;
		
		mListMove(list, item1, item2->next);
		mListMove(list, item2, p);
	}
}

/**@ ソート
 *
 * @p:comp 比較関数。\
 * item1 == item2 で 0、item1 < item2 で負の値、item1 > item2 で正の値を返す。
 * @p:param 比較関数に渡すパラメータ */

void mListSort(mList *list,int (*comp)(mListItem *,mListItem *,void *),void *param)
{
	mList res,tmp;
	mListItem *pi,*pi_r,*pinext;
	int i,j,lcnt,rcnt;

	//マージソート

	res = *list;

	for(i = 1; 1; i <<= 1)
	{
		tmp.top = tmp.bottom = NULL;
		pi = res.top;

		//各ブロックをソートして tmp に追加

		for(j = 0; pi; j++, pi = pi_r)
		{
			//2分割の右側の位置と左の個数

			lcnt = 0;
			rcnt = i;
			
			for(pi_r = pi; lcnt < i && pi_r; lcnt++, pi_r = pi_r->next);

			//左側・右側の先頭アイテムを比較。
			//小さい方を取り出して tmp に追加

			while(lcnt || rcnt)
			{
				if(lcnt && (rcnt == 0 || !pi_r || (comp)(pi, pi_r, param) <= 0))
				{
					//左側
					
					pinext = pi->next;
					mListLinkRemove(&res, pi);
					mListLinkAppend(&tmp, pi);
					pi = pinext;

					if(!pi) break;
					
					lcnt--;
				}
				else
				{
					//右側
					
					if(!pi_r) break;

					pinext = pi_r->next;
					mListLinkRemove(&res, pi_r);
					mListLinkAppend(&tmp, pi_r);
					pi_r = pinext;
					
					rcnt--;
				}
			}
		}

		res = tmp;

		//結合が1回しか行われなかった場合、終了
		if(j == 1) break;
	}

	list->top = res.top;
	list->bottom = res.bottom;
}


//========================
// ほか
//========================


/**@ インデックス位置からアイテム取得 */

mListItem *mListGetItemAtIndex(mList *list,int index)
{
	mListItem *p;
	
	for(p = list->top; p && index > 0; p = p->next, index--);
	
	return p;
}

/**@ 2つのアイテムの位置関係を得る
 *
 * @r:0 で item1 == item2。\
 * 1 で item1 は item2 より後。\
 * -1 で item1 は item2 より前 */

int mListItemGetDir(mListItem *item1,mListItem *item2)
{
	mListItem *prev,*next;
	
	if(item1 == item2) return 0;

	prev = item1->prev;
	next = item1->next;

	while(prev && next)
	{
		if(prev == item2) return 1;
		if(next == item2) return -1;
	
		prev = prev->prev;
		next = next->next;
	}

	//どちらかが NULL になった
	return (!next)? 1: -1;
}

/**@ アイテムのインデックス位置取得 */

int mListItemGetIndex(mListItem *item)
{
	int pos = -1;

	for(; item; item = item->prev, pos++);
	
	return pos;
}


//================================
// リンク操作
//================================


/**@ リストの末尾にリンクを追加
 *
 * @g:リンク操作
 * @d:{em:mList::num の値は操作されない:em} */

void mListLinkAppend(mList *list,mListItem *item)
{
	if(list->bottom)
	{
		item->prev = list->bottom;
		(list->bottom)->next = item;
	}
	else
	{
		item->prev = NULL;
		list->top = item;
	}
	
	item->next = NULL;
	list->bottom = item;
}

/**@ リストの指定位置にリンクを挿入
 * 
 * @p:pos 挿入位置。この前に挿入される。NULL で末尾に追加。 */

void mListLinkInsert(mList *list,mListItem *item,mListItem *pos)
{
	if(!pos)
		mListLinkAppend(list, item);
	else
	{
		if(pos->prev)
			(pos->prev)->next = item;
		else
			list->top = item;
		
		item->prev = pos->prev;
		pos->prev = item;
		item->next = pos;
	}
}

/**@ リストからアイテムのリンクを外す */

void mListLinkRemove(mList *list,mListItem *item)
{
	if(item->prev)
		(item->prev)->next = item->next;
	else
		list->top = item->next;
	
	if(item->next)
		(item->next)->prev = item->prev;
	else
		list->bottom = item->prev;
}


//================================
// キャッシュリスト
//================================


/**@ キャッシュリストにアイテムを新規作成
 *
 * @g:キャッシュリスト
 * @d:キャッシュリスト内では、
 * 参照カウンタ (mListCacheItem::refcnt) が 1 以上のアイテムは常に存在する。\
 * 参照カウンタが 0 のアイテムは、明示的に削除するまで保持される。\
 * \
 * リストのアイテム順は、[top] (NEW) refcnt > 0 (OLD) | (OLD) refcnt == 0 (NEW) [bottom] となる。\
 * \
 * アイテム追加時は、参照カウンタ 1 で、リストの先頭に置かれる。 */

mListCacheItem *mListCache_appendNew(mList *list,int size)
{
	mListCacheItem *pi;

	pi = (mListCacheItem *)mListItemNew(size);
	if(pi)
	{
		//先頭に追加

		mListInsertItem(list, list->top, MLISTITEM(pi));

		pi->refcnt = 1;
	}

	return pi;
}

/**@ キャッシュアイテムの参照カウンタを増やす
 *
 * @d:同時に、アイテムを先頭へ移動させる。 */

void mListCache_refItem(mList *list,mListCacheItem *item)
{
	item->refcnt++;

	mListMoveToTop(list, MLISTITEM(item));
}

/**@ キャッシュリストからアイテムを解放
 *
 * @d:参照カウンタを一つ減らす。\
 * 参照カウンタが 0 になったら末尾へ移動し、そのまま残る。 */

void mListCache_releaseItem(mList *list,mListCacheItem *item)
{
	if(item->refcnt)
	{
		item->refcnt--;

		if(item->refcnt == 0)
			mListMove(list, MLISTITEM(item), NULL);
	}
}

/**@ 参照カウンタが 0 のアイテムを、最大で指定数になるように削除
 *
 * @d:リスト内の参照カウンタ 0 のアイテム (未使用状態のアイテム) が、
 * 全体で maxnum の数になるように、古いものから削除する。 */

void mListCache_deleteUnused(mList *list,int maxnum)
{
	mListCacheItem *pi,*prev;
	int cnt = 0;

	//recnt == 0 のアイテムは、終端に old <--> new の順で並んでいるので、
	//末尾から maxnum 分を保持し、それより前のアイテムは削除

	for(pi = MLISTCACHEITEM(list->bottom); pi && pi->refcnt == 0; pi = prev)
	{
		prev = MLISTCACHEITEM(pi->prev);

		if(cnt < maxnum)
			//保持
			cnt++;
		else
			mListDelete(list, MLISTITEM(pi));
	}
}
