/*
 *  aml_generator.c
 *  Chameleon
 *
 *  Created by Mozodojo on 20/07/10.
 *  Copyright 2010 mozo. All rights reserved.
 *
 * additions and corrections by Slice and pcj, 2012.
 */

#include "AmlGenerator.h"

BOOLEAN aml_add_to_parent(AML_CHUNK* parent, AML_CHUNK* node)
{
	if (parent && node)
	{
		switch (parent->Type) 
		{
			case AML_CHUNK_NONE:
			case AML_CHUNK_BYTE:
			case AML_CHUNK_WORD:
			case AML_CHUNK_DWORD:
			case AML_CHUNK_QWORD:
			case AML_CHUNK_ALIAS:
				MsgLog("aml_add_to_parent: node doesn't support child nodes!\n");
				return FALSE;
			case AML_CHUNK_NAME:
				if (parent->First) 
				{
					MsgLog("aml_add_to_parent: name node supports only one child node!\n");
					return FALSE;
				}
				break;

			default:
				break;
		}
		
		if (!parent->First)
			parent->First = node;
		
		if (parent->Last)
			parent->Last->Next = node;
		
		parent->Last = node;
		
		return TRUE;
	}
	
	return FALSE;
}

AML_CHUNK* aml_create_node(AML_CHUNK* parent)
{
	AML_CHUNK* node = (AML_CHUNK*)AllocateZeroPool(sizeof(AML_CHUNK));
	
	aml_add_to_parent(parent, node);
	
	return node;
}

void aml_destroy_node(AML_CHUNK* node)
{
	// Delete child nodes
	AML_CHUNK* child = node->First;
	
	while (child) 
	{
		AML_CHUNK* next = child->Next;
		
		if (child->Buffer)
			FreePool(child->Buffer);
		
		FreePool(child);
		
		child = next;
	}
	
	// Free node
	if (node->Buffer)
		FreePool(node->Buffer);
	
	FreePool(node);
}

AML_CHUNK* aml_add_buffer(AML_CHUNK* parent, /* CONST*/ CHAR8* buffer, UINT32 size)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node) 
	{
		node->Type = AML_CHUNK_NONE;
		node->Length = (UINT16)size;
		node->Buffer = AllocateZeroPool (node->Length);
		CopyMem(node->Buffer, buffer, node->Length);
	}
	
	return node;
}

AML_CHUNK* aml_add_byte(AML_CHUNK* parent, UINT8 value)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node) 
	{
		node->Type = AML_CHUNK_BYTE;
		
		node->Length = 1;
		node->Buffer = AllocateZeroPool (node->Length);
		node->Buffer[0] = value;
	}
	
	return node;
}

AML_CHUNK* aml_add_word(AML_CHUNK* parent, UINT16 value)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node) 
	{
		node->Type = AML_CHUNK_WORD;
		node->Length = 2;
		node->Buffer = AllocateZeroPool (node->Length);
		node->Buffer[0] = value & 0xff;
		node->Buffer[1] = value >> 8;
	}
	
	return node;
}

AML_CHUNK* aml_add_dword(AML_CHUNK* parent, UINT32 value)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node) 
	{
		node->Type = AML_CHUNK_DWORD;
		node->Length = 4;
		node->Buffer = AllocateZeroPool (node->Length);
		node->Buffer[0] = value & 0xff;
		node->Buffer[1] = (value >> 8) & 0xff;
		node->Buffer[2] = (value >> 16) & 0xff;
		node->Buffer[3] = (value >> 24) & 0xff;
	}
	
	return node;
}

AML_CHUNK* aml_add_qword(AML_CHUNK* parent, UINT64 value)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node) 
	{
		node->Type = AML_CHUNK_QWORD;
		node->Length = 8;
		node->Buffer = AllocateZeroPool (node->Length);
		node->Buffer[0] = value & 0xff;
    node->Buffer[1] = RShiftU64(value, 8) & 0xff;
    node->Buffer[2] = RShiftU64(value, 16) & 0xff;
    node->Buffer[3] = RShiftU64(value, 24) & 0xff;
    node->Buffer[4] = RShiftU64(value, 32) & 0xff;
    node->Buffer[5] = RShiftU64(value, 40) & 0xff;
    node->Buffer[6] = RShiftU64(value, 48) & 0xff;
    node->Buffer[7] = RShiftU64(value, 56) & 0xff;
	}
	
	return node;
}

UINT32 aml_fill_simple_name(CHAR8* buffer, /* CONST*/ CHAR8* name)
{
	if (AsciiStrLen(name) < 4) 
	{
//		MsgLog("aml_fill_simple_name: simple name %a has incorrect lengh! Must be 4.\n", name);
		return 0;
	}
	
	CopyMem(buffer, name, 4);
	return 4;
}

UINT32 aml_fill_name(AML_CHUNK* node, /* CONST*/ CHAR8* name)
{
  INTN len, offset, count;
  UINT32 root = 0;
  
	if (!node) 
		return 0;
	
	len = AsciiStrLen(name);
  offset = 0;
  count = len >> 2;
	
	if ((len % 4) > 1 || count == 0) 
	{
//		MsgLog("aml_fill_name: pathname %a has incorrect length! Must be 4, 8, 12, 16, etc...\n", name);
		return 0;
	}
	
	if (((len % 4) == 1) && (name[0] == '\\'))
		root++;
			
	if (count == 1) 
	{
		node->Length = (UINT16)(4 + root);
		node->Buffer = AllocateZeroPool (node->Length+4);
		CopyMem(node->Buffer, name, 4 + root);
    offset += 4 + root;
		return (UINT32)offset;
	}
	
	if (count == 2) 
	{
		node->Length = 2 + 8;
		node->Buffer = AllocateZeroPool (node->Length+4);
		node->Buffer[offset++] = 0x5c; // Root Char
		node->Buffer[offset++] = 0x2e; // Double name
		CopyMem(node->Buffer+offset, name + root, 8);
    offset += 8;
		return (UINT32)offset;
	}
	
	node->Length = (UINT16)(3 + (count << 2));
	node->Buffer = AllocateZeroPool (node->Length+4);
	node->Buffer[offset++] = 0x5c; // Root Char
	node->Buffer[offset++] = 0x2f; // Multi name
	node->Buffer[offset++] = (CHAR8)count; // Names count
	CopyMem(node->Buffer+offset, name + root, count*4);
	offset += count*4;
	return (UINT32)offset; //node->Length;
}

AML_CHUNK* aml_add_scope(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_SCOPE;
		
		aml_fill_name(node, name);
	}
	
	return node;
}

AML_CHUNK* aml_add_name(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_NAME;
		
		aml_fill_name(node, name);
	}
	
	return node;
}

AML_CHUNK* aml_add_method(AML_CHUNK* parent, /* CONST*/ CHAR8* name, UINT8 args)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
    UINTN offset = aml_fill_name(node, name);
		node->Type = AML_CHUNK_METHOD;
    node->Length++;
    node->Buffer[offset] = args;
//    AML_CHUNK* meth = aml_add_byte(node, args);
//    return meth;
	}
	
	return node;
}


AML_CHUNK* aml_add_package(AML_CHUNK* parent)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_PACKAGE;
		
		node->Length = 1;
		node->Buffer = AllocateZeroPool (node->Length);
	}
	
	return node;
}

AML_CHUNK* aml_add_alias(AML_CHUNK* parent, /* CONST*/ CHAR8* name1, /* CONST*/ CHAR8* name2)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_ALIAS;
		
		node->Length = 8;
		node->Buffer = AllocateZeroPool (node->Length);
		aml_fill_simple_name(node->Buffer, name1);
		aml_fill_simple_name(node->Buffer+4, name2);
	}
	
	return node;
}

AML_CHUNK* aml_add_return_name(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_RETURN;
		aml_fill_name(node, name);
	}
	
	return node;
}

AML_CHUNK* aml_add_return_byte(AML_CHUNK* parent, UINT8 value)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_RETURN;
		aml_add_byte(node, value);
	}
	
	return node;
}

AML_CHUNK* aml_add_device(AML_CHUNK* parent, /* CONST*/ CHAR8* name)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_DEVICE;
		aml_fill_name(node, name);
	}
	
	return node;
}

AML_CHUNK* aml_add_local0(AML_CHUNK* parent)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_LOCAL0;
		node->Length = 1;
	}
	
	return node;
}

AML_CHUNK* aml_add_store(AML_CHUNK* parent)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_STORE_OP;
		node->Length = 1;
	}
	
	return node;
}

AML_CHUNK* aml_add_byte_buffer(AML_CHUNK* parent, /* CONST*/ CHAR8* data, UINT32 size)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
	    INTN offset=0;
		node->Type = AML_CHUNK_BUFFER;
		node->Length = (UINT8)(size + 2);
		node->Buffer = AllocateZeroPool (node->Length);
		node->Buffer[offset++] = AML_CHUNK_BYTE;  //0x0A
		node->Buffer[offset++] = (CHAR8)size;
		CopyMem(node->Buffer+offset, data, node->Length);
	}
	
	return node;
}

AML_CHUNK* aml_add_string_buffer(AML_CHUNK* parent, /* CONST*/ CHAR8* StringBuf)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
	    UINTN offset=0;
	    UINTN len = AsciiStrLen(StringBuf);
		node->Type = AML_CHUNK_BUFFER;
		node->Length = (UINT8)(len + 3);
		node->Buffer = AllocateZeroPool (node->Length);
		node->Buffer[offset++] = AML_CHUNK_BYTE;
		node->Buffer[offset++] = (CHAR8)(len + 1);
		CopyMem(node->Buffer+offset, StringBuf, len);
//		node->Buffer[offset+len] = '\0';  //already zero pool
	}
	
	return node;
}

AML_CHUNK* aml_add_string(AML_CHUNK* parent, /* CONST*/ CHAR8* StringBuf)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
	    INTN len = AsciiStrLen(StringBuf);
		node->Type = AML_CHUNK_STRING;
		node->Length = (UINT8)(len + 1);
		node->Buffer = AllocateZeroPool (len + 1);
		CopyMem(node->Buffer, StringBuf, len);
//		node->Buffer[len] = '\0';
	}
	
	return node;
}

AML_CHUNK* aml_add_return(AML_CHUNK* parent)
{
	AML_CHUNK* node = aml_create_node(parent);
	
	if (node)
	{
		node->Type = AML_CHUNK_RETURN;
		//aml_add_byte(node, value);
	}
	
	return node;
}


UINT8 aml_get_size_length(UINT32 size)
{
	if (size + 1 <= 0x3f)
		return 1;
	else if (size + 2 <= 0xfff) /* Encode in 4 bits and 1 byte */
		return 2;
	else if (size + 3 <= 0xfffff) /* Encode in 4 bits and 2 bytes */
		return 3;
	
	return 4; /* Encode 0xfffffff in 4 bits and 2 bytes */
}

UINT32 aml_calculate_size(AML_CHUNK* node)
{
	if (node)
	{
		// Calculate child nodes size
		AML_CHUNK* child = node->First;
		UINT8 child_count = 0;
		
      node->Size = 0;
		while (child) 
		{
			child_count++;
			
			node->Size += (UINT16)aml_calculate_size(child);
			
			child = child->Next;
		}
		
		switch (node->Type) 
		{
			case AML_CHUNK_NONE:
			case AML_STORE_OP:
      case AML_CHUNK_LOCAL0:
				node->Size += node->Length;
				break;  
            
      case AML_CHUNK_METHOD:  
			case AML_CHUNK_SCOPE:
			case AML_CHUNK_BUFFER: 
				node->Size += 1 + node->Length;
				node->Size += aml_get_size_length(node->Size);
				break;
				
      case AML_CHUNK_DEVICE:
				node->Size += 2 + node->Length;
				node->Size += aml_get_size_length(node->Size);
				break;

			case AML_CHUNK_PACKAGE:
				node->Buffer[0] = child_count;
				node->Size += 1 + node->Length;
				node->Size += aml_get_size_length(node->Size);
				break;
				
			case AML_CHUNK_BYTE:
				if (node->Buffer[0] == 0x0 || node->Buffer[0] == 0x1) 
				{
					node->Size += node->Length;
				}
				else 
				{
					node->Size += 1 + node->Length;
				}
				
				break;
				
			case AML_CHUNK_WORD:
			case AML_CHUNK_DWORD:
			case AML_CHUNK_QWORD:
			case AML_CHUNK_ALIAS:
			case AML_CHUNK_NAME:
      case AML_CHUNK_RETURN:
      case AML_CHUNK_STRING:
				node->Size += 1 + node->Length;
				break;
		}
		
		return node->Size;
	}
	
	return 0;
}

UINT32 aml_write_byte(UINT8 value, CHAR8* buffer, UINT32 offset)
{
	buffer[offset++] = value;
	
	return offset;
}

UINT32 aml_write_word(UINT16 value, CHAR8* buffer, UINT32 offset)
{
	buffer[offset++] = value & 0xff;
	buffer[offset++] = value >> 8;
	
	return offset;
}

UINT32 aml_write_dword(UINT32 value, CHAR8* buffer, UINT32 offset)
{
	buffer[offset++] = value & 0xff;
	buffer[offset++] = (value >> 8) & 0xff;
	buffer[offset++] = (value >> 16) & 0xff;
	buffer[offset++] = (value >> 24) & 0xff;
	
	return offset;
}

UINT32 aml_write_qword(UINT64 value, CHAR8* buffer, UINT32 offset)
{
	buffer[offset++] = value & 0xff;
  buffer[offset++] = RShiftU64(value, 8) & 0xff;
  buffer[offset++] = RShiftU64(value, 16) & 0xff;
  buffer[offset++] = RShiftU64(value, 24) & 0xff;
  buffer[offset++] = RShiftU64(value, 32) & 0xff;
  buffer[offset++] = RShiftU64(value, 40) & 0xff;
  buffer[offset++] = RShiftU64(value, 48) & 0xff;
  buffer[offset++] = RShiftU64(value, 56) & 0xff;
	
	return offset;
}

UINT32 aml_write_buffer(CONST CHAR8* value, UINT32 size, CHAR8* buffer, UINT32 offset)
{
	if (size > 0)
	{
		CopyMem(buffer + offset, value, size);
	}
	
	return offset + size;
}

UINT32 aml_write_size(UINT32 size, CHAR8* buffer, UINT32 offset)
{
	if (size <= 0x3f) /* simple 1 byte length in 6 bits */
	{
		buffer[offset++] = (CHAR8)size;
	}
	else if (size <= 0xfff) 
	{
		buffer[offset++] = 0x40 | (size & 0xf); /* 0x40 is type, 0x0X is first nibble of length */
		buffer[offset++] = (size >> 4) & 0xff; /* +1 bytes for rest length */
	}
	else if (size <= 0xfffff) 
	{
		buffer[offset++] = 0x80 | (size & 0xf); /* 0x80 is type, 0x0X is first nibble of length */
		buffer[offset++] = (size >> 4) & 0xff; /* +2 bytes for rest length */
		buffer[offset++] = (size >> 12) & 0xff;
	}
    else 
	{
		buffer[offset++] = 0xc0 | (size & 0xf); /* 0xC0 is type, 0x0X is first nibble of length */
		buffer[offset++] = (size >> 4) & 0xff; /* +3 bytes for rest length */
		buffer[offset++] = (size >> 12) & 0xff;
		buffer[offset++] = (size >> 20) & 0xff;
	}
	
	return offset;
}

UINT32 aml_write_node(AML_CHUNK* node, CHAR8* buffer, UINT32 offset)
{
	if (node && buffer) 
	{
		UINT32 old = offset;
      AML_CHUNK* child = node->First;
		
		switch (node->Type) 
		{
			case AML_CHUNK_NONE:
				offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
				break;

            case AML_CHUNK_LOCAL0:
			case AML_STORE_OP:
				offset = aml_write_byte(node->Type, buffer, offset);
				break;
				
			case AML_CHUNK_DEVICE:
				offset = aml_write_byte(AML_CHUNK_OP, buffer, offset);
				offset = aml_write_byte(node->Type, buffer, offset);
				offset = aml_write_size(node->Size-2, buffer, offset);
				offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
				break;

			case AML_CHUNK_SCOPE:
      case AML_CHUNK_METHOD:
			case AML_CHUNK_PACKAGE:
			case AML_CHUNK_BUFFER:
				offset = aml_write_byte(node->Type, buffer, offset);
				offset = aml_write_size(node->Size-1, buffer, offset);
				offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
				break;
				
			case AML_CHUNK_BYTE:
				if (node->Buffer[0] == 0x0 || node->Buffer[0] == 0x1) {
					offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
				} else {
					offset = aml_write_byte(node->Type, buffer, offset);
					offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
				}
				break;
				
			case AML_CHUNK_WORD:
			case AML_CHUNK_DWORD:
			case AML_CHUNK_QWORD:
			case AML_CHUNK_ALIAS:
			case AML_CHUNK_NAME:
      case AML_CHUNK_RETURN:
      case AML_CHUNK_STRING:
				offset = aml_write_byte(node->Type, buffer, offset);
				offset = aml_write_buffer(node->Buffer, node->Length, buffer, offset);
				break;
				
			default:
				break;
		}

		while (child) {
			offset = aml_write_node(child, buffer, offset);
			child = child->Next;
		}
		
		if (offset - old != node->Size) {
			MsgLog("Node size incorrect: type=0x%x size=%x offset=%x\n",
             node->Type, node->Size, (offset - old));
    }
	}
	
	return offset;
}
