#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include <linux/tapi/tapi.h>
#include <linux/tapi/tapi-ioctl.h>


struct tapi_stream_file {
	struct tapi_device *tdev;
	struct tapi_stream *stream;
};

static inline struct tapi_device *inode_to_tdev(struct inode *inode)
{
	return container_of(inode->i_cdev, struct tapi_char_device, cdev)->tdev;
}

static int tapi_stream_open(struct inode *inode, struct file *file)
{
	int ret;
	struct tapi_device *tdev = inode_to_tdev(inode);
	struct tapi_stream_file *stream;

	get_device(&tdev->dev);

	stream = kzalloc(sizeof(*stream), GFP_KERNEL);
	if (!stream) {
		ret = -ENOMEM;
		goto err_put;
	}

	stream->stream = tapi_stream_alloc(tdev);
	if (IS_ERR(stream->stream)) {
		ret = PTR_ERR(stream->stream);
		goto err_free;
	}
	stream->tdev = tdev;

	init_waitqueue_head(&stream->stream->recv_wait);
	skb_queue_head_init(&stream->stream->recv_queue);

	file->private_data = stream;

	return 0;

err_free:
	kfree(stream);
err_put:
	put_device(&tdev->dev);
	return ret;
}

static int tapi_stream_release(struct inode *inode, struct file *file)
{
	struct tapi_stream_file *stream = file->private_data;

	if (stream) {
		tapi_stream_free(stream->tdev, stream->stream);
		put_device(&stream->tdev->dev);
		kfree(stream);
	}

	return 0;
}

static long tapi_stream_ioctl(struct file *file, unsigned int cmd,
	unsigned long arg)
{
	int ret = 0;
	struct tapi_stream_file *stream = file->private_data;
	struct tapi_device *tdev = stream->tdev;

	switch (cmd) {
	case TAPI_STREAM_IOCTL_GET_ENDPOINT:
		ret = stream->stream->ep.id;
		break;
	case TAPI_STREAM_IOCTL_CONFIGURE:
		break;
	case TAPI_STREAM_IOCTL_START:
		ret = tapi_stream_start(tdev, stream->stream);
		break;
	case TAPI_STREAM_IOCTL_STOP:
		ret = tapi_stream_stop(tdev, stream->stream);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static unsigned int tapi_stream_poll(struct file *file, struct poll_table_struct *wait)
{
	struct tapi_stream_file *stream = file->private_data;
	int ret;

	poll_wait(file, &stream->stream->recv_wait, wait);

	ret = POLLOUT;

	if (!skb_queue_empty(&stream->stream->recv_queue))
		ret |= POLLIN;

	return ret;
}

static ssize_t tapi_stream_read(struct file *file, char __user *buffer,
	size_t count, loff_t *offset)
{
	struct tapi_stream_file *stream = file->private_data;
	struct sk_buff *skb;

	skb = skb_dequeue(&stream->stream->recv_queue);
	if (!skb) {
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;

		do {
			interruptible_sleep_on(&stream->stream->recv_wait);
			skb = skb_dequeue(&stream->stream->recv_queue);
		} while (skb == NULL && !signal_pending(current));

		if (skb == NULL)
			return -ERESTARTNOHAND;
	}

	if (skb->len > count) {
		skb_queue_head(&stream->stream->recv_queue, skb);
		return -EMSGSIZE;
	}

	if (copy_to_user(buffer, skb->data, skb->len)) {
		skb_queue_head(&stream->stream->recv_queue, skb);
		return -EFAULT;
	}

	count = skb->len;

	kfree_skb(skb);

	return count;
}

static ssize_t tapi_stream_write(struct file *file, const char __user *buffer,
	size_t count, loff_t *ppos)
{
	struct tapi_stream_file *stream = file->private_data;
	struct tapi_device *tdev = stream->tdev;
	struct sk_buff *skb;

	if (count == 0)
		return 0;

	skb = alloc_skb(count, GFP_USER);
	if (!skb)
		return -ENOMEM;

	if (copy_from_user(skb_put(skb, count), buffer, count)) {
		kfree_skb(skb);
		return -EFAULT;
	}

	tdev->ops->stream_send(tdev, stream->stream, skb);

	return count;
}

static const struct file_operations tapi_stream_file_ops = {
	.owner = THIS_MODULE,
	.read = tapi_stream_read,
	.write = tapi_stream_write,
	.open = tapi_stream_open,
	.release = tapi_stream_release,
	.poll = tapi_stream_poll,
	.unlocked_ioctl = tapi_stream_ioctl,
};

int tapi_register_stream_device(struct tapi_device* tdev)
{
	dev_set_name(&tdev->stream_dev.dev, "tapi%uS", tdev->id);
	return tapi_char_device_register(tdev, &tdev->stream_dev, &tapi_stream_file_ops);
}

int tapi_stream_recv(struct tapi_device *tdev, struct tapi_stream * stream,
	struct sk_buff *skb)
{
	skb_queue_tail(&stream->recv_queue, skb);
	wake_up(&stream->recv_wait);

	return 0;
}
EXPORT_SYMBOL_GPL(tapi_stream_recv);