// -*-C++-*-
// This file is a part of the IncludeOS unikernel - www.includeos.org
//
// Copyright 2015 Oslo and Akershus University College of Applied Sciences
// and Alfred Bratterud
//
// 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
#ifndef API_SMP_HEADER
#define API_SMP_HEADER

#include <delegate>
#include <vector>
#include <smp_utils>

#ifndef SMP_MAX_CORES
# ifdef INCLUDEOS_SMP_ENABLE
#   define SMP_MAX_CORES   32
# else
#   define SMP_MAX_CORES   1
# endif
#endif

#define SMP_ALIGN       64

// access a std::array of T by current CPU id
#define PER_CPU(x) (per_cpu_help<decltype(x)::value_type, SMP_MAX_CORES>(x))

class SMP {
public:
  using task_func = delegate<void()>;
  using done_func = delegate<void()>;

  template <typename T>
  using Array = std::array<T, SMP_MAX_CORES>;

  // return the current CPU id
  static int cpu_id() noexcept;

  // return the number of active CPUs
  static int cpu_count() noexcept;

  // Return the indices of all initialized CPU cores
  static const std::pmr::vector<int>& active_cpus();

  static int active_cpus(int index){
    return active_cpus().at(index);
  }


  // implement this function to execute something on all APs at init
  static void init_task();

  // execute @func on another CPU core
  // call @done back on main CPU when task returns
  // use signal() to broadcast work should begin
  static void add_task(task_func func, done_func done, int cpu = 0);
  static void add_task(task_func func, int cpu = 0);
  // execute a function on the main cpu
  static void add_bsp_task(done_func func);

  // call this to signal that tasks are queued up
  // if cpu == 0, broadcast signal to all
  static void signal(int cpu = 0);
  static void signal_bsp();

  // trigger interrupt on specified CPU
  static void unicast(int cpu, uint8_t intr);
  // broadcast-trigger interrupt on all waiting APs
  static void broadcast(uint8_t intr);

  // a global spinlock to synchronize text output (and other things)
  static void global_lock()   noexcept;
  static void global_unlock() noexcept;
};

//#define SMP_DEBUG 1

// SMP serialized print helpers
#define SMP_ALWAYS_PRINT(fmt, ...) \
    SMP::global_lock();     \
    printf(fmt, ##__VA_ARGS__); \
    SMP::global_unlock();

#ifdef SMP_DEBUG
#define SMP_PRINT(fmt, ...) SMP_ALWAYS_PRINT(fmt, ##__VA_ARGS__)
#define CPULOG(X,...) SMP_PRINT("[C%i]" X,SMP::cpu_id(),##__VA_ARGS__)
#else
#define SMP_PRINT(fmt, ...) /** fmt **/
#define CPULOG(X,...) ;
#endif

#include <array>
#ifdef INCLUDEOS_SMP_ENABLE
  template <typename T, size_t N>
  inline T& per_cpu_help(std::array<T, N>& array)
  {
    unsigned cpuid;
# ifdef ARCH_x86_64
    asm("movl %%gs:(0x0), %0" : "=r" (cpuid));
# elif defined(ARCH_i686)
    asm("movl %%fs:(0x0), %0" : "=r" (cpuid));
# else
    #error "Implement me?"
# endif
    return array.at(cpuid);
  }
#else
  template <typename T, size_t N>
  inline T& per_cpu_help(std::array<T, N>& array)
  {
    return array[0];
  }
#endif

#endif
