/*
    Copyright (c) 2007 uchan
*/

#include "../bootpack.h"

/*internal*/ class Header
{
public:
    int Size;
    dword Prev, Next;

    inline static void WriteUsed(dword ptr, dword size)
    {
        *(dword*)ptr = *(dword*)(ptr + size - sizeof(dword)) = size;
    }

    inline static void WriteFree(dword ptr, dword size, dword prev, dword next)
    {
        dword* p = (dword*)ptr;
        *(p++) = *(dword*)(ptr + size - sizeof(dword)) = size | 0x80000000;
        *(p++) = prev;
        *p = next;
    }
    
    inline static void WriteLast(dword ptr, dword prev)
    {
        dword* p = (dword*)ptr;
        *(p++) = 0;
        *p = prev;
    }
};

Memory::Memory()
{
}

void Memory::init(dword start, dword end)
{
    this->start = this->firstFree = start;
    this->end = end;
    this->used = 0;
    Header::WriteLast(this->start, 0);
}

Memory::~Memory()
{
}

void* Memory::allocate(dword size)
{
    dword sz = (sizeof(dword) + size + sizeof(dword) + 15) & 0xfffffff0; // align16
    dword ptr = this->searchFit(sz);
    if (ptr == 0 || ptr + sz > this->end) return NULL;
    this->used += sz;

    Header* h = (Header*)ptr;
    dword sz_old = h->Size & 0x7fffffff;
    dword nptr = ptr + sz, prev = h->Prev, next = nptr;
    if (sz_old == 0)
    {
        Header::WriteLast(nptr, prev);
    }
    else
    {
        if (sz < sz_old)
        {
            Header::WriteFree(nptr, sz_old - sz, prev, h->Next);
            ((Header*)h->Next)->Prev = nptr;
        }
        else
        {
            next = h->Next;
            ((Header*)next)->Prev = prev;
        }
    }
    Header::WriteUsed(ptr, sz);
    this->setNext(prev, next);
    return (void*)(ptr + sizeof(dword));
}

dword Memory::searchFit(dword size)
{
    for (dword p = this->firstFree; p <= this->end; p = ((Header*)p)->Next)
    {
        int sz = ((Header*)p)->Size;
        if (sz == 0)
        {
            return p;
        }
        else if (sz > 0)
        {
            break;
        }
        sz &= 0x7fffffff;
        if (size <= (dword)sz) return p;
    }
    return 0;
}

void Memory::free(void* address)
{
    dword ptr = ((dword)address) - sizeof(dword), prev = 0;
    if (!this->checkAddress(ptr, &prev))
    {
        return;
    }

    dword sz = *(int*)ptr, next = this->getNext(prev);
    this->used -= sz;
    if (ptr > this->start)
    {
        int psz = *(int*)(ptr - sizeof(int));
        if (psz < 0)
        {
            psz &= 0x7fffffff;
            ptr -= psz;
            sz  += psz;
            prev = ((Header*)ptr)->Prev;
        }
    }
    dword nptr = ptr + sz, nsz = *(dword*)nptr;
    if (nsz < 0)
    {
        sz += nsz & 0x7fffffff;
        next = ((Header*)nptr)->Next;
    }

    if (nsz == 0)
    {
        Header::WriteLast(ptr, prev);
    }
    else
    {
        Header::WriteFree(ptr, sz, prev, next);
        ((Header*)next)->Prev = ptr;
    }
    this->setNext(prev, ptr);
}

bool Memory::checkAddress(dword ptr, dword* prev)
{
    if (ptr < this->start || ptr > this->end) return false;

    int psz1 = *(int*)ptr;
    if (psz1 < 16 || (psz1 & 15) != 0 || ptr + psz1 > this->end) return false;

    int psz2 = *(int*)(ptr + psz1 - sizeof(int));
    if (psz1 != psz2) return false;

    for (dword p = this->firstFree; p <= ptr; p = ((Header*)p)->Next)
    {
        if (((Header*)p)->Size >= 0) return false;
        *prev = p;
    }
    return true;
}

void Memory::setNext(dword ptr, dword next)
{
    if (ptr == 0)
    {
        this->firstFree = next;
    }
    else
    {
        ((Header*)ptr)->Next = next;
    }
}

dword Memory::getNext(dword ptr)
{
    return ptr == 0 ? this->firstFree : ((Header*)ptr)->Next;
}

dword Memory::getMemorySize(dword start, dword end)
{
	const int EFLAGS_AC_BIT     = 0x00040000;
	const int CR0_CACHE_DISABLE = 0x60000000;
	bool flag486 = false;
	dword i;

	// 386か486以降かテスト
	eflags |= EFLAGS_AC_BIT; // AC-BITを1にする
	if(eflags & EFLAGS_AC_BIT) // 386ならAC-BITは自動で0になる。
	{
		flag486 = true;
	}
	eflags &= ~EFLAGS_AC_BIT; // AC-BITを0にする。

	if(flag486)
	{
		cr0 |= CR0_CACHE_DISABLE; // キャッシュ禁止
	}

	// メモリーテスト
	i = getMemorySizeSub(start, end);

	if(flag486)
	{
		cr0 &= ~CR0_CACHE_DISABLE; // キャッシュ許可
	}

	return i;
}

dword Memory::getMemorySizeSub(dword start, dword end)
{
	unsigned int i, old, check_size;
	volatile unsigned int *p; // volatileを指定しないと、メモリテストが正常に出来ない
	const unsigned int pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;

	i = start;
	// 16MB,32KB,256Bの順で調べていく。
	for(check_size = 0x01000000; check_size > 255; check_size /= 0x100)
	{
		for(; i <= end; i += check_size)
		{
			p = (unsigned int *)(i + check_size - 4);
			old = *p;
			*p = pat0;			// 試しに書く
			*p ^= 0xffffffff;	// 反転させる
			if(*p != pat1)
			{
				*p = old;
				break;
			}
			*p ^= 0xffffffff;	// もう一度反転
			if(*p != pat0)
			{
				*p = old;
				break;
			}
			*p = old;
		}
	}
	return i;
}

dword Memory::getFreeSize()
{
	return this->end - this->start - this->used;
}

void *memcpy(void *s, const void *ct, size_t n)
{
	void *tmp = s;
	char *dp = (char*)s;
	char *sp = (char*)ct;

	while (n--)
		*dp++ = *sp++;

	return tmp;
}


/*
 * malloc, free, new, deleteのサポート
 */

void* malloc(size_t size) {
	void* p = g_km.allocate(size);
	// ゼロクリアしない
	return p;
}

void* calloc(size_t size) {
	void* p = g_km.allocate(size);
	if (p != 0) {
		memset(p, 0, size);
	}
	return p;
}

void free(void* p) {
	if (p != 0) {
		g_km.free(p);
	}
}

void* operator new(size_t size) {
	void* p = g_km.allocate(size);
	if (p != 0) {
		memset(p, 0, size);
	}
	return p;
}

void* operator new[](size_t size) {
	void* p = g_km.allocate(size);
	if (p != 0) {
		memset(p, 0, size);
	}
	return p;
}

void operator delete(void* p)
{
	if (p != 0) {
		g_km.free(p);
	}
}

void operator delete[](void* p)
{
	if (p != 0) {
		g_km.free(p);
	}
}

