<?php

/**
 * Manage AJAX requests for the Lockee plugin
 */
class Lockee_Ajax extends Lockee_Singleton
{
  /**
   * Init the Ajax verification functionnality
   */
  protected function init() {}

  /**
   * Verify the code via AJAX
   */
  public function verify_code()
  {
    if (empty($_GET["id"]) || !isset($_GET["code"]) || ($_GET["code"] == '')) {
      $this->send_json_response(0, "", 1);
      return;
    }

    $id = intval($_GET['id']);
    $code = htmlspecialchars($_GET['code']);
    $lock_type = get_post_meta($id, 'lockee_type', true);
    $lock_options = get_post_meta($id, 'lockee_options', true);

    $open = $this->test_code($id, $code, $lock_type, $lock_options);

    if (get_post_type($id) == 'lockee_lock') {
      $post_content = apply_filters('the_content', get_post_field('post_content', $id));
    } else {
      $post_content = get_post_meta($id, 'lockee_content', true);
      if (empty($post_content)) {
        $post_content = apply_filters('the_content', get_post_field('post_content', $id));
      }
    }

    $nbtest = get_post_meta($id, 'lockee_test', true) || 0;
    update_post_meta($id, 'lockee_test', $nbtest + 1);
    if ($open) {
      $nbopen = get_post_meta($id, 'lockee_open', true) || 0;
      update_post_meta($id, 'lockee_open', $nbopen + 1);
    }

    $content = $open ? '<div class="lck-content">' . $post_content . '</div>' : '';

    $this->send_json_response($open, $content, 0);
  }

  /**
   * Send a JSON response
   */
  private function send_json_response($open, $content, $error)
  {
    $response = [
      "open" => $open,
      "content" => $content,
      "linked" => "",
      "redirect" => 0,
      "top" => 0,
      "error" => $error,
    ];
    echo json_encode($response);
    wp_die();
  }

  /**
   * Test a code against the stored codes
   */
  private function test_code($id, $code, $type, $options)
  {
    $codes = $this->get_codes($id);

    switch ($type) {
      case "G":
      case "G1":
      case "G2":
        return $this->test_geolocation_code($code, $codes, $type, $options);
      case "P":
        return $this->test_password_code($code, $codes, $options);
      case "L":
        return $this->test_login_code($code, $codes, $options);
      case "M":
      case "M2":
        return $this->test_musical_code($code, $codes, $options);
      case "O":
        return $this->test_switch_code($code, $codes, $options);
      default:
        return in_array($code, $codes, true) ? 1 : 0;
    }
  }

  /**
   * Get the codes from the post meta
   */
  private function get_codes($id)
  {
    $lock_code = get_post_meta($id, 'lockee_code', true);
    return !empty($lock_code) ? explode("<OR>", $lock_code) : [];
  }

  /**
   * Test a geolocation code
   */
  private function test_geolocation_code($code, $codes, $type, $options)
  {
    $tcode1 = explode(";", $code);
    $rla1 = deg2rad($tcode1[0]);
    $rlo1 = deg2rad($tcode1[1]);
    $earth_radius = 6378137;

    foreach ($codes as $c) {
      $tcode2 = explode(";", $c);
      if ($tcode1[0] == $tcode2[0] && $tcode1[1] == $tcode2[1]) {
        return 1;
      }

      $rla2 = deg2rad($tcode2[0]);
      $rlo2 = deg2rad($tcode2[1]);
      $dlo = ($rlo2 - $rlo1) / 2;
      $dla = ($rla2 - $rla1) / 2;
      $a = sin($dla) * sin($dla) + cos($rla1) * cos($rla2) * (sin($dlo) * sin($dlo));
      $d = 2 * atan2(sqrt($a), sqrt(1 - $a));
      $dist = $earth_radius * $d;

      $limit = $type === "G2" ? $tcode2[2] : min($tcode1[2] + $tcode2[2], 50);
      if ($dist < $limit) {
        return 1;
      }
    }
    return 0;
  }

  /**
   * Test a password code
   */
  private function test_password_code($code, $codes, $options)
  {
    foreach ($codes as $c) {
      if ($this->ignoreTypes($c, $options) === $this->ignoreTypes($code, $options)) {
        return 1;
      }
    }
    return 0;
  }

  /**
   * Test a login code
   */
  private function test_login_code($code, $codes, $options)
  {
    $tcode = explode("#-~~-#", $code);
    foreach ($codes as $c) {
      $tcodes = explode("#-~~-#", $c);
      if (
        $this->ignoreTypes($tcodes[0], $options) === $this->ignoreTypes($tcode[0], $options)
        && $this->ignoreTypes($tcodes[1], $options) === $this->ignoreTypes($tcode[1], $options)
      ) {
        return 1;
      }
    }
    return 0;
  }

  /**
   * Test a musical code
   */
  private function test_musical_code($code, $codes, $options)
  {
    $code = $this->flatToSharp($code);
    foreach ($codes as $c) {
      $c = $this->flatToSharp($c);
      if (strpos($options, "V") === false && strpos($options, "Q") >= 0) {
        $code = $this->filterSilence($code);
        $c = $this->filterSilence($c);
      }
      if ($code === $c) {
        return 1;
      }
    }
    return 0;
  }

  /**
   * Test a switch code
   */
  private function test_switch_code($code, $codes, $options)
  {
    foreach ($codes as $c) {
      if (strpos($options, "Q") === false) {
        $code = $this->orderSequence($code);
        $c = $this->orderSequence($c);
      }
      if ($code === $c) {
        return 1;
      }
    }
    return 0;
  }

  /**
   * Convert flat notes to sharp notes
   */
  private function flatToSharp($txt)
  {
    $replacements = [
      "Db" => "C#",
      "Eb" => "D#",
      "Gb" => "F#",
      "Ab" => "G#",
      "Bb" => "A#"
    ];
    return str_replace(array_keys($replacements), array_values($replacements), $txt);
  }

  /**
   * Filter silence in musical codes
   */
  private function filterSilence($txt)
  {
    $txt = str_replace("_", "", $txt);
    return str_replace("  ", " ", $txt);
  }

  /**
   * Order the sequence
   */
  private function orderSequence($seq)
  {
    $seqTab = str_split($seq);
    sort($seqTab);
    return implode("", $seqTab);
  }

  /**
   * Ignore certain types of characters in a string
   */
  private function ignoreTypes($txt, $types)
  {
    $txt = htmlspecialchars_decode($txt);
    $txt = str_replace("'", "'", $txt);

    if (strpos($types, "A") !== false) {
      $txt = htmlentities($txt, ENT_NOQUOTES, "utf-8");
      $txt = preg_replace("#&([A-za-z])(?:acute|cedil|caron|circ|grave|orn|ring|slash|th|tilde|uml);#", '\1', $txt);
      $txt = preg_replace("#&([A-za-z]{2})(?:lig);#", '\1', $txt);
      $txt = html_entity_decode($txt);
    }

    if (strpos($types, "P") !== false) {
      $txt = preg_replace("/\p{P}/", "", $txt);
      $exceptions = ["'", "#\h#u", " "];
      $txt = str_replace($exceptions, "", $txt);
    }

    if (strpos($types, "C") !== false) {
      $txt = mb_strtolower($txt, "UTF-8");
    }

    return $txt;
  }
}
