// Copyright (c) 2012, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "common/mac/arch_utilities.h"

#include <mach-o/arch.h>
#include <mach-o/fat.h>
#include <stdio.h>
#include <string.h>

#ifndef CPU_SUBTYPE_ARM_V7S
#define CPU_SUBTYPE_ARM_V7S (static_cast<cpu_subtype_t>(11))
#endif  // CPU_SUBTYPE_ARM_V7S

#ifndef CPU_TYPE_ARM64
#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
#endif  // CPU_TYPE_ARM64

#ifndef CPU_SUBTYPE_ARM64_ALL
#define CPU_SUBTYPE_ARM64_ALL (static_cast<cpu_subtype_t>(0))
#endif  // CPU_SUBTYPE_ARM64_ALL

#ifndef CPU_SUBTYPE_ARM64_E
#define CPU_SUBTYPE_ARM64_E (static_cast<cpu_subtype_t>(2))
#endif  // CPU_SUBTYPE_ARM64_E

namespace {

const NXArchInfo* ArchInfo_arm64(cpu_subtype_t cpu_subtype) {
  const char* name = NULL;
  switch (cpu_subtype) {
    case CPU_SUBTYPE_ARM64_ALL:
      name = "arm64";
      break;
    case CPU_SUBTYPE_ARM64_E:
      name = "arm64e";
      break;
    default:
      return NULL;
  }

  NXArchInfo* arm64 = new NXArchInfo;
  *arm64 = *NXGetArchInfoFromCpuType(CPU_TYPE_ARM,
                                     CPU_SUBTYPE_ARM_V7);
  arm64->name = name;
  arm64->cputype = CPU_TYPE_ARM64;
  arm64->cpusubtype = cpu_subtype;
  arm64->description = "arm 64";
  return arm64;
}

const NXArchInfo* ArchInfo_armv7s() {
  NXArchInfo* armv7s = new NXArchInfo;
  *armv7s = *NXGetArchInfoFromCpuType(CPU_TYPE_ARM,
                                      CPU_SUBTYPE_ARM_V7);
  armv7s->name = "armv7s";
  armv7s->cpusubtype = CPU_SUBTYPE_ARM_V7S;
  armv7s->description = "arm v7s";
  return armv7s;
}

}  // namespace

namespace google_breakpad {

const NXArchInfo* BreakpadGetArchInfoFromName(const char* arch_name) {
  // TODO: Remove this when the OS knows about arm64.
  if (!strcmp("arm64", arch_name))
    return BreakpadGetArchInfoFromCpuType(CPU_TYPE_ARM64,
                                          CPU_SUBTYPE_ARM64_ALL);

  if (!strcmp("arm64e", arch_name))
    return BreakpadGetArchInfoFromCpuType(CPU_TYPE_ARM64,
                                          CPU_SUBTYPE_ARM64_E);

  // TODO: Remove this when the OS knows about armv7s.
  if (!strcmp("armv7s", arch_name))
    return BreakpadGetArchInfoFromCpuType(CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S);

  return NXGetArchInfoFromName(arch_name);
}

const NXArchInfo* BreakpadGetArchInfoFromCpuType(cpu_type_t cpu_type,
                                                 cpu_subtype_t cpu_subtype) {
  // TODO: Remove this when the OS knows about arm64.
  if (cpu_type == CPU_TYPE_ARM64 && cpu_subtype == CPU_SUBTYPE_ARM64_ALL) {
    static const NXArchInfo* arm64 = ArchInfo_arm64(cpu_subtype);
    return arm64;
  }

  if (cpu_type == CPU_TYPE_ARM64 && cpu_subtype == CPU_SUBTYPE_ARM64_E) {
    static const NXArchInfo* arm64e = ArchInfo_arm64(cpu_subtype);
    return arm64e;
  }

  // TODO: Remove this when the OS knows about armv7s.
  if (cpu_type == CPU_TYPE_ARM && cpu_subtype == CPU_SUBTYPE_ARM_V7S) {
    static const NXArchInfo* armv7s = ArchInfo_armv7s();
    return armv7s;
  }

  return NXGetArchInfoFromCpuType(cpu_type, cpu_subtype);
}

}  // namespace google_breakpad

#ifndef __APPLE__
namespace {

enum Architecture {
  kArch_i386 = 0,
  kArch_x86_64,
  kArch_x86_64h,
  kArch_arm,
  kArch_arm64,
  kArch_arm64e,
  kArch_ppc,
  // This must be last.
  kNumArchitectures
};

// enum Architecture above and kKnownArchitectures below
// must be kept in sync.
const NXArchInfo kKnownArchitectures[] = {
  {
    "i386",
    CPU_TYPE_I386,
    CPU_SUBTYPE_I386_ALL,
    NX_LittleEndian,
    "Intel 80x86"
  },
  {
    "x86_64",
    CPU_TYPE_X86_64,
    CPU_SUBTYPE_X86_64_ALL,
    NX_LittleEndian,
    "Intel x86-64"
  },
  {
    "x86_64h",
    CPU_TYPE_X86_64,
    CPU_SUBTYPE_X86_64_H,
    NX_LittleEndian,
    "Intel x86-64h Haswell"
  },
  {
    "arm",
    CPU_TYPE_ARM,
    CPU_SUBTYPE_ARM_ALL,
    NX_LittleEndian,
    "ARM"
  },
  {
    "arm64",
    CPU_TYPE_ARM64,
    CPU_SUBTYPE_ARM64_ALL,
    NX_LittleEndian,
    "ARM64"
  },
  {
    "arm64e",
    CPU_TYPE_ARM64,
    CPU_SUBTYPE_ARM64_E,
    NX_LittleEndian,
    "ARM64e"
  },
  {
    "ppc",
    CPU_TYPE_POWERPC,
    CPU_SUBTYPE_POWERPC_ALL,
    NX_BigEndian,
    "PowerPC"
  }
};

}  // namespace

const NXArchInfo *NXGetLocalArchInfo(void) {
  Architecture arch;
#if defined(__i386__)
  arch = kArch_i386;
#elif defined(__x86_64__)
  arch = kArch_x86_64;
#elif defined(__arm64)
  arch = kArch_arm64;
#elif defined(__arm__)
  arch = kArch_arm;
#elif defined(__powerpc__)
  arch = kArch_ppc;
#else
  #error "Unsupported CPU architecture"
#endif
  return &kKnownArchitectures[arch];
}

const NXArchInfo *NXGetArchInfoFromName(const char *name) {
  for (int arch = 0; arch < kNumArchitectures; ++arch) {
    if (!strcmp(name, kKnownArchitectures[arch].name)) {
      return &kKnownArchitectures[arch];
    }
  }
  return NULL;
}

const NXArchInfo *NXGetArchInfoFromCpuType(cpu_type_t cputype,
                                           cpu_subtype_t cpusubtype) {
  const NXArchInfo *candidate = NULL;
  for (int arch = 0; arch < kNumArchitectures; ++arch) {
    if (kKnownArchitectures[arch].cputype == cputype) {
      if (kKnownArchitectures[arch].cpusubtype == cpusubtype) {
        return &kKnownArchitectures[arch];
      }
      if (!candidate) {
        candidate = &kKnownArchitectures[arch];
      }
    }
  }
  return candidate;
}

struct fat_arch *NXFindBestFatArch(cpu_type_t cputype,
                                   cpu_subtype_t cpusubtype,
                                   struct fat_arch *fat_archs,
                                   uint32_t nfat_archs) {
  struct fat_arch *candidate = NULL;
  for (uint32_t f = 0; f < nfat_archs; ++f) {
    if (fat_archs[f].cputype == cputype) {
      if (fat_archs[f].cpusubtype == cpusubtype) {
        return &fat_archs[f];
      }
      if (!candidate) {
        candidate = &fat_archs[f];
      }
    }
  }
  return candidate;
}
#endif  // !__APPLE__
