/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <linux/pkt_sched.h>
#include <netlink/object-api.h>
#include <netlink-types.h>

#include "wifi_hal.h"
#include "common.h"
#include <netlink-types.h>

interface_info *getIfaceInfo(wifi_interface_handle handle)
{
    return (interface_info *)handle;
}

wifi_handle getWifiHandle(wifi_interface_handle handle)
{
    return getIfaceInfo(handle)->handle;
}

hal_info *getHalInfo(wifi_handle handle)
{
    return (hal_info *)handle;
}

hal_info *getHalInfo(wifi_interface_handle handle)
{
    return getHalInfo(getWifiHandle(handle));
}

wifi_handle getWifiHandle(hal_info *info)
{
    return (wifi_handle)info;
}

wifi_interface_handle getIfaceHandle(interface_info *info)
{
    return (wifi_interface_handle)info;
}

wifi_error wifi_register_handler(wifi_handle handle, int cmd, nl_recvmsg_msg_cb_t func, void *arg)
{
    hal_info *info = (hal_info *)handle;

    /* TODO: check for multiple handlers? */

    if (info->num_event_cb < info->alloc_event_cb) {
        info->event_cb[info->num_event_cb].nl_cmd  = cmd;
        info->event_cb[info->num_event_cb].vendor_id  = 0;
        info->event_cb[info->num_event_cb].vendor_subcmd  = 0;
        info->event_cb[info->num_event_cb].cb_func = func;
        info->event_cb[info->num_event_cb].cb_arg  = arg;
        info->num_event_cb++;
        ALOGI("Successfully added event handler %p for command %d", func, cmd);
        return WIFI_SUCCESS;
    } else {
        return WIFI_ERROR_OUT_OF_MEMORY;
    }
}

wifi_error wifi_register_vendor_handler(wifi_handle handle,
        uint32_t id, int subcmd, nl_recvmsg_msg_cb_t func, void *arg)
{
    hal_info *info = (hal_info *)handle;

    for (int i = 0; i < info->num_event_cb; i++) {
        if(info->event_cb[info->num_event_cb].vendor_id  == id &&
           info->event_cb[info->num_event_cb].vendor_subcmd == subcmd)
        {
            info->event_cb[info->num_event_cb].cb_func = func;
            info->event_cb[info->num_event_cb].cb_arg  = arg;
            ALOGI("Updated event handler %p for vendor 0x%0x, subcmd 0x%0x"
                " and arg %p", func, id, subcmd, arg);
            return WIFI_SUCCESS;
        }
    }

    if (info->num_event_cb < info->alloc_event_cb) {
        info->event_cb[info->num_event_cb].nl_cmd  = NL80211_CMD_VENDOR;
        info->event_cb[info->num_event_cb].vendor_id  = id;
        info->event_cb[info->num_event_cb].vendor_subcmd  = subcmd;
        info->event_cb[info->num_event_cb].cb_func = func;
        info->event_cb[info->num_event_cb].cb_arg  = arg;
        info->num_event_cb++;
        ALOGI("Added event handler %p for vendor 0x%0x, subcmd 0x%0x and arg"
            " %p", func, id, subcmd, arg);
        return WIFI_SUCCESS;
    } else {
        return WIFI_ERROR_OUT_OF_MEMORY;
    }
}

void wifi_unregister_handler(wifi_handle handle, int cmd)
{
    hal_info *info = (hal_info *)handle;

    if (cmd == NL80211_CMD_VENDOR) {
        ALOGE("Must use wifi_unregister_vendor_handler to remove vendor handlers");
    }

    for (int i = 0; i < info->num_event_cb; i++) {
        if (info->event_cb[i].nl_cmd == cmd) {
            memmove(&info->event_cb[i], &info->event_cb[i+1],
                (info->num_event_cb - i) * sizeof(cb_info));
            info->num_event_cb--;
            ALOGI("Successfully removed event handler for command %d", cmd);
            return;
        }
    }
}

void wifi_unregister_vendor_handler(wifi_handle handle, uint32_t id, int subcmd)
{
    hal_info *info = (hal_info *)handle;

    for (int i = 0; i < info->num_event_cb; i++) {

        if (info->event_cb[i].nl_cmd == NL80211_CMD_VENDOR
                && info->event_cb[i].vendor_id == id
                && info->event_cb[i].vendor_subcmd == subcmd) {

            memmove(&info->event_cb[i], &info->event_cb[i+1],
                (info->num_event_cb - i) * sizeof(cb_info));
            info->num_event_cb--;
            ALOGI("Successfully removed event handler for vendor 0x%0x", id);
            return;
        }
    }
}


wifi_error wifi_register_cmd(wifi_handle handle, int id, WifiCommand *cmd)
{
    hal_info *info = (hal_info *)handle;

    ALOGD("registering command %d", id);

    if (info->num_cmd < info->alloc_cmd) {
        info->cmd[info->num_cmd].id   = id;
        info->cmd[info->num_cmd].cmd  = cmd;
        info->num_cmd++;
        ALOGI("Successfully added command %d: %p", id, cmd);
        return WIFI_SUCCESS;
    } else {
        return WIFI_ERROR_OUT_OF_MEMORY;
    }
}

WifiCommand *wifi_unregister_cmd(wifi_handle handle, int id)
{
    hal_info *info = (hal_info *)handle;

    ALOGD("un-registering command %d", id);

    for (int i = 0; i < info->num_cmd; i++) {
        if (info->cmd[i].id == id) {
            WifiCommand *cmd = info->cmd[i].cmd;
            memmove(&info->cmd[i], &info->cmd[i+1], (info->num_cmd - i) * sizeof(cmd_info));
            info->num_cmd--;
            ALOGI("Successfully removed command %d: %p", id, cmd);
            return cmd;
        }
    }

    return NULL;
}

void wifi_unregister_cmd(wifi_handle handle, WifiCommand *cmd)
{
    hal_info *info = (hal_info *)handle;

    for (int i = 0; i < info->num_cmd; i++) {
        if (info->cmd[i].cmd == cmd) {
            int id = info->cmd[i].id;
            memmove(&info->cmd[i], &info->cmd[i+1], (info->num_cmd - i) * sizeof(cmd_info));
            info->num_cmd--;
            ALOGI("Successfully removed command %d: %p", id, cmd);
            return;
        }
    }
}

#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

void hexdump(char *bytes, u16 len)
{
    int i=0;
    ALOGI("******HexDump len:%d*********", len);
    for (i = 0; ((i + 7) < len); i+=8) {
        ALOGI("%02x %02x %02x %02x   %02x %02x %02x %02x",
              bytes[i], bytes[i+1],
              bytes[i+2], bytes[i+3],
              bytes[i+4], bytes[i+5],
              bytes[i+6], bytes[i+7]);
    }
    if ((len - i) >= 4) {
        ALOGI("%02x %02x %02x %02x",
              bytes[i], bytes[i+1],
              bytes[i+2], bytes[i+3]);
        i+=4;
    }
    for (;i < len;i++) {
        ALOGI("%02x", bytes[i]);
    }
    ALOGI("******HexDump End***********");
}

#ifdef __cplusplus
}
#endif /* __cplusplus */