#ifdef __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <asm/bitops.h>
#include <linux/mutex.h>

#include "proc_api.h"
#include "../../common/uthash.h"

#define MAX_COMMAND_LEN                 (256)
#define MAX_CMD_LEN                     (32)
#define MAX_PARAMS_LEN                  (128)

#define TOLOWER(x)                      ((x) | 0x20)
#ifndef MAX
#define MAX(a, b)                       (((a) > (b)) ? (a) : (b))
#endif

#define HELP_BITS                       (31)
#define IOCTL_BITS                      (30)

#define PROC_API_DBG_DIR                ("api")

typedef struct {
    char                    key[MAX_PATH];              ///< 节点名称
    char                    dir_name[MAX_NAME_LEN];     ///< 目录名称
    PDBGFS_PRIV             data;                       ///< 节点数据
    struct proc_dir_entry*  entry;                      ///< proc 目录数据结构
    UT_hash_handle          hh;                         ///< Hash链表节点
} PROC_INFO, *PPROC_INFO;

static const struct file_operations g_DbgSeq_fops;
static struct proc_dir_entry*  g_DebugProcFS;

static DEFINE_RWLOCK(g_proc_lock);
static PPROC_INFO g_proc_items = NULL;

static int proc_api_show(struct seq_file* seq, void* token)
{
    PPROC_INFO pInfo = NULL, tmp = NULL;
    int i = 0;
    seq_printf(seq, "Total API items:        %u\n", HASH_COUNT(g_proc_items));
    seq_printf(seq, "API root direcotry:     /proc/%s/\n", PROC_API_DIR_NAME);
    seq_puts(seq, "-------------------------------------"
             "-------------------------------------\n");
    seq_puts(seq, "|   ID    |   Direcotry    |      Name      |  Show  |  IOCtrl  |  Help  |\n");
    seq_puts(seq, "|------------------------------------"
             "------------------------------------|\n");

    read_lock(&g_proc_lock);
    HASH_ITER(hh, g_proc_items, pInfo, tmp) {
        seq_printf(seq, "|   %03d   | %14s | %14s |   %1s    |    %1s     |   %1s    |\n",
                   i++,
                   pInfo->dir_name ? pInfo->dir_name : "",
                   pInfo->data->name,
                   pInfo->data->show ? "*" : "-",
                   pInfo->data->ioctl ? "*" : "-",
                   pInfo->data->help ? "*" : "-");
    }
    read_unlock(&g_proc_lock);

    seq_puts(seq, "-------------------------------------"
             "-------------------------------------\n");
    return 0;
}

static int proc_api_ioctl(struct seq_file* seq, void* token)
{
    PDBGFS_PRIV priv = (PDBGFS_PRIV)(seq->private);

    seq_printf(seq, "Run Command [%s] with (%s)\n", priv->cmd, priv->cmd_data);
    return 0;
}

static int proc_api_help(struct seq_file* seq, void* token)
{
    PDBGFS_PRIV priv = (PDBGFS_PRIV)(seq->private);
    seq_puts(seq, "==============Options Helps=============\n");
    seq_printf(seq, "usage: echo \"<params>\" > /proc/%s/%s/%s\n",
               PROC_API_DIR_NAME, PROC_API_DBG_DIR, priv->name);

    return 0;
}

static DBGFS_PRIV g_DbgConfig[] = {
    {
        "info",   0, 0L, NULL, NULL,
        proc_api_show, proc_api_ioctl, proc_api_help
    },
};

static PROC_API g_ProcApi = {
    PROC_API_DBG_DIR, g_DbgConfig, sizeof(g_DbgConfig) / sizeof(g_DbgConfig[0]),
};

static int proc_api_init(const char* proc_dir_name)
{
    if(proc_dir_name) {
        g_DebugProcFS = proc_mkdir(proc_dir_name, NULL);

        if(g_DebugProcFS != NULL) {
            return ERR_DBGFS_NO_ERROR;
        }
    }

    return ERR_DBGFS_INIT;
}

static void proc_api_uninit(const char* proc_dir_name)
{
    remove_proc_entry(proc_dir_name, NULL);
}

/**
 * @brief 注册proc模块
 *
 * @param pv: 模块proc信息
 * @return int: 0 成功, -EINVAL 参数错误
 */
int proc_api_register(PPROC_API pv)
{
    struct proc_dir_entry* pdir;
    int i = 0;

    if(pv == NULL || pv->data == NULL || pv->num_data <= 0) {
        return -EINVAL;
    }

    if(pv->dir_name && strlen(pv->dir_name) > 0) {
        pdir = proc_mkdir(pv->dir_name, g_DebugProcFS);
    } else {
        pdir = g_DebugProcFS;
    }

//    printk(KERN_ERR "Register: %s number %d of %s\n", pv->dir_name, pv->num_data, pv->data->name);

    for(i = 0; i < pv->num_data; i++) {
        PPROC_INFO pInfo = (PPROC_INFO)kmalloc(sizeof(PROC_INFO), GFP_KERNEL);

        if(pInfo == NULL) {
            printk(KERN_ERR "Malloc Error\n");
            return ERR_MALLOC_MEMORY;
        }

        memset(pInfo, 0, sizeof(PROC_INFO));

        if(!proc_create_data(pv->data[i].name, 0x644, pdir, &g_DbgSeq_fops, &pv->data[i])) {
            printk(KERN_ERR "proc_create_data Error\n");
            continue;
        }

        if(pv->dir_name && strlen(pv->dir_name) > 0) {
            strcpy(pInfo->dir_name, pv->dir_name);
            sprintf(pInfo->key, "%s%s", pv->dir_name, pv->data[i].name);
        } else {
            sprintf(pInfo->key, "%s", pv->data[i].name);
        }

        pInfo->entry = pdir;
        pInfo->data = &pv->data[i];
        write_lock(&g_proc_lock);
        HASH_ADD_STR(g_proc_items, key, pInfo);
        write_unlock(&g_proc_lock);
    }

    return 0;
}

/**
 * @brief 清除proc模块注册
 *
 * @param pv: 模块proc信息
 * @return int: 0 成功, -EINVAL 参数错误
 */
int proc_api_unregister(PPROC_API pv)
{
    PPROC_INFO pInfo = NULL;
    char  key[MAX_PATH];
    int i;

    if(pv == NULL || pv->data == NULL || pv->num_data <= 0) {
        return -EINVAL;
    }

    for(i = 0; i < pv->num_data; i++) {
        memset(key, 0, MAX_PATH);

        if(pv->dir_name && strlen(pv->dir_name) > 0) {
            sprintf(key, "%s%s", pv->dir_name, pv->data[i].name);
        } else {
            sprintf(key, "%s", pv->data[i].name);
        }

        HASH_FIND_STR(g_proc_items, key, pInfo);

        if(pInfo) {
            write_lock(&g_proc_lock);
            HASH_DEL(g_proc_items, pInfo);
            write_unlock(&g_proc_lock);
            remove_proc_entry(pv->data[i].name, pInfo->entry);
            kfree(pInfo);
        }
    }

    if(pv->dir_name && strlen(pv->dir_name) > 0) {
        remove_proc_subtree(pv->dir_name, g_DebugProcFS);
    }

    return 0;
}

static int proc_seq_open(struct inode* inode, struct file* file)
{
    PDBGFS_PRIV priv = (PDBGFS_PRIV)(PDE_DATA(inode));

    if(test_bit(HELP_BITS, &priv->mask) && priv->help) {
        clear_bit(HELP_BITS, &priv->mask);
        return single_open(file, priv->help, priv);
    } else if(test_bit(IOCTL_BITS, &priv->mask) && priv->ioctl) {
        clear_bit(IOCTL_BITS, &priv->mask);
        return single_open(file, priv->ioctl, priv);
    } else {
        return single_open(file, priv->show, priv);
    }
}

static int proc_seq_release(struct inode* inode, struct file* file)
{
    return single_release(inode, file);
}

static int get_value_base(unsigned char* pBuf)
{
    int strLen = pBuf ? strlen(pBuf) : -1;

    if(pBuf) {
        int i = 0;

        if(pBuf[0] == '0' && pBuf[1] == 'x') {
            return 16;
        }

        for(i = 0; i < strLen; i++) {
            if(TOLOWER(pBuf[i]) >= 'a' && TOLOWER(pBuf[i]) <= 'f') {
                return 16;
            }
        }
    }

    return 10;
}

/**
 * @brief 获取参数中的字符串数据类型值
 *
 * @param pBuf: 字符串缓冲区
 * @param index: 指定参数位置
 * @param pValue: 返回提取的字符串值
 * @param maxBytes: 最大参数限制
 * @return int: 0 成功, -EINVAL 参数错误,-ENOENT 没有对应的值
 */
int get_string_value(unsigned char* pBuf, int index, unsigned char* pValue, unsigned int maxBytes)
{
    char tmpBuf[128];
    char* token;
    char* s = tmpBuf;
    int paramIndex = 0;

    if(pBuf == NULL || index < 0 || pValue == NULL) {
        return -EINVAL;
    }

    memset(pValue, 0, maxBytes);

    strcpy(tmpBuf, pBuf);

    for(token = strsep(&s, " ");
        token != NULL;
        token = strsep(&s, " ")) {
        if(token != NULL) {
            if(index != paramIndex) {
                paramIndex += 1;
                continue;
            }

            strcpy(pValue, token);

            return 0;
        }
    }

    return -ENOENT;
}

/**
 * @brief 从参数的指定位置提取整数值
 *
 * @param pBuf: 字符串缓冲区
 * @param index: 指定参数位置
 * @param pValue: 返回获取的值
 * @return int: 0 成功, -EINVAL 参数错误,-ENOENT 没有对应的值,-EFAULT 字符串转整数错误
 */
int get_int_value(unsigned char* pBuf, int index, int* pValue)
{
    char tmpBuf[128];
    char* token;
    char* s = tmpBuf;
    int paramIndex = 0;
    int paramValue = -1;

    if(pBuf == NULL || index < 0 || pValue == NULL) {
        return -EINVAL;
    }

    strcpy(tmpBuf, pBuf);

    for(token = strsep(&s, " ");
        token != NULL;
        token = strsep(&s, " ")) {
        if(token != NULL) {
            if(index != paramIndex) {
                paramIndex += 1;
                continue;
            }

            if(kstrtol(token, get_value_base(token), (long*)&paramValue) != 0) {
                printk(KERN_ERR "strict_strtol [%s] Error\n", token);
                return -EFAULT;
            } else {
                *pValue = paramValue;
                return 0;
            }
        }
    }

    return -ENOENT;
}

static int process_input_content(struct seq_file* seq, unsigned char* pBuf, PDBGFS_PRIV pv)
{
    int strLen = pBuf ? strlen(pBuf) - 1 : -1;
    char tmpBuf[MAX_COMMAND_LEN], cmdBuf[MAX_CMD_LEN], paramBuf[MAX_PARAMS_LEN];
    int i = 0;
    int bIsCmd = 0;

    if(strlen <= 0){
        return -ERR_PARA_OUTOFRANGE;
    }

    memset(tmpBuf, 0, MAX_COMMAND_LEN);
    memset(cmdBuf, 0, MAX_CMD_LEN);
    memset(paramBuf, 0, MAX_PARAMS_LEN);

    if(pBuf == NULL && pv == NULL) {
        return -ERR_PARA_OUTOFRANGE;
    }

    strcpy(tmpBuf, pBuf);

    for(i = 0; i < strLen; i++) {
        if(tmpBuf[i] == ' ') {
            strncpy(cmdBuf, tmpBuf, i);
            strncpy(paramBuf, &tmpBuf[i + 1], MAX(strLen - i - 1, 0));
            bIsCmd = 1;
            break;
        }
    }

    if(!bIsCmd) {
        if(kstrtoul(pBuf, get_value_base(pBuf), (long unsigned int*)&pv->params)) {
            printk(KERN_ERR "strict_strtoul [%s] base %d Error\n", pBuf, get_value_base(pBuf));
            return -ERR_DBGFS_WRITEOPTS;
        }
    } else {
        if(pv->ioctl != NULL) {
            if(pv->cmd != NULL) {
                kfree(pv->cmd);
            }

            if(pv->cmd_data) {
                kfree(pv->cmd_data);
            }

            if(strlen(cmdBuf) > 0) {
                pv->cmd = kmalloc(strlen(cmdBuf) + 1, GFP_KERNEL);

                if(pv->cmd == NULL) {
                    printk(KERN_ERR "Malloc %lu Error\n", strlen(cmdBuf) + 1);
                    return -ERR_MALLOC_MEMORY;
                }

                strcpy(pv->cmd, cmdBuf);
            }

            if(strlen(paramBuf) > 0) {
                pv->cmd_data = kmalloc(strlen(paramBuf) + 1, GFP_KERNEL);

                if(pv->cmd_data == NULL) {
                    printk(KERN_ERR "Malloc %ld Error\n", strlen(paramBuf) + 1);
                    return -ERR_MALLOC_MEMORY;
                }

                strcpy(pv->cmd_data, paramBuf);
            }

            set_bit(IOCTL_BITS, &pv->mask);
            printk(KERN_INFO "%s do %s command with %s\n", pv->name, pv->cmd, pv->cmd_data);
        }

        return ERR_DBGFS_NO_ERROR;
    }

    return ERR_PARA_OUTOFRANGE;
}

static ssize_t proc_seq_option_write(struct file* file, const char __user* userbuf,
                                     size_t count, loff_t* data)
{
    char buf[64];
    struct seq_file* seq = (struct seq_file*)file->private_data;
    PDBGFS_PRIV pv = (PDBGFS_PRIV)seq->private;

    if(count >= sizeof(buf)) {
        printk(KERN_ERR "Input Params Error:count = %ld, max size = %ld\n", count,
               sizeof(buf));
        return -ERR_PARA_OUTOFRANGE;
    }

    if(copy_from_user(buf, userbuf, count)) {
        printk(KERN_ERR "Copy Data To Kernel Error\n");
        return -ERR_DBGFS_WRITEOPTS;
    }

    if(buf[0] == 'h' &&
       buf[1] == 'e' &&
       buf[2] == 'l' &&
       buf[3] == 'p') {
        if(pv->help != NULL) {
            set_bit(HELP_BITS, &pv->mask);
            //pv->help((struct seq_file*)file->private_data, pv);
        }

        return count;
    }

    buf[count] = 0x00;

    if(!process_input_content(seq, buf, pv) == ERR_DBGFS_NO_ERROR) {
        printk(KERN_ERR "Input [%s] Process Error\n", buf);
    }

    return count;
}

static const struct file_operations g_DbgSeq_fops = {
    .owner   = THIS_MODULE,
    .open    = proc_seq_open,
    .read    = seq_read,
    .write   = proc_seq_option_write,
    .llseek  = seq_lseek,
    .release = proc_seq_release,
};

#define VERSION ("v0.0.0.2")

static int api_module_init(void)
{
    printk(KERN_ALERT "Hello ISG PROC API version: %s\n", VERSION);
    proc_api_init(PROC_API_DIR_NAME);

    if(proc_api_register(&g_ProcApi) != ERR_DBGFS_NO_ERROR) {
        printk(KERN_ERR "Regisetr %s Error\n", g_ProcApi.dir_name);
    }

    return 0;
}
module_init(api_module_init);

static void __exit api_module_exit(void)
{
    printk(KERN_ALERT "Bye ISG PROC API version: %s\n", VERSION);
    proc_api_unregister(&g_ProcApi);

    proc_api_uninit(PROC_API_DIR_NAME);
}
module_exit(api_module_exit);

EXPORT_SYMBOL(proc_api_register);
EXPORT_SYMBOL(proc_api_unregister);
EXPORT_SYMBOL(get_int_value);
EXPORT_SYMBOL(get_string_value);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION("0.1");
#endif