/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkTypes.h"

#include "SkThreadUtils.h"
#include "SkThreadUtils_win.h"

SkThread_WinData::SkThread_WinData(SkThread::entryPointProc entryPoint, void* data)
    : fHandle(nullptr)
    , fParam(data)
    , fThreadId(0)
    , fEntryPoint(entryPoint)
    , fStarted(false)
{
    fCancelEvent = CreateEvent(
        nullptr,  // default security attributes
        false, //auto reset
        false, //not signaled
        nullptr); //no name
}

SkThread_WinData::~SkThread_WinData() {
    CloseHandle(fCancelEvent);
}

static DWORD WINAPI thread_start(LPVOID data) {
    SkThread_WinData* winData = static_cast<SkThread_WinData*>(data);

    //See if this thread was canceled before starting.
    if (WaitForSingleObject(winData->fCancelEvent, 0) == WAIT_OBJECT_0) {
        return 0;
    }

    winData->fEntryPoint(winData->fParam);
    return 0;
}

SkThread::SkThread(entryPointProc entryPoint, void* data) {
    SkThread_WinData* winData = new SkThread_WinData(entryPoint, data);
    fData = winData;

    if (nullptr == winData->fCancelEvent) {
        return;
    }

    winData->fHandle = CreateThread(
        nullptr,                   // default security attributes
        0,                      // use default stack size
        thread_start,           // thread function name (proxy)
        winData,                // argument to thread function (proxy args)
        CREATE_SUSPENDED,       // create suspended so affinity can be set
        &winData->fThreadId);   // returns the thread identifier
}

SkThread::~SkThread() {
    if (fData != nullptr) {
        SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
        // If created thread but start was never called, kill the thread.
        if (winData->fHandle != nullptr && !winData->fStarted) {
            if (SetEvent(winData->fCancelEvent) != 0) {
                if (this->start()) {
                    this->join();
                }
            } else {
                //kill with prejudice
                TerminateThread(winData->fHandle, -1);
            }
        }
        delete winData;
    }
}

bool SkThread::start() {
    SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
    if (nullptr == winData->fHandle) {
        return false;
    }

    if (winData->fStarted) {
        return false;
    }
    winData->fStarted = -1 != ResumeThread(winData->fHandle);
    return winData->fStarted;
}

void SkThread::join() {
    SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
    if (nullptr == winData->fHandle || !winData->fStarted) {
        return;
    }

    WaitForSingleObject(winData->fHandle, INFINITE);
}

static unsigned int num_bits_set(DWORD_PTR mask) {
    unsigned int count;
    for (count = 0; mask; ++count) {
        mask &= mask - 1;
    }
    return count;
}

static unsigned int nth_set_bit(unsigned int n, DWORD_PTR mask) {
    n %= num_bits_set(mask);
    for (unsigned int setBitsSeen = 0, currentBit = 0; true; ++currentBit) {
        if (mask & (static_cast<DWORD_PTR>(1) << currentBit)) {
            ++setBitsSeen;
            if (setBitsSeen > n) {
                return currentBit;
            }
        }
    }
}

bool SkThread::setProcessorAffinity(unsigned int processor) {
    SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
    if (nullptr == winData->fHandle) {
        return false;
    }

    DWORD_PTR processAffinityMask;
    DWORD_PTR systemAffinityMask;
    if (0 == GetProcessAffinityMask(GetCurrentProcess(),
                                    &processAffinityMask,
                                    &systemAffinityMask)) {
        return false;
    }

    DWORD_PTR threadAffinityMask = 1 << nth_set_bit(processor, processAffinityMask);
    return 0 != SetThreadAffinityMask(winData->fHandle, threadAffinityMask);
}
