/*
 * Copyright (C) 2016-2019 The University of Notre Dame This software is
 * distributed under the GNU General Public License. See the file LICENSE
 * for details.
 */

#include "console.h"
#include "kobject.h"
#include "kmalloc.h"
#include "string.h"

#include "device.h"
#include "fs.h"
#include "window.h"
#include "console.h"
#include "pipe.h"

#include "kernel/error.h"

static struct kobject *kobject_create()
{
	struct kobject *k = kmalloc(sizeof(*k));
	k->refcount = 1;
	k->offset = 0;
	k->tag = 0;
	k->data.file = 0;
	return k;
}

struct kobject *kobject_create_file(struct fs_dirent *f)
{
	struct kobject *k = kobject_create();
	k->type = KOBJECT_FILE;
	k->data.file = f;
	return k;
}

struct kobject *kobject_create_dir( struct fs_dirent *d )
{
	struct kobject *k = kobject_create();
	k->type = KOBJECT_DIR;
	k->data.dir = d;
	return k;
}

struct kobject *kobject_create_device(struct device *d)
{
	struct kobject *k = kobject_create();
	k->type = KOBJECT_DEVICE;
	k->data.device = d;
	return k;
}

struct kobject *kobject_create_window(struct window *g)
{
	struct kobject *k = kobject_create();
	k->type = KOBJECT_WINDOW;
	k->data.window = g;
	return k;
}

struct kobject *kobject_create_console(struct console *c)
{
	struct kobject *k = kobject_create();
	k->type = KOBJECT_CONSOLE;
	k->data.console = c;
	return k;
}

struct kobject *kobject_create_pipe(struct pipe *p)
{
	struct kobject *k = kobject_create();
	k->type = KOBJECT_PIPE;
	k->data.pipe = p;
	return k;
}

struct kobject *kobject_addref(struct kobject *k)
{
	k->refcount++;
	return k;
}

struct kobject * kobject_copy( struct kobject *ksrc )
{
	struct kobject *kdst = kobject_create();

	kdst->data = ksrc->data;
	kdst->type = ksrc->type;
	kdst->offset = ksrc->offset;
	kdst->refcount = 1;

	if(ksrc->tag) {
		kdst->tag = strdup(ksrc->tag);
	} else {
		kdst->tag = 0;
	}

	switch (ksrc->type) {
	case KOBJECT_WINDOW:
		window_addref(ksrc->data.window);
		break;
	case KOBJECT_CONSOLE:
		console_addref(ksrc->data.console);
		break;
	case KOBJECT_FILE:
		fs_dirent_addref(ksrc->data.file);
		break;
	case KOBJECT_DIR:
		fs_dirent_addref(ksrc->data.dir);
		break;
	case KOBJECT_DEVICE:
		device_addref(ksrc->data.device);
		break;
	case KOBJECT_PIPE:
		pipe_addref(ksrc->data.pipe);
		break;
	}

	return kdst;
}

struct kobject *kobject_create_window_from_window( struct kobject *k, int x, int y, int width, int height )
{
	if(k->type!=KOBJECT_WINDOW) return 0;

	struct window *w = window_create(k->data.window,x,y,width,height);
	if(w) {
		return kobject_create_window(w);
	} else {
		return 0;
	}
}

struct kobject *kobject_create_console_from_window( struct kobject *k )
{
	if(k->type!=KOBJECT_WINDOW) return 0;
	struct console *c = console_create(k->data.window);
	if(!c) return 0;
	return kobject_create_console(c);
}

struct kobject * kobject_create_file_from_dir( struct kobject *kobject, const char *name )
{
	if(kobject->type==KOBJECT_DIR) {
		struct fs_dirent *d = fs_dirent_mkfile(kobject->data.dir,name);
		if(d) {
			return kobject_create_file(d);
		} else {
			return 0;
		}
	} else {
		return 0;
		// XXX KERROR_NOT_IMPLEMENTED;
	}
	return 0;
}

struct kobject * kobject_create_dir_from_dir( struct kobject *kobject, const char *name )
{
	if(kobject->type==KOBJECT_DIR) {
		struct fs_dirent *d = fs_dirent_mkdir(kobject->data.dir,name);
		if(d) {
			return kobject_create_dir(d);
		} else {
			return 0;
		}
	} else {
		return 0;
		// XXX KERROR_NOT_IMPLEMENTED;
	}
	return 0;
}

int kobject_read(struct kobject *kobject, void *buffer, int size, kernel_io_flags_t flags )
{
	int actual = 0;

	switch (kobject->type) {
	case KOBJECT_FILE:
		actual = fs_dirent_read(kobject->data.file, (char *) buffer, (uint32_t) size, kobject->offset);
		break;
	case KOBJECT_DIR:
		return KERROR_INVALID_REQUEST;
		break;
	case KOBJECT_DEVICE:
		if(flags&KERNEL_IO_NONBLOCK) {
			actual = device_read_nonblock(kobject->data.device, buffer, size / device_block_size(kobject->data.device), 0);
		} else {
			actual = device_read(kobject->data.device, buffer, size / device_block_size(kobject->data.device), 0);
		}
		break;
	case KOBJECT_PIPE:
		if(flags&KERNEL_IO_NONBLOCK) {
			actual = pipe_read_nonblock(kobject->data.pipe, buffer, size);
		} else {
			actual = pipe_read(kobject->data.pipe, buffer, size);
		}
		break;
	case KOBJECT_WINDOW:
		if(flags&KERNEL_IO_NONBLOCK) {
			actual = window_read_events_nonblock(kobject->data.window, buffer, size);
		} else {
			actual = window_read_events(kobject->data.window, buffer, size);
		}
		break;
	case KOBJECT_CONSOLE:
		if(flags&KERNEL_IO_NONBLOCK) {
			actual = console_read_nonblock(kobject->data.console,buffer,size);
		} else {
			actual = console_read(kobject->data.console,buffer,size);
		}

		break;
	default:
		actual = 0;
		break;
	}

	if(actual > 0) kobject->offset += actual;

	return actual;
}

int kobject_write(struct kobject *kobject, void *buffer, int size, kernel_io_flags_t flags )
{
	switch (kobject->type) {
	case KOBJECT_WINDOW:
		if(flags&KERNEL_IO_POST) {
			return window_post_events(kobject->data.window,buffer,size);
		} else {
			return window_write_graphics(kobject->data.window,buffer,size);
		}
		break;
	case KOBJECT_CONSOLE:
		if(flags&KERNEL_IO_POST) {
			return console_post(kobject->data.console,buffer,size);
		} else {
			return console_write(kobject->data.console, buffer, size );
		}
		break;
	case KOBJECT_FILE:{
			int actual = fs_dirent_write(kobject->data.file, (char *) buffer, (uint32_t) size, kobject->offset);
			if(actual > 0)
				kobject->offset += actual;
			return actual;
		}
	case KOBJECT_DEVICE:
		return device_write(kobject->data.device, buffer, size / device_block_size(kobject->data.device), 0);
	case KOBJECT_PIPE:
		if(flags&KERNEL_IO_NONBLOCK) {
			return pipe_write_nonblock(kobject->data.pipe, buffer, size);
		} else {
			return pipe_write(kobject->data.pipe, buffer, size);
		}
	default:
		return 0;
	}
	return 0;
}

int kobject_list(struct kobject *kobject, void *buffer, int size)
{
	if(kobject->type==KOBJECT_DIR) {
		return fs_dirent_list(kobject->data.dir,buffer,size);
	} else {
		return KERROR_NOT_A_DIRECTORY;
	}
}

int kobject_lookup( struct kobject *kobject, const char *name, struct kobject **newobj )
{
	if(kobject->type==KOBJECT_DIR) {
		struct fs_dirent *d;
		d = fs_dirent_traverse(kobject->data.dir,name);
		if(d) {
			if(fs_dirent_isdir(d)) {
				*newobj = kobject_create_dir(d);
			} else {
				*newobj = kobject_create_file(d);
			}
			return 0;
		} else {
			return KERROR_NOT_FOUND;
		}
	} else {
		return KERROR_NOT_IMPLEMENTED;
	}
	return 0;
}

int kobject_remove( struct kobject *kobject, const char *name )
{
	if(kobject->type==KOBJECT_DIR) {
		return fs_dirent_remove(kobject->data.dir,name);
	} else {
		return KERROR_NOT_IMPLEMENTED;
	}
	return 0;
}

int kobject_close(struct kobject *kobject)
{
	kobject->refcount--;

	if(kobject->refcount==0) {
		switch (kobject->type) {
		case KOBJECT_WINDOW:
			window_delete(kobject->data.window);
			break;
		case KOBJECT_CONSOLE:
			console_delete(kobject->data.console);
			break;
		case KOBJECT_FILE:
			fs_dirent_close(kobject->data.file);
			break;
		case KOBJECT_DIR:
			fs_dirent_close(kobject->data.dir);
			break;
		case KOBJECT_DEVICE:
			device_close(kobject->data.device);
			break;
		case KOBJECT_PIPE:
			pipe_delete(kobject->data.pipe);
			break;
		default:
			break;
		}
		if (kobject->tag)
			kfree(kobject->tag);
		kfree(kobject);
		return 0;
	} else if(kobject->refcount>1 ) {
		if(kobject->type==KOBJECT_PIPE) {
			pipe_flush(kobject->data.pipe);
		}
	}
	return 0;
}

int kobject_size(struct kobject *kobject, int *dims, int n)
{
	switch (kobject->type) {
	case KOBJECT_WINDOW:
		if(n==2) {
			dims[0] = window_width(kobject->data.window);
			dims[1] = window_height(kobject->data.window);
			return 0;
		} else {
			return KERROR_INVALID_REQUEST;
		}
	case KOBJECT_CONSOLE:
		if(n==2) {
			console_size(kobject->data.console,&dims[0],&dims[1]);
			return 0;
		} else {
			return KERROR_INVALID_REQUEST;
		}
	case KOBJECT_FILE:
		if(n==1) {
			dims[0] = fs_dirent_size(kobject->data.file);
			return 0;
		} else {
			return KERROR_INVALID_REQUEST;
		}
	case KOBJECT_DIR:
		if(n==1) {
			dims[0] = fs_dirent_size(kobject->data.dir);
			return 0;
		} else {
			return KERROR_INVALID_REQUEST;
		}
	case KOBJECT_DEVICE:
		if(n==2) {
			dims[0] = device_nblocks(kobject->data.device);
			dims[1] = device_block_size(kobject->data.device);
			return 0;
		} else {
			return KERROR_INVALID_REQUEST;
		}
	case KOBJECT_PIPE:
		if(n==1) {
			dims[0] = pipe_size(kobject->data.pipe);
			return 0;
		} else {
			return KERROR_INVALID_REQUEST;
		}
	}
	return KERROR_INVALID_REQUEST;
}

int kobject_get_type(struct kobject *kobject)
{
	return kobject->type;
}

int kobject_set_tag(struct kobject *kobject, char *new_tag)
{
	if(kobject->tag != 0) {
		kfree(kobject->tag);
	}
	kobject->tag = kmalloc(strlen(new_tag) * sizeof(char));
	strcpy(kobject->tag, new_tag);
	return 1;
}

int kobject_get_tag(struct kobject *kobject, char *buffer, int buffer_size)
{
	if(kobject->tag != 0) {
		strcpy(buffer, kobject->tag);
		return 1;
	}
	return 0;
}