/*
 * Copyright (c) 2010-2012 Yuichi Watanabe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Yuichi Watanabe nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <core/param.h>
#include <core/printf.h>
#include <core/string.h>
#include <core/vmmerr.h>
#include "param.h"

/*
 * /boot/vmm.elf vm=vm0,vm1 vm1.cpu=6,7 vm1.mem=80000000-200000000 vm1.pci=00:1c.0 shell=5
 */

enum param_state {
	STATE_NAME,
	STATE_VALUE,
	STATE_OTHER_VALUE,
	STATE_IGNORE
};

static char saved_cmdline[CMDLINE_SIZE];

void
param_save(const char *cmdline)
{
	snprintf(saved_cmdline, CMDLINE_SIZE, "%s", cmdline);
	printf("VMM params: %s\n", cmdline);
}

vmmerr_t
param_get_str(const char *name, int number, char *buf, size_t len)
{
	char *cur;
	char *na;
	size_t na_len;
	size_t name_len;
	enum param_state state;

	if (name == NULL || *name == '\0') {
		return VMMERR_INVAL;
	}

	name_len = strlen(name);
	cur = na = saved_cmdline;
	na_len = 0;
	state = STATE_NAME;

	for (;;) {
		switch (*cur) {
		case '\0':
		case ' ':
		case '\t':
			switch (state) {
			case STATE_NAME:
				if (na_len == name_len &&
				    memcmp(na, name, na_len) == 0) {
					if (buf && len == 0) {
						return VMMERR_BUFSIZE;
					} else if (buf) {
						*buf = '\0';
					}
					return VMMERR_SUCCESS;
				}
				break;
			case STATE_VALUE:
				if (buf && len == 0) {
					return VMMERR_BUFSIZE;
				} else if (buf) {
					*buf = '\0';
				}
				return VMMERR_SUCCESS;
			case STATE_OTHER_VALUE:
				return VMMERR_NOTEXIST;
			case STATE_IGNORE:
				break;
			}
			if (*cur == '\0') {
				goto out;
			}
			na = cur + 1;
			na_len = 0;
			state = STATE_NAME;
			break;
		case '=':
			switch (state) {
			case STATE_NAME:
				if (na_len == name_len &&
				    memcmp(na, name, na_len) == 0) {
					if (number == 0) {
						state = STATE_VALUE;
					} else {
						state = STATE_OTHER_VALUE;
					}
				} else {
					state = STATE_IGNORE;
				}
				break;
			case STATE_VALUE:
				if (buf && len == 0) {
					return VMMERR_BUFSIZE;
				} else if (buf) {
					*buf = *cur;
					buf++;
					len--;
				}
				break;
			case STATE_OTHER_VALUE:
			case STATE_IGNORE:
				break;
			}
			break;
		case ',':
			switch (state) {
			case STATE_NAME:
				break;
			case STATE_VALUE:
				if (buf && len == 0) {
					return VMMERR_BUFSIZE;
				} else if (buf) {
					*buf = '\0';
				}
				return VMMERR_SUCCESS;
			case STATE_OTHER_VALUE:
				number--;
				if (number == 0) {
					state = STATE_VALUE;
				}
				break;
			case STATE_IGNORE:
				break;
			}
			break;
		default:
			switch (state) {
			case STATE_NAME:
				na_len++;
				break;
			case STATE_VALUE:
				if (buf && len == 0) {
					return VMMERR_BUFSIZE;
				} else if (buf) {
					*buf = *cur;
					buf++;
					len--;
				}
				break;
			case STATE_OTHER_VALUE:
			case STATE_IGNORE:
				break;
			}
			break;
		}
		cur++;
	}
 out:
	return VMMERR_NOTEXIST;
}

static vmmerr_t
asci_to_ux(const char *buf, u64 *value, int bit)
{
	int i;
	u64 ret_val = 0;

	if (buf[0] == '\0') {
		/*
		 * If a param is the zero-length string, return
		 * VMMERR_NOTEXIST.
		 */
		return VMMERR_NOTEXIST;
	}
	for (i = 0; i < bit / 4; i++) {
		if (buf[i] == '\0') {
			break;
		}
		if (buf[i] >= '0' && buf[i] <= '9') {
			ret_val <<= 4;
			ret_val += buf[i] - '0';
		} else if (buf[i] >= 'a' && buf[i] <= 'f') {
			ret_val <<= 4;
			ret_val += buf[i] - 'a' + 0xa;
		} else if (buf[i] >= 'A' && buf[i] <= 'F') {
			ret_val <<= 4;
			ret_val += buf[i] - 'A' + 0xa;
		} else {
			return VMMERR_INVAL;
		}
	}
	if (buf[i] != '\0') {
		return VMMERR_INVAL;
	}
	*value = ret_val;
	return VMMERR_SUCCESS;
}

static vmmerr_t
asci_to_u8(const char *buf, u8 *value)
{
	u64 temp;
	vmmerr_t err;
	err = asci_to_ux(buf, &temp, 8);
	*value = temp;
	return err;
}

static vmmerr_t
asci_to_u16(const char *buf, u16 *value)
{
	u64 temp;
	vmmerr_t err;
	err = asci_to_ux(buf, &temp, 16);
	*value = temp;
	return err;
}

static vmmerr_t
asci_to_u32(const char *buf, u32 *value)
{
	u64 temp;
	vmmerr_t err;
	err = asci_to_ux(buf, &temp, 32);
	*value = temp;
	return err;
}

static vmmerr_t
asci_to_u64(const char *buf, u64 *value)
{
	u64 temp;
	vmmerr_t err;
	err = asci_to_ux(buf, &temp, 64);
	*value = temp;
	return err;
}

vmmerr_t
param_get_u8(const char *name, int number, u8 *value)
{
	char buf[9];
	vmmerr_t vmmerr;

	vmmerr = param_get_str(name, number, buf, sizeof(buf));
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	return asci_to_u8(buf, value);
}

vmmerr_t
param_get_u16(const char *name, int number, u16 *value)
{
	char buf[9];
	vmmerr_t vmmerr;

	vmmerr = param_get_str(name, number, buf, sizeof(buf));
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	return asci_to_u16(buf, value);
}

vmmerr_t
param_get_u32(const char *name, int number, u32 *value)
{
	char buf[9];
	vmmerr_t vmmerr;

	vmmerr = param_get_str(name, number, buf, sizeof(buf));
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	return asci_to_u32(buf, value);
}

vmmerr_t
param_get_u64(const char *name, int number, u64 *value)
{
	char buf[17];
	vmmerr_t vmmerr;

	vmmerr = param_get_str(name, number, buf, sizeof(buf));
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	return asci_to_u64(buf, value);
}

vmmerr_t
param_get_u64_range(const char *name, int number, u64 *value1, u64 *value2)
{
	char buf[16 * 2 + 1 + 1];
	vmmerr_t vmmerr;
	char *val2 = buf;

	vmmerr = param_get_str(name, number, buf, sizeof(buf));
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	while (*val2) {
		if (*val2 == '-') {
			*val2 = '\0';
			val2++;
			goto found;
		}
		val2++;
	}
	return VMMERR_INVAL;
 found:
	vmmerr =  asci_to_u64(buf, value1);
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	vmmerr =  asci_to_u64(val2, value2);
	return vmmerr;
}

vmmerr_t
param_get_bdf(const char *name, int number, u8 *bus, u8 *dev, u8 *func)
{
	char buf[8]; /* bb:dd.f */
	char *dev_str = buf;
	char *func_str;
	vmmerr_t vmmerr;

	vmmerr = param_get_str(name, number, buf, sizeof(buf));
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	if (*dev_str == '\0') {
		/*
		 * If a param is the zero-length string, return
		 * VMMERR_NOTEXIST.
		 */
		return VMMERR_NOTEXIST;
	}
	while (*dev_str) {
		if (*dev_str == ':') {
			*dev_str = '\0';
			dev_str++;
			goto found1;
		}
		dev_str++;
	}
	return VMMERR_INVAL;
 found1:
	func_str = dev_str + 1;
	while (*func_str) {
		if (*func_str == '.') {
			*func_str = '\0';
			func_str++;
			goto found2;
		}
		func_str++;
	}
	return VMMERR_INVAL;
 found2:
	vmmerr = asci_to_u8(buf, bus);
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	vmmerr = asci_to_u8(dev_str, dev);
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}

	vmmerr = asci_to_u8(func_str, func);
	if (vmmerr != VMMERR_SUCCESS) {
		return vmmerr;
	}
	return vmmerr;
}
