/*
// Copyright (c) 2014 Intel Corporation 
//
// 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 <poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/queue.h>
#include <linux/netlink.h>
#include <sys/types.h>
#include <unistd.h>
#include <DrmConfig.h>
#include <common/utils/HwcTrace.h>
#include <UeventObserver.h>

namespace android {
namespace intel {

UeventObserver::UeventObserver()
    : mUeventFd(-1),
      mExitRDFd(-1),
      mExitWDFd(-1),
      mListeners()
{
}

UeventObserver::~UeventObserver()
{
    deinitialize();
}

bool UeventObserver::initialize()
{
    mListeners.clear();

    if (mUeventFd != -1) {
        return true;
    }

    mThread = new UeventObserverThread(this);
    if (!mThread.get()) {
        ELOGTRACE("failed to create uevent observer thread");
        return false;
    }

    // init uevent socket
    struct sockaddr_nl addr;
    // set the socket receive buffer to 64K
    // NOTE: this is only called for once
    int sz = 64 * 1024;

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid =  pthread_self() | getpid();
    addr.nl_groups = 0xffffffff;

    mUeventFd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (mUeventFd < 0) {
        DEINIT_AND_RETURN_FALSE("failed to create uevent socket");
    }

    if (setsockopt(mUeventFd, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz))) {
        WLOGTRACE("setsockopt() failed");
        //return false;
    }

    if (bind(mUeventFd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
        DEINIT_AND_RETURN_FALSE("failed to bind scoket");
        return false;
    }

    memset(mUeventMessage, 0, UEVENT_MSG_LEN);

    int exitFds[2];
    if (pipe(exitFds) < 0) {
        ELOGTRACE("failed to make pipe");
        deinitialize();
        return false;
    }
    mExitRDFd = exitFds[0];
    mExitWDFd = exitFds[1];

    return true;
}

void UeventObserver::deinitialize()
{
    if (mUeventFd != -1) {
        if (mExitWDFd != -1) {
            close(mExitWDFd);
            mExitWDFd = -1;
        }
        close(mUeventFd);
        mUeventFd = -1;
    }

    if (mThread.get()) {
        mThread->requestExitAndWait();
        mThread = NULL;
    }

    while (!mListeners.isEmpty()) {
        UeventListener *listener = mListeners.valueAt(0);
        mListeners.removeItemsAt(0);
        delete listener;
    }
}

void UeventObserver::start()
{
    if (mThread.get()) {
        mThread->run("UeventObserver", PRIORITY_URGENT_DISPLAY);
    }
}


void UeventObserver::registerListener(const char *event, UeventListenerFunc func, void *data)
{
    if (!event || !func) {
        ELOGTRACE("invalid event string or listener to register");
        return;
    }

    String8 key(event);
    if (mListeners.indexOfKey(key) >= 0) {
        ELOGTRACE("listener for uevent %s exists", event);
        return;
    }

    UeventListener *listener = new UeventListener;
    if (!listener) {
        ELOGTRACE("failed to create Uevent Listener");
        return;
    }
    listener->func = func;
    listener->data = data;

    mListeners.add(key, listener);
}

bool UeventObserver::threadLoop()
{
    if (mUeventFd == -1) {
        ELOGTRACE("invalid uEvent file descriptor");
        return false;
    }

    struct pollfd fds[2];
    int nr;

    fds[0].fd = mUeventFd;
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    fds[1].fd = mExitRDFd;
    fds[1].events = POLLIN;
    fds[1].revents = 0;
    nr = poll(fds, 2, -1);

    if (nr > 0 && fds[0].revents == POLLIN) {
        int count = recv(mUeventFd, mUeventMessage, UEVENT_MSG_LEN - 2, 0);
        if (count > 0) {
            onUevent();
        }
    } else if (fds[1].revents) {
        close(mExitRDFd);
        mExitRDFd = -1;
        ILOGTRACE("exiting wait");
        return false;
    }
    // always looping
    return true;
}

void UeventObserver::onUevent()
{
    char *msg = mUeventMessage;
    const char *envelope = DrmConfig::getUeventEnvelope();
    if (strncmp(msg, envelope, strlen(envelope)) != 0)
        return;

    msg += strlen(msg) + 1;

    UeventListener *listener;
    String8 key;
    while (*msg) {
        key = String8(msg);
        if (mListeners.indexOfKey(key) >= 0) {
            DLOGTRACE("received Uevent: %s", msg);
            listener = mListeners.valueFor(key);
            if (listener) {
                listener->func(listener->data);
            } else {
                ELOGTRACE("no listener for uevent %s", msg);
            }
        }
        msg += strlen(msg) + 1;
    }
}

} // namespace intel
} // namespace android