// Copyright 2017 Google LLC
//
// 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.

// This program dumps current host data to the standard output.
// Output can be text or json if the `--json` flag is passed.

#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cpu_features_macros.h"

#if defined(CPU_FEATURES_ARCH_X86)
#include "cpuinfo_x86.h"
#elif defined(CPU_FEATURES_ARCH_ARM)
#include "cpuinfo_arm.h"
#elif defined(CPU_FEATURES_ARCH_AARCH64)
#include "cpuinfo_aarch64.h"
#elif defined(CPU_FEATURES_ARCH_MIPS)
#include "cpuinfo_mips.h"
#elif defined(CPU_FEATURES_ARCH_PPC)
#include "cpuinfo_ppc.h"
#endif

// Design principles
// -----------------
// We build a tree structure containing all the data to be displayed.
// Then depending on the output type (text or json) we walk the tree and display
// the data accordingly.

// We use a bump allocator to allocate strings and nodes of the tree,
// Memory is not intended to be reclaimed.
typedef struct {
  char* ptr;
  size_t size;
} BumpAllocator;

char gGlobalBuffer[64 * 1024];
BumpAllocator gBumpAllocator = {.ptr = gGlobalBuffer,
                                .size = sizeof(gGlobalBuffer)};

static void internal_error() {
  fputs("internal error\n", stderr);
  exit(EXIT_FAILURE);
}

#define ALIGN 8

static void assertAligned() {
  if ((uintptr_t)(gBumpAllocator.ptr) % ALIGN) internal_error();
}

static void BA_Align() {
  while (gBumpAllocator.size && (uintptr_t)(gBumpAllocator.ptr) % ALIGN) {
    --gBumpAllocator.size;
    ++gBumpAllocator.ptr;
  }
  assertAligned();
}

// Update the available memory left in the BumpAllocator.
static void* BA_Bump(size_t size) {
  assertAligned();
  // Align size to next 8B boundary.
  size = (size + ALIGN - 1) / ALIGN * ALIGN;
  if (gBumpAllocator.size < size) internal_error();
  void* ptr = gBumpAllocator.ptr;
  gBumpAllocator.size -= size;
  gBumpAllocator.ptr += size;
  return ptr;
}

// The type of the nodes in the tree.
typedef enum {
  NT_INVALID,
  NT_INT,
  NT_MAP,
  NT_MAP_ENTRY,
  NT_ARRAY,
  NT_ARRAY_ELEMENT,
  NT_STRING,
} NodeType;

// The node in the tree.
typedef struct Node {
  NodeType type;
  unsigned integer;
  const char* string;
  struct Node* value;
  struct Node* next;
} Node;

// Creates an initialized Node.
static Node* BA_CreateNode(NodeType type) {
  Node* tv = (Node*)BA_Bump(sizeof(Node));
  assert(tv);
  *tv = (Node){.type = type};
  return tv;
}

// Adds an integer node.
static Node* CreateInt(int value) {
  Node* tv = BA_CreateNode(NT_INT);
  tv->integer = value;
  return tv;
}

// Adds a string node.
// `value` must outlive the tree.
static Node* CreateConstantString(const char* value) {
  Node* tv = BA_CreateNode(NT_STRING);
  tv->string = value;
  return tv;
}

// Adds a map node.
static Node* CreateMap() { return BA_CreateNode(NT_MAP); }

// Adds an array node.
static Node* CreateArray() { return BA_CreateNode(NT_ARRAY); }

// Adds a formatted string node.
static Node* CreatePrintfString(const char* format, ...) {
  va_list arglist;
  va_start(arglist, format);
  char* const ptr = gBumpAllocator.ptr;
  const int written = vsnprintf(ptr, gBumpAllocator.size, format, arglist);
  va_end(arglist);
  if (written < 0 || written >= (int)gBumpAllocator.size) internal_error();
  return CreateConstantString((char*)BA_Bump(written));
}

// Adds a string node.
static Node* CreateString(const char* value) {
  return CreatePrintfString("%s", value);
}

// Adds a map entry node.
static void AddMapEntry(Node* map, const char* key, Node* value) {
  assert(map && map->type == NT_MAP);
  Node* current = map;
  while (current->next) current = current->next;
  current->next = (Node*)BA_Bump(sizeof(Node));
  *current->next = (Node){.type = NT_MAP_ENTRY, .string = key, .value = value};
}

// Adds an array element node.
static void AddArrayElement(Node* array, Node* value) {
  assert(array && array->type == NT_ARRAY);
  Node* current = array;
  while (current->next) current = current->next;
  current->next = (Node*)BA_Bump(sizeof(Node));
  *current->next = (Node){.type = NT_ARRAY_ELEMENT, .value = value};
}

static int cmp(const void* p1, const void* p2) {
  return strcmp(*(const char* const*)p1, *(const char* const*)p2);
}

#define DEFINE_ADD_FLAGS(HasFeature, FeatureName, FeatureType, LastEnum) \
  static void AddFlags(Node* map, const FeatureType* features) {         \
    size_t i;                                                            \
    const char* ptrs[LastEnum] = {0};                                    \
    size_t count = 0;                                                    \
    for (i = 0; i < LastEnum; ++i) {                                     \
      if (HasFeature(features, i)) {                                     \
        ptrs[count] = FeatureName(i);                                    \
        ++count;                                                         \
      }                                                                  \
    }                                                                    \
    qsort((void*)ptrs, count, sizeof(char*), cmp);                       \
    Node* const array = CreateArray();                                   \
    for (i = 0; i < count; ++i)                                          \
      AddArrayElement(array, CreateConstantString(ptrs[i]));             \
    AddMapEntry(map, "flags", array);                                    \
  }

#if defined(CPU_FEATURES_ARCH_X86)
DEFINE_ADD_FLAGS(GetX86FeaturesEnumValue, GetX86FeaturesEnumName, X86Features,
                 X86_LAST_)
#elif defined(CPU_FEATURES_ARCH_ARM)
DEFINE_ADD_FLAGS(GetArmFeaturesEnumValue, GetArmFeaturesEnumName, ArmFeatures,
                 ARM_LAST_)
#elif defined(CPU_FEATURES_ARCH_AARCH64)
DEFINE_ADD_FLAGS(GetAarch64FeaturesEnumValue, GetAarch64FeaturesEnumName,
                 Aarch64Features, AARCH64_LAST_)
#elif defined(CPU_FEATURES_ARCH_MIPS)
DEFINE_ADD_FLAGS(GetMipsFeaturesEnumValue, GetMipsFeaturesEnumName,
                 MipsFeatures, MIPS_LAST_)
#elif defined(CPU_FEATURES_ARCH_PPC)
DEFINE_ADD_FLAGS(GetPPCFeaturesEnumValue, GetPPCFeaturesEnumName, PPCFeatures,
                 PPC_LAST_)
#endif

// Prints a json string with characters escaping.
static void printJsonString(const char* str) {
  putchar('"');
  for (; str && *str; ++str) {
    switch (*str) {
      case '\"':
      case '\\':
      case '/':
      case '\b':
      case '\f':
      case '\n':
      case '\r':
      case '\t':
        putchar('\\');
    }
    putchar(*str);
  }
  putchar('"');
}

// Walks a Node and print it as json.
static void printJson(const Node* current) {
  assert(current);
  switch (current->type) {
    case NT_INVALID:
      break;
    case NT_INT:
      printf("%d", current->integer);
      break;
    case NT_STRING:
      printJsonString(current->string);
      break;
    case NT_ARRAY:
      putchar('[');
      if (current->next) printJson(current->next);
      putchar(']');
      break;
    case NT_MAP:
      putchar('{');
      if (current->next) printJson(current->next);
      putchar('}');
      break;
    case NT_MAP_ENTRY:
      printf("\"%s\":", current->string);
      printJson(current->value);
      if (current->next) {
        putchar(',');
        printJson(current->next);
      }
      break;
    case NT_ARRAY_ELEMENT:
      printJson(current->value);
      if (current->next) {
        putchar(',');
        printJson(current->next);
      }
      break;
  }
}

// Walks a Node and print it as text.
static void printTextField(const Node* current) {
  switch (current->type) {
    case NT_INVALID:
      break;
    case NT_INT:
      printf("%3d (0x%02X)", current->integer, current->integer);
      break;
    case NT_STRING:
      fputs(current->string, stdout);
      break;
    case NT_ARRAY:
      if (current->next) printTextField(current->next);
      break;
    case NT_MAP:
      if (current->next) {
        printf("{");
        printJson(current->next);
        printf("}");
      }
      break;
    case NT_MAP_ENTRY:
      printf("%-15s : ", current->string);
      printTextField(current->value);
      if (current->next) {
        putchar('\n');
        printTextField(current->next);
      }
      break;
    case NT_ARRAY_ELEMENT:
      printTextField(current->value);
      if (current->next) {
        putchar(',');
        printTextField(current->next);
      }
      break;
  }
}

static void printTextRoot(const Node* current) {
  if (current->type == NT_MAP && current->next) printTextField(current->next);
}

static void showUsage(const char* name) {
  printf(
      "\n"
      "Usage: %s [options]\n"
      "      Options:\n"
      "      -h | --help     Show help message.\n"
      "      -j | --json     Format output as json instead of plain text.\n"
      "\n",
      name);
}

static Node* GetCacheTypeString(CacheType cache_type) {
  switch (cache_type) {
    case CPU_FEATURE_CACHE_NULL:
      return CreateConstantString("null");
    case CPU_FEATURE_CACHE_DATA:
      return CreateConstantString("data");
    case CPU_FEATURE_CACHE_INSTRUCTION:
      return CreateConstantString("instruction");
    case CPU_FEATURE_CACHE_UNIFIED:
      return CreateConstantString("unified");
    case CPU_FEATURE_CACHE_TLB:
      return CreateConstantString("tlb");
    case CPU_FEATURE_CACHE_DTLB:
      return CreateConstantString("dtlb");
    case CPU_FEATURE_CACHE_STLB:
      return CreateConstantString("stlb");
    case CPU_FEATURE_CACHE_PREFETCH:
      return CreateConstantString("prefetch");
  }
  UNREACHABLE();
}

static void AddCacheInfo(Node* root, const CacheInfo* cache_info) {
  Node* array = CreateArray();
  for (int i = 0; i < cache_info->size; ++i) {
    CacheLevelInfo info = cache_info->levels[i];
    Node* map = CreateMap();
    AddMapEntry(map, "level", CreateInt(info.level));
    AddMapEntry(map, "cache_type", GetCacheTypeString(info.cache_type));
    AddMapEntry(map, "cache_size", CreateInt(info.cache_size));
    AddMapEntry(map, "ways", CreateInt(info.ways));
    AddMapEntry(map, "line_size", CreateInt(info.line_size));
    AddMapEntry(map, "tlb_entries", CreateInt(info.tlb_entries));
    AddMapEntry(map, "partitioning", CreateInt(info.partitioning));
    AddArrayElement(array, map);
  }
  AddMapEntry(root, "cache_info", array);
}

static Node* CreateTree() {
  Node* root = CreateMap();
#if defined(CPU_FEATURES_ARCH_X86)
  char brand_string[49];
  const X86Info info = GetX86Info();
  const CacheInfo cache_info = GetX86CacheInfo();
  FillX86BrandString(brand_string);
  AddMapEntry(root, "arch", CreateString("x86"));
  AddMapEntry(root, "brand", CreateString(brand_string));
  AddMapEntry(root, "family", CreateInt(info.family));
  AddMapEntry(root, "model", CreateInt(info.model));
  AddMapEntry(root, "stepping", CreateInt(info.stepping));
  AddMapEntry(root, "uarch",
              CreateString(
                  GetX86MicroarchitectureName(GetX86Microarchitecture(&info))));
  AddFlags(root, &info.features);
  AddCacheInfo(root, &cache_info);
#elif defined(CPU_FEATURES_ARCH_ARM)
  const ArmInfo info = GetArmInfo();
  AddMapEntry(root, "arch", CreateString("ARM"));
  AddMapEntry(root, "implementer", CreateInt(info.implementer));
  AddMapEntry(root, "architecture", CreateInt(info.architecture));
  AddMapEntry(root, "variant", CreateInt(info.variant));
  AddMapEntry(root, "part", CreateInt(info.part));
  AddMapEntry(root, "revision", CreateInt(info.revision));
  AddFlags(root, &info.features);
#elif defined(CPU_FEATURES_ARCH_AARCH64)
  const Aarch64Info info = GetAarch64Info();
  AddMapEntry(root, "arch", CreateString("aarch64"));
  AddMapEntry(root, "implementer", CreateInt(info.implementer));
  AddMapEntry(root, "variant", CreateInt(info.variant));
  AddMapEntry(root, "part", CreateInt(info.part));
  AddMapEntry(root, "revision", CreateInt(info.revision));
  AddFlags(root, &info.features);
#elif defined(CPU_FEATURES_ARCH_MIPS)
  const MipsInfo info = GetMipsInfo();
  AddMapEntry(root, "arch", CreateString("mips"));
  AddFlags(root, &info.features);
#elif defined(CPU_FEATURES_ARCH_PPC)
  const PPCInfo info = GetPPCInfo();
  const PPCPlatformStrings strings = GetPPCPlatformStrings();
  AddMapEntry(root, "arch", CreateString("ppc"));
  AddMapEntry(root, "platform", CreateString(strings.platform));
  AddMapEntry(root, "model", CreateString(strings.model));
  AddMapEntry(root, "machine", CreateString(strings.machine));
  AddMapEntry(root, "cpu", CreateString(strings.cpu));
  AddMapEntry(root, "instruction", CreateString(strings.type.platform));
  AddMapEntry(root, "microarchitecture",
              CreateString(strings.type.base_platform));
  AddFlags(root, &info.features);
#endif
  return root;
}

int main(int argc, char** argv) {
  BA_Align();
  const Node* const root = CreateTree();
  bool outputJson = false;
  int i = 1;
  for (; i < argc; ++i) {
    const char* arg = argv[i];
    if (strcmp(arg, "-j") == 0 || strcmp(arg, "--json") == 0) {
      outputJson = true;
    } else {
      showUsage(argv[0]);
      if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0)
        return EXIT_SUCCESS;
      return EXIT_FAILURE;
    }
  }
  if (outputJson)
    printJson(root);
  else
    printTextRoot(root);
  putchar('\n');
  return EXIT_SUCCESS;
}
