/*
 * Copyright (C) 2013 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.
 */
//#define LOG_NDEBUG 0
#define LOG_TAG "EmulatedCamera_HotplugThread"
#include <cutils/log.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/inotify.h>

#include "EmulatedCameraHotplugThread.h"
#include "EmulatedCameraFactory.h"

#define FAKE_HOTPLUG_FILE "/data/misc/media/emulator.camera.hotplug"

#define EVENT_SIZE (sizeof(struct inotify_event))
#define EVENT_BUF_LEN (1024*(EVENT_SIZE+16))

#define SubscriberInfo EmulatedCameraHotplugThread::SubscriberInfo

namespace android {

EmulatedCameraHotplugThread::EmulatedCameraHotplugThread(
    const int* cameraIdArray,
    size_t size) :
        Thread(/*canCallJava*/false) {

    mRunning = true;
    mInotifyFd = 0;

    for (size_t i = 0; i < size; ++i) {
        int id = cameraIdArray[i];

        if (createFileIfNotExists(id)) {
            mSubscribedCameraIds.push_back(id);
        }
    }
}

EmulatedCameraHotplugThread::~EmulatedCameraHotplugThread() {
}

status_t EmulatedCameraHotplugThread::requestExitAndWait() {
    ALOGE("%s: Not implemented. Use requestExit + join instead",
          __FUNCTION__);
    return INVALID_OPERATION;
}

void EmulatedCameraHotplugThread::requestExit() {
    Mutex::Autolock al(mMutex);

    ALOGV("%s: Requesting thread exit", __FUNCTION__);
    mRunning = false;

    bool rmWatchFailed = false;
    Vector<SubscriberInfo>::iterator it;
    for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {

        if (inotify_rm_watch(mInotifyFd, it->WatchID) == -1) {

            ALOGE("%s: Could not remove watch for camID '%d',"
                  " error: '%s' (%d)",
                 __FUNCTION__, it->CameraID, strerror(errno),
                 errno);

            rmWatchFailed = true ;
        } else {
            ALOGV("%s: Removed watch for camID '%d'",
                __FUNCTION__, it->CameraID);
        }
    }

    if (rmWatchFailed) { // unlikely
        // Give the thread a fighting chance to error out on the next
        // read
        if (close(mInotifyFd) == -1) {
            ALOGE("%s: close failure error: '%s' (%d)",
                 __FUNCTION__, strerror(errno), errno);
        }
    }

    ALOGV("%s: Request exit complete.", __FUNCTION__);
}

status_t EmulatedCameraHotplugThread::readyToRun() {
    Mutex::Autolock al(mMutex);

    mInotifyFd = -1;

    do {
        ALOGV("%s: Initializing inotify", __FUNCTION__);

        mInotifyFd = inotify_init();
        if (mInotifyFd == -1) {
            ALOGE("%s: inotify_init failure error: '%s' (%d)",
                 __FUNCTION__, strerror(errno), errno);
            mRunning = false;
            break;
        }

        /**
         * For each fake camera file, add a watch for when
         * the file is closed (if it was written to)
         */
        Vector<int>::const_iterator it, end;
        it = mSubscribedCameraIds.begin();
        end = mSubscribedCameraIds.end();
        for (; it != end; ++it) {
            int cameraId = *it;
            if (!addWatch(cameraId)) {
                mRunning = false;
                break;
            }
        }
    } while(false);

    if (!mRunning) {
        status_t err = -errno;

        if (mInotifyFd != -1) {
            close(mInotifyFd);
        }

        return err;
    }

    return OK;
}

bool EmulatedCameraHotplugThread::threadLoop() {

    // If requestExit was already called, mRunning will be false
    while (mRunning) {
        char buffer[EVENT_BUF_LEN];
        int length = TEMP_FAILURE_RETRY(
                        read(mInotifyFd, buffer, EVENT_BUF_LEN));

        if (length < 0) {
            ALOGE("%s: Error reading from inotify FD, error: '%s' (%d)",
                 __FUNCTION__, strerror(errno),
                 errno);
            mRunning = false;
            break;
        }

        ALOGV("%s: Read %d bytes from inotify FD", __FUNCTION__, length);

        int i = 0;
        while (i < length) {
            inotify_event* event = (inotify_event*) &buffer[i];

            if (event->mask & IN_IGNORED) {
                Mutex::Autolock al(mMutex);
                if (!mRunning) {
                    ALOGV("%s: Shutting down thread", __FUNCTION__);
                    break;
                } else {
                    ALOGE("%s: File was deleted, aborting",
                          __FUNCTION__);
                    mRunning = false;
                    break;
                }
            } else if (event->mask & IN_CLOSE_WRITE) {
                int cameraId = getCameraId(event->wd);

                if (cameraId < 0) {
                    ALOGE("%s: Got bad camera ID from WD '%d",
                          __FUNCTION__, event->wd);
                } else {
                    // Check the file for the new hotplug event
                    String8 filePath = getFilePath(cameraId);
                    /**
                     * NOTE: we carefully avoid getting an inotify
                     * for the same exact file because it's opened for
                     * read-only, but our inotify is for write-only
                     */
                    int newStatus = readFile(filePath);

                    if (newStatus < 0) {
                        mRunning = false;
                        break;
                    }

                    int halStatus = newStatus ?
                        CAMERA_DEVICE_STATUS_PRESENT :
                        CAMERA_DEVICE_STATUS_NOT_PRESENT;
                    gEmulatedCameraFactory.onStatusChanged(cameraId,
                                                           halStatus);
                }

            } else {
                ALOGW("%s: Unknown mask 0x%x",
                      __FUNCTION__, event->mask);
            }

            i += EVENT_SIZE + event->len;
        }
    }

    if (!mRunning) {
        close(mInotifyFd);
        return false;
    }

    return true;
}

String8 EmulatedCameraHotplugThread::getFilePath(int cameraId) const {
    return String8::format(FAKE_HOTPLUG_FILE ".%d", cameraId);
}

bool EmulatedCameraHotplugThread::createFileIfNotExists(int cameraId) const
{
    String8 filePath = getFilePath(cameraId);
    // make sure this file exists and we have access to it
    int fd = TEMP_FAILURE_RETRY(
                open(filePath.string(), O_WRONLY | O_CREAT | O_TRUNC,
                     /* mode = ug+rwx */ S_IRWXU | S_IRWXG ));
    if (fd == -1) {
        ALOGE("%s: Could not create file '%s', error: '%s' (%d)",
             __FUNCTION__, filePath.string(), strerror(errno), errno);
        return false;
    }

    // File has '1' by default since we are plugged in by default
    if (TEMP_FAILURE_RETRY(write(fd, "1\n", /*count*/2)) == -1) {
        ALOGE("%s: Could not write '1' to file '%s', error: '%s' (%d)",
             __FUNCTION__, filePath.string(), strerror(errno), errno);
        return false;
    }

    close(fd);
    return true;
}

int EmulatedCameraHotplugThread::getCameraId(String8 filePath) const {
    Vector<int>::const_iterator it, end;
    it = mSubscribedCameraIds.begin();
    end = mSubscribedCameraIds.end();
    for (; it != end; ++it) {
        String8 camPath = getFilePath(*it);

        if (camPath == filePath) {
            return *it;
        }
    }

    return NAME_NOT_FOUND;
}

int EmulatedCameraHotplugThread::getCameraId(int wd) const {
    for (size_t i = 0; i < mSubscribers.size(); ++i) {
        if (mSubscribers[i].WatchID == wd) {
            return mSubscribers[i].CameraID;
        }
    }

    return NAME_NOT_FOUND;
}

SubscriberInfo* EmulatedCameraHotplugThread::getSubscriberInfo(int cameraId)
{
    for (size_t i = 0; i < mSubscribers.size(); ++i) {
        if (mSubscribers[i].CameraID == cameraId) {
            return (SubscriberInfo*)&mSubscribers[i];
        }
    }

    return NULL;
}

bool EmulatedCameraHotplugThread::addWatch(int cameraId) {
    String8 camPath = getFilePath(cameraId);
    int wd = inotify_add_watch(mInotifyFd,
                               camPath.string(),
                               IN_CLOSE_WRITE);

    if (wd == -1) {
        ALOGE("%s: Could not add watch for '%s', error: '%s' (%d)",
             __FUNCTION__, camPath.string(), strerror(errno),
             errno);

        mRunning = false;
        return false;
    }

    ALOGV("%s: Watch added for camID='%d', wd='%d'",
          __FUNCTION__, cameraId, wd);

    SubscriberInfo si = { cameraId, wd };
    mSubscribers.push_back(si);

    return true;
}

bool EmulatedCameraHotplugThread::removeWatch(int cameraId) {
    SubscriberInfo* si = getSubscriberInfo(cameraId);

    if (!si) return false;

    if (inotify_rm_watch(mInotifyFd, si->WatchID) == -1) {

        ALOGE("%s: Could not remove watch for camID '%d', error: '%s' (%d)",
             __FUNCTION__, cameraId, strerror(errno),
             errno);

        return false;
    }

    Vector<SubscriberInfo>::iterator it;
    for (it = mSubscribers.begin(); it != mSubscribers.end(); ++it) {
        if (it->CameraID == cameraId) {
            break;
        }
    }

    if (it != mSubscribers.end()) {
        mSubscribers.erase(it);
    }

    return true;
}

int EmulatedCameraHotplugThread::readFile(String8 filePath) const {

    int fd = TEMP_FAILURE_RETRY(
                open(filePath.string(), O_RDONLY, /*mode*/0));
    if (fd == -1) {
        ALOGE("%s: Could not open file '%s', error: '%s' (%d)",
             __FUNCTION__, filePath.string(), strerror(errno), errno);
        return -1;
    }

    char buffer[1];
    int length;

    length = TEMP_FAILURE_RETRY(
                    read(fd, buffer, sizeof(buffer)));

    int retval;

    ALOGV("%s: Read file '%s', length='%d', buffer='%c'",
         __FUNCTION__, filePath.string(), length, buffer[0]);

    if (length == 0) { // EOF
        retval = 0; // empty file is the same thing as 0
    } else if (buffer[0] == '0') {
        retval = 0;
    } else { // anything non-empty that's not beginning with '0'
        retval = 1;
    }

    close(fd);

    return retval;
}

} //namespace android