/*
 * Copyright (C) 2015 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_TAG "hwc-drm-worker"

#include "worker.h"

#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/signal.h>

#include <cutils/log.h>

namespace android {

Worker::Worker(const char *name, int priority)
    : name_(name), priority_(priority), exit_(false), initialized_(false) {
}

Worker::~Worker() {
  if (!initialized_)
    return;

  pthread_kill(thread_, SIGTERM);
  pthread_cond_destroy(&cond_);
  pthread_mutex_destroy(&lock_);
}

int Worker::InitWorker() {
  int ret = pthread_cond_init(&cond_, NULL);
  if (ret) {
    ALOGE("Failed to int thread %s condition %d", name_.c_str(), ret);
    return ret;
  }

  ret = pthread_mutex_init(&lock_, NULL);
  if (ret) {
    ALOGE("Failed to init thread %s lock %d", name_.c_str(), ret);
    pthread_cond_destroy(&cond_);
    return ret;
  }

  ret = pthread_create(&thread_, NULL, InternalRoutine, this);
  if (ret) {
    ALOGE("Could not create thread %s %d", name_.c_str(), ret);
    pthread_mutex_destroy(&lock_);
    pthread_cond_destroy(&cond_);
    return ret;
  }
  initialized_ = true;
  return 0;
}

bool Worker::initialized() const {
  return initialized_;
}

int Worker::Lock() {
  return pthread_mutex_lock(&lock_);
}

int Worker::Unlock() {
  return pthread_mutex_unlock(&lock_);
}

int Worker::SignalLocked() {
  return SignalThreadLocked(false);
}

int Worker::ExitLocked() {
  int signal_ret = SignalThreadLocked(true);
  if (signal_ret)
    ALOGE("Failed to signal thread %s with exit %d", name_.c_str(), signal_ret);

  int join_ret = pthread_join(thread_, NULL);
  if (join_ret && join_ret != ESRCH)
    ALOGE("Failed to join thread %s in exit %d", name_.c_str(), join_ret);

  return signal_ret | join_ret;
}

int Worker::Signal() {
  int ret = Lock();
  if (ret) {
    ALOGE("Failed to acquire lock in Signal() %d\n", ret);
    return ret;
  }

  int signal_ret = SignalLocked();

  ret = Unlock();
  if (ret) {
    ALOGE("Failed to release lock in Signal() %d\n", ret);
    return ret;
  }
  return signal_ret;
}

int Worker::Exit() {
  int ret = Lock();
  if (ret) {
    ALOGE("Failed to acquire lock in Exit() %d\n", ret);
    return ret;
  }

  int exit_ret = ExitLocked();

  ret = Unlock();
  if (ret) {
    ALOGE("Failed to release lock in Exit() %d\n", ret);
    return ret;
  }
  return exit_ret;
}

int Worker::WaitForSignalOrExitLocked() {
  if (exit_)
    return -EINTR;

  int ret = pthread_cond_wait(&cond_, &lock_);

  if (exit_)
    return -EINTR;

  return ret;
}

// static
void *Worker::InternalRoutine(void *arg) {
  Worker *worker = (Worker *)arg;

  setpriority(PRIO_PROCESS, 0, worker->priority_);

  while (true) {
    int ret = worker->Lock();
    if (ret) {
      ALOGE("Failed to lock %s thread %d", worker->name_.c_str(), ret);
      continue;
    }

    bool exit = worker->exit_;

    ret = worker->Unlock();
    if (ret) {
      ALOGE("Failed to unlock %s thread %d", worker->name_.c_str(), ret);
      break;
    }
    if (exit)
      break;

    worker->Routine();
  }
  return NULL;
}

int Worker::SignalThreadLocked(bool exit) {
  if (exit)
    exit_ = exit;

  int ret = pthread_cond_signal(&cond_);
  if (ret) {
    ALOGE("Failed to signal condition on %s thread %d", name_.c_str(), ret);
    return ret;
  }

  return 0;
}
}