<?php
if (!CURL_ENABLED) define('ACTIVITYPUB_ENABLED', false);

enum LogType {
  case ActivityPub;
  case Auth;
  case Csv;
  case Mailer;
  case MySQL;
}

class Result {
  public bool $isSuccess;
  public ?string $message;

  public function __construct(bool $isSuccess, ?string $message = null) {
    $this->isSuccess = $isSuccess;
    $this->message = $message;
  }

  public static function Success(?string $message = null): self {
    return new self(true, $message);
  }

  public static function Error(string $message): self {
    return new self(false, $message);
  }
}

class Roles {
  // 例： if ($user->role & (Roles::STAFF | Roles::NINTENDONDA))
  public const int BANNED         = 0;
  public const int MEMBER         = 1 << 0; // 1
  public const int NINTENDONDA    = 1 << 1; // 2
  public const int PLAYSTATIONNDA = 1 << 2; // 4
  public const int FULLNDA        = 1 << 3; // 8 (NINTENDONDA + PLAYSTATIONNDA)
  public const int SUBSCRIBER     = 1 << 4; // 16
  public const int NSUBSCRIBER    = 1 << 5; // 32 (NINTENDNDA + SUBSCRIBER)
  public const int PSUBSCRIBER    = 1 << 6; // 64 (PLAYSTATIONNDA + SUBSCRIBER)
  public const int FSUBSCRIBER    = 1 << 7; // 128 (NINTENDNDA + PLAYSTATIONNDA + SUBSCRIBER)
  public const int STAFF          = 1 << 8; // 256
  public const int ADMIN          = 1 << 9; // 512
}

class Gender {
  public const int UNKNOWN = -1; // 不明
  public const int MALE    =  0; // 男性
  public const int FEMALE  =  1; // 女性
}

$colorPalette = [
  'ultradark' => [
    'black'  => '#020102',
    'white'  => '#b3b1b3',
    'grey'   => '#5c535c',
    'yellow' => '#8d8b0d',
    'orange' => '#724e0b',
    'green'  => '#1e6907',
    'purple' => '#410a5a',
    'lime'   => '#198d5b',
    'pink'   => '#9e0ea3',
    'cyan'   => '#1e8c9b',
    'red'    => '#861623',
    'blue'   => '#164a85',
  ],
  'dark' => [
    'black'  => '#120f12',
    'white'  => '#cfcbcf',
    'grey'   => '#746c75',
    'yellow' => '#b8b515',
    'orange' => '#ac7718',
    'green'  => '#2c980c',
    'purple' => '#550f75',
    'lime'   => '#10c074',
    'pink'   => '#c016c6',
    'cyan'   => '#1cbcd0',
    'red'    => '#bc1729',
    'blue'   => '#1a6ecf',
  ],
  'medium' => [
    'black'  => '#232023',
    'white'  => '#f6f6f6',
    'grey'   => '#988f98',
    'yellow' => '#f1ed25',
    'orange' => '#f7a717',
    'green'  => '#2de12c',
    'purple' => '#b421f8',
    'lime'   => '#20f398',
    'pink'   => '#f545f5',
    'cyan'   => '#29d3ff',
    'red'    => '#ee4030',
    'blue'   => '#2687f7',
  ],
  'light' => [
    'black'  => '#443b44',
    'white'  => '#fcfcfc',
    'grey'   => '#bdb4bd',
    'yellow' => '#ecea71',
    'orange' => '#f8c56a',
    'green'  => '#6cf344',
    'purple' => '#ae6bdb',
    'lime'   => '#88ecc1',
    'pink'   => '#ea79d8',
    'cyan'   => '#8ae5ff',
    'red'    => '#f35869',
    'blue'   => '#6aa6eb',
  ],
  'ultralight' => [
    'black'  => '#574d57',
    'white'  => '#ffffff',
    'grey'   => '#d6cfd6',
    'yellow' => '#f5f4cf',
    'orange' => '#f3d6a3',
    'green'  => '#baf3a8',
    'purple' => '#d2bae2',
    'lime'   => '#b6e9d3',
    'pink'   => '#e6b9de',
    'cyan'   => '#c2e8f3',
    'red'    => '#ecb0b7',
    'blue'   => '#bbd4f0',
  ],
];

function uuid(): string {
  $data = random_bytes(16);
  $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
  $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
  return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

function kys(mixed $arg): void {
  if (gettype($arg) == 'boolean') $arg = $arg === true ? 'true' : 'false';
  if (gettype($arg) == 'array' || gettype($arg) == 'object') {
    foreach ($arg as $a) { if (gettype($a) == 'boolean') $a = $a === true ? 'true' : 'false'; }
  }
  echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
  echo '<header style="padding: 10px 0; display: flex; justify-content: space-evenly; background-color: #550f75; margin-bottom: 20px; position: sticky; top: 0;"><div><b>K</b>ILL</div> <div><b>Y</b>OUR</div> <div><b>S</b>ELF</div></header>';
  echo '<pre>';
  print_r($arg);
  echo '<pre>';
  die();
}

function ffs(): void {
  echo '<style>html { color: #fcfcfc; background-color: #232023; } body { margin: 0; }</style>';
  echo '<header style="padding: 10px 0; display: flex; justify-content: space-evenly; background-color: #b61729; margin-bottom: 20px; position: sticky; top: 0;"><div><b>F</b>OR</div> <div><b>F</b>UCKS</div> <div><b>S</b>AKE</div></header>';

  $stack = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 50);
  $st = [];
  unset($stack[0]);
  $i = 0;

  echo '<pre>';
  print_r($stack);
  echo '<pre>';
  foreach ($stack as $s) {
    if (isset($s['file'])) $st[$i]['file'] = $s['file'].(isset($s['line']) ? ':'.$s['line'] : '');
    else $st[$i]['file'] = '';
    if (isset($s['function'])) $st[$i]['func'] = (isset($s['class']) ? $s['class'].(isset($s['type']) ? $s['type'] : '::') : '').$s['function'];
    else $st[$i]['func'] = '';
    if (isset($s['object'])) $st[$i]['objs'] = $s['object'];
    else $st[$i]['objs'] = new \stdClass;
    if (isset($s['args'])) $st[$i]['args'] = $s['args'];
    else $st[$i]['args'] = [];
    $i++;
  }
  unset($stack[$i]);

  foreach ($st as $s) {
    echo '<div>';
    echo '<b>ファイル：</b>';
    echo $s['file'].'<br />';
    echo '<b>関数：</b>';
    echo $s['func'].'<br />';
    echo '<b>オブジェクト：</b>';
    echo print_r($s['objs']).'<br />';
    echo '<b>その他：</b>';
    echo print_r($s['args']).'<br />';
    echo '</div>';
  }
  die();
}

function base58btc_encode(string $bin): string {
  $a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
  $base = 58;
  $num = \gmp_import($bin, 1, GMP_LSW_FIRST | GMP_NATIVE_ENDIAN);
  $res = '';

  if (\gmp_cmp($num, 0) == 0) return '1';

  while (\gmp_cmp($num, 0) > 0) {
    $mod = \gmp_intval(\gmp_mod($num, $base));
    $res = $a[$mod].$res;
    $num = \gmp_div_q($num, $base);
  }

  $bytes = str_split($bin);
  foreach ($bytes as $byte) {
    if (ord($byte) === 0) {
      $res = '1'.$res;
    } else {
      break;
    }
  }

  return $res;
}

function logger(LogType $section, mixed $arg): void {
  if (LOGGING_ENABLED) {
    $success = false;
    $logfile = ROOT.'/log/';

    switch ($section) {
      case LogType::ActivityPub:
        $logfile .= 'ap_log.text';
        $success = true;
        break;
      case LogType::Auth:
        $logfile .= 'auth_log.text';
        $success = true;
        break;
      case LogType::Csv:
        $logfile .= 'csv_log.text';
        $success = true;
        break;
      case LogType::Mailer:
        $logfile .= 'mail_log.text';
        $success = true;
        break;
      case LogType::MySQL:
        $logfile .= 'mysql_log.text';
        $success = true;
        break;
      default:
        break;
    }

    if ($success) file_put_contents($logfile, $arg."\n", FILE_APPEND);
  }
}

function to_money($amount, $lang) {
  $amount = floatval($amount);

  switch (strtolower($lang)) {
    case 'ja':
      if ($amount >= 100000000) {
        $oku = $amount / 100000000;
        return $oku.'億円';
      } else if ($amount >= 10000) {
        $man = $amount / 10000;
        return $man.'万円';
      }
      return number_format($amount, 0).'円';
    case 'en':
      if ($amount >= 1000000000) {
        $billion = $amount / 1000000000;
        return '¥ '.$billion.' billion';
      } else if ($amount >= 1000000) {
        $million = $amount / 1000000;
        return '¥ '.$million.' million';
      } else if ($amount >= 1000) {
        $thousand = $amount / 1000;
        return '¥ '.$thousand.' thousand';
      }
      return '¥ '.number_format($amount, 0);
    default:
      return '¥ '.number_format($amount, 0);
  }
}

function randstr(): string {
  $len = random_int(1, 20);
  return bin2hex(random_bytes($len));
}

function assert_null(mixed $assertion, Throwable|string|null $description = null): bool {
  if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
  if (is_null($assertion)) return true;
  assert(false, $description ?? 'Assertion failed: value is not null');
  return false;
}

function assert_not_null(mixed $assertion, Throwable|string|null $description = null): bool {
  if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
  if (!is_null($assertion)) return true;
  assert(false, $description ?? 'Assertion failed: value is null');
  return false;
}

function assert_unless_success(Result $assertion, Throwable|string|null $description = null): bool {
  if (ini_get('zend.assertions') < 1) trigger_error('assert機能性が無効です。有効するには、php.iniで「zend.assertions = 1」に設定して下さい。', E_USER_WARNING);
  if ($assertion->isSuccess) return true;
  assert(false, $description ?? $assertion->message ?? 'Assertion failed: Result is not successful');
  return false;
}

function getcookie(string $name): string|null {
  return $_COOKIE[$name] ?? null;
}

function namecolor(\stdClass $userData): string {
  $ban = "#888888";
  $male = "#97ACEF";
  $female = "#F185C9";
  $ungender = "#7C60B0";

  $gender = 'color: '.($userData->gender === Gender::MALE ? $male : ($userData->gender === Gender::FEMALE ? $female : $ungender)).';';
  $style = $userData->namecolor ?: ($userData->role !== Roles::BANNED ? $gender : 'color: '.$ban.';');

  $showname = $userData->displayname ?: $userData->username;

  $color = "<span style=\"{$style}\">{$showname}</span>";
  if ($userData->role & (Roles::ADMIN | Roles::STAFF))
    $color .= "<span style=\"font-size: x-small; background: #10c074; border: 1px solid #fcfcfc; border-radius: 10px; padding: 0 0.5em;\">✓</span>";
  $suffix = $userData->gender === Gender::MALE ? 'くん' : ($userData->gender === Gender::FEMALE ? 'ちゃん' : 'さん');

  return $color.$suffix;
}

function make_csrf_token(?bool $force = false): string {
  if (null !== getcookie('csrf_token') && !$force) return getcookie('csrf_token');
  $token = bin2hex(random_bytes(32));
  setcookie('csrf_token', $token, [
    'expires' => time() + 300, // 5分
    'path' => '/',
    'domain' => $_SERVER['SERVER_NAME'],
    'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
    'httponly' => true,
    'samesite' => 'Strict'
  ]);
  return $token;
}

function verify_csrf_token(string $token): bool {
  return null !== getcookie('csrf_token') && hash_equals(getcookie('csrf_token'), $token);
}

function count_special_chars(string $str): int {
  $count = 0;
  $len = strlen($str);

  for ($i = 0; $i < $len; ++$i) {
    if (str_contains('!', $str[$i])) ++$count;
    if (str_contains('"', $str[$i])) ++$count;
    if (str_contains('#', $str[$i])) ++$count;
    if (str_contains('$', $str[$i])) ++$count;
    if (str_contains('%', $str[$i])) ++$count;
    if (str_contains('&', $str[$i])) ++$count;
    if (str_contains('\'', $str[$i])) ++$count;
    if (str_contains('(', $str[$i])) ++$count;
    if (str_contains(')', $str[$i])) ++$count;
    if (str_contains('-', $str[$i])) ++$count;
    if (str_contains('=', $str[$i])) ++$count;
    if (str_contains('^', $str[$i])) ++$count;
    if (str_contains('~', $str[$i])) ++$count;
    if (str_contains('\\', $str[$i])) ++$count;
    if (str_contains('|', $str[$i])) ++$count;
    if (str_contains('[', $str[$i])) ++$count;
    if (str_contains(']', $str[$i])) ++$count;
    if (str_contains(':', $str[$i])) ++$count;
    if (str_contains('@', $str[$i])) ++$count;
    if (str_contains('`', $str[$i])) ++$count;
    if (str_contains('*', $str[$i])) ++$count;
    if (str_contains('{', $str[$i])) ++$count;
    if (str_contains('}', $str[$i])) ++$count;
    if (str_contains(';', $str[$i])) ++$count;
    if (str_contains('+', $str[$i])) ++$count;
    if (str_contains(',', $str[$i])) ++$count;
    if (str_contains('<', $str[$i])) ++$count;
    if (str_contains('.', $str[$i])) ++$count;
    if (str_contains('>', $str[$i])) ++$count;
    if (str_contains('/', $str[$i])) ++$count;
    if (str_contains('?', $str[$i])) ++$count;
    if (str_contains('_', $str[$i])) ++$count;
  }

  return $count;
}

function countmatch(string $str): bool {
  $len = strlen($str);

  $numUpper = preg_match_all('/[A-Z]/', $str) ?? 0;
  $numLower = preg_match_all('/[a-z]/', $str) ?? 0;
  $numDigit = preg_match_all('/[0-9]/', $str) ?? 0;
  $numSymbol = count_special_chars($str);
  $sum = $numUpper + $numLower + $numDigit + $numSymbol;

  return $len == $sum;
}