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

#pragma once

#include <dlfcn.h>
#include <memory>

#include <android/log.h>
#include <android/trace.h>

namespace gamesdk {

class Trace {
  public:
    using ATrace_beginSection_type = void (*)(const char *sectionName);
    using ATrace_endSection_type = void (*)();
    using ATrace_isEnabled_type = bool (*)();

    Trace() {
        __android_log_print(ANDROID_LOG_INFO, "Trace", "Unable to load NDK tracing APIs");
    }

    Trace(ATrace_beginSection_type beginSection,
          ATrace_endSection_type endSection,
          ATrace_isEnabled_type isEnabled)
        : ATrace_beginSection(beginSection),
          ATrace_endSection(endSection),
          ATrace_isEnabled(isEnabled) {}

    static std::unique_ptr<Trace> create() {
        void *libandroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
        if (!libandroid) {
            return std::make_unique<Trace>();
        }

        auto beginSection = reinterpret_cast<ATrace_beginSection_type>(
            dlsym(libandroid, "ATrace_beginSection"));
        if (!beginSection) {
            return std::make_unique<Trace>();
        }

        auto endSection = reinterpret_cast<ATrace_endSection_type>(
            dlsym(libandroid, "ATrace_endSection"));
        if (!endSection) {
            return std::make_unique<Trace>();
        }

        auto isEnabled = reinterpret_cast<ATrace_isEnabled_type>(
            dlsym(libandroid, "ATrace_isEnabled"));
        if (!isEnabled) {
            return std::make_unique<Trace>();
        }

        return std::make_unique<Trace>(beginSection, endSection, isEnabled);
    }

    bool isAvailable() const {
        return ATrace_beginSection != nullptr;
    }

    bool isEnabled() const {
        return (ATrace_isEnabled != nullptr) && ATrace_isEnabled();
    }

    void beginSection(const char *name) const {
        if (!ATrace_beginSection) {
            return;
        }

        ATrace_beginSection(name);
    }

    void endSection() const {
        if (!ATrace_endSection) {
            return;
        }

        ATrace_endSection();
    }

    static Trace *getInstance() {
        static std::unique_ptr<Trace> trace = Trace::create();
        return trace.get();
    };

  private:
    const ATrace_beginSection_type ATrace_beginSection = nullptr;
    const ATrace_endSection_type ATrace_endSection = nullptr;
    const ATrace_isEnabled_type ATrace_isEnabled = nullptr;
};

struct ScopedTrace {
    ScopedTrace(const char *name) {
        Trace *trace = Trace::getInstance();
        if (!trace->isAvailable() || !trace->isEnabled()) {
            return;
        }

        trace->beginSection(name);
        mIsTracing = true;
    }

    ~ScopedTrace() {
        if (!mIsTracing) {
            return;
        }

        Trace *trace = Trace::getInstance();
        trace->endSection();
    }

  private:
    bool mIsTracing = false;
};

} // namespace gamesdk

#define PASTE_HELPER_HELPER(a, b) a ## b
#define PASTE_HELPER(a, b) PASTE_HELPER_HELPER(a, b)
#define TRACE_CALL() gamesdk::ScopedTrace PASTE_HELPER(scopedTrace, __LINE__)(__PRETTY_FUNCTION__)