/*
 *
 * Copyright 2017, 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.
 */

#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_THREADED_HANDLER_H_
#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_THREADED_HANDLER_H_

#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <functional>

#include <inttypes.h>
#include <unistd.h>

#include <android-base/logging.h>
#include <android-base/stringprintf.h>

#include "perfprofd_record.pb.h"

#include "config.h"
#include "dropbox.h"
#include "perfprofdcore.h"
#include "perfprofd_io.h"

namespace android {
namespace perfprofd {

class ThreadedConfig : public Config {
 public:
  void Sleep(size_t seconds) override {
    if (seconds == 0) {
      return;
    }
    std::unique_lock<std::mutex> guard(mutex_);
    using namespace std::chrono_literals;
    cv_.wait_for(guard, seconds * 1s, [&]() { return interrupted_; });
  }
  bool ShouldStopProfiling() override {
    std::unique_lock<std::mutex> guard(mutex_);
    return interrupted_;
  }

  void ResetStopProfiling() {
    std::unique_lock<std::mutex> guard(mutex_);
    interrupted_ = false;
  }
  void StopProfiling() {
    std::unique_lock<std::mutex> guard(mutex_);
    interrupted_ = true;
    cv_.notify_all();
  }

  bool IsProfilingEnabled() const override {
    return true;
  }

  // Operator= to simplify setting the config values. This will retain the
  // original mutex, condition-variable etc.
  ThreadedConfig& operator=(const ThreadedConfig& rhs) {
    // Copy base fields.
    *static_cast<Config*>(this) = static_cast<const Config&>(rhs);

    return *this;
  }

 private:
  bool is_profiling = false;
  std::mutex mutex_;
  std::condition_variable cv_;
  bool interrupted_ = false;

  friend class ThreadedHandler;
};

class ThreadedHandler  {
 public:
  ThreadedHandler() : cur_config_(new ThreadedConfig()) {}
  explicit ThreadedHandler(ThreadedConfig* in) : cur_config_(in) {
    CHECK(cur_config_ != nullptr);
  }

  virtual ~ThreadedHandler() {}

  template <typename ConfigFn> bool StartProfiling(ConfigFn fn, std::string* error_msg) {
    std::lock_guard<std::mutex> guard(lock_);

    if (cur_config_->is_profiling) {
      *error_msg = "Already profiling";
      return false;
    }
    cur_config_->is_profiling = true;
    cur_config_->ResetStopProfiling();

    fn(*cur_config_);

    HandlerFn handler = GetResultHandler();
    auto profile_runner = [handler](ThreadedHandler* service) {
      ProfilingLoop(*service->cur_config_, handler);

      // This thread is done.
      std::lock_guard<std::mutex> unset_guard(service->lock_);
      service->cur_config_->is_profiling = false;
    };
    std::thread profiling_thread(profile_runner, this);
    profiling_thread.detach();  // Let it go.

    return true;
  }

  bool StopProfiling(std::string* error_msg) {
    std::lock_guard<std::mutex> guard(lock_);
    if (!cur_config_->is_profiling) {
      *error_msg = "Not profiling";
      return false;
    }

    cur_config_->StopProfiling();

    return true;
  }

 protected:
  // Handler for ProfilingLoop.
  virtual bool ResultHandler(android::perfprofd::PerfprofdRecord* encodedProfile,
                             Config* config) {
    CHECK(config != nullptr);
    if (encodedProfile == nullptr) {
      return false;
    }

    if (static_cast<ThreadedConfig*>(config)->send_to_dropbox) {
      std::string error_msg;
      if (!dropbox::SendToDropbox(encodedProfile, config->destination_directory, &error_msg)) {
        LOG(WARNING) << "Failed dropbox submission: " << error_msg;
        return false;
      }
      return true;
    }

    if (encodedProfile == nullptr) {
      return false;
    }
    std::string data_file_path(config->destination_directory);
    data_file_path += "/perf.data";
    std::string path = android::base::StringPrintf("%s.encoded.%d", data_file_path.c_str(), seq_);
    if (!SerializeProtobuf(encodedProfile, path.c_str(), config->compress)) {
      return false;
    }

    seq_++;
    return true;
  }

  template <typename Fn>
  void RunOnConfig(Fn& fn) {
    std::lock_guard<std::mutex> guard(lock_);
    fn(cur_config_->is_profiling, cur_config_.get());
  }

 private:
  // Helper for the handler.
  HandlerFn GetResultHandler() {
    return HandlerFn(std::bind(&ThreadedHandler::ResultHandler,
                               this,
                               std::placeholders::_1,
                               std::placeholders::_2));
  }

  std::mutex lock_;

  std::unique_ptr<ThreadedConfig> cur_config_;

  int seq_ = 0;
};

}  // namespace perfprofd
}  // namespace android

#endif  // SYSTEM_EXTRAS_PERFPROFD_PERFPROFD_THREADED_HANDLER_H_