#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

#include "bible.h"

MODULE_LICENSE("GPL"); //God's Parables and Liturgy

int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static int on_dev_uevent(struct device*, struct kobj_uevent_env*);

#define DEVICE_NAME "bible"

static dev_t devt;
static struct class* device_class;
static struct device* device;
static struct cdev character_device;

static struct file_operations fops = {
    .read = device_read,
    .open = device_open,
    .release = device_release
};

int init_module(void)
{
    int result;
    if((result = alloc_chrdev_region(&devt, 0, 1, DEVICE_NAME)) < 0)
    {
        printk(KERN_ALERT "Couldn't alloc chrdev region: %d\n", result);

        return result;
    }

    if((device_class = class_create(THIS_MODULE, DEVICE_NAME)) == NULL)
    {
        printk(KERN_ALERT "Couldn't create device class\n");

        unregister_chrdev_region(devt, 1);
        return -1;
    }

    device_class->dev_uevent = on_dev_uevent;

    if((device = device_create(device_class, NULL, devt, NULL, DEVICE_NAME)) == NULL)
    {
        printk(KERN_ALERT "Couldn't create device\n");

        class_destroy(device_class);
        unregister_chrdev_region(devt, 1);
        return -1;
    }

    cdev_init(&character_device, &fops);

    if((result = cdev_add(&character_device, devt, 1)) < 0)
    {
        printk(KERN_ALERT "Couldn't add character device\n");

        device_destroy(device_class, devt);
        class_destroy(device_class);
        unregister_chrdev_region(devt, 1);
        return result;
    }

    return 0;
}

void cleanup_module(void)
{
    device_destroy(device_class, devt);
    class_destroy(device_class);
    unregister_chrdev_region(devt, 1);
}

static int on_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
{
    add_uevent_var(env, "DEVMODE=%#o", 0444);

    return 0;
}

static int device_open(struct inode *inode, struct file *file)
{
    try_module_get(THIS_MODULE);

    return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
    module_put(THIS_MODULE);

    return 0;
}

static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t * offset)
{
    int i = 0;

    if(*offset + length > bible_txt_len)
    {
        length = bible_txt_len - *offset;
    }

    for(; i < length; i++)
    {
        put_user(*(bible_txt + *offset + i), buffer + i);
    }

    *offset += length;

    return i;
}
