<?php

/**
 * High level functionality for interacting with the Patreon API.
 *
 * @author Jon Ziebell
 */
class patreon extends external_api {

  public static $exposed = [
    'private' => [
      'authorize',
      'initialize'
    ],
    'public' => []
  ];

  protected static $log_mysql = 'all';

  protected static $cache = false;
  protected static $cache_for = null;

  /**
   * If the original API call fails, the patreon token is updated outside of
   * the current transaction to ensure it doesn't get rolled back due to an
   * exception.
   *
   * The problem with that is that all subsequent API calls need this
   * value...so it's stored here statically on the class and used instead of
   * the old database value.
   */
  protected static $patreon_token = null;

  /**
   * Redirect to Patreon to do the oAuth. Note: Put a space between scopes and
   * urlencode the whole thing if it includes special characters.
   */
  public function authorize() {
    header('Location: https://www.patreon.com/oauth2/authorize?response_type=code&client_id=' . $this->setting->get('patreon_client_id') . '&redirect_uri=' . $this->setting->get('patreon_redirect_uri') . '&scope=identity');
  }

  /**
   * Obtain the first set of tokens for a a patreon user, then sync that
   * user's Patreon settings, then return code that closes the window.
   *
   * @param string $code The code used to get tokens from patreon with.
   */
  public function initialize($code = null) {
    if($code !== null) {
      $this->api('patreon_token', 'obtain', ['code' => $code]);
      $this->api('user', 'sync_patreon_status');
    }

    echo '<html><head><title></title></head><body><script type="text/javascript">window.close();</script></body></html><!--';

    $this->request->set_headers([
      'Content-Type' => 'text/html'
    ], true);

    // Need to commit the transaction so the stuff gets saved.
    $this->database->commit_transaction();
  }

  /**
   * Send an API call to patreon and return the response.
   *
   * @param string $method GET or POST
   * @param string $endpoint The API endpoint
   * @param array $arguments POST or GET parameters
   * @param boolean $auto_refresh_token Whether or not to automatically get a
   * new token if the old one is expired.
   *
   * @return array The response of this API call.
   */
  public function patreon_api($method, $endpoint, $arguments, $auto_refresh_token = true) {
    $curl = [
      'method' => $method
    ];

    // Authorize/token endpoints don't use the /1/ in the URL. Everything else
    // does.
    $full_endpoint = $endpoint;
    if ($full_endpoint !== 'authorize' && $full_endpoint !== 'token') {
      $full_endpoint = '/v2/' . $full_endpoint;

      // For non-authorization endpoints, add the access_token header. Will use
      // provided token if set, otherwise will get the one for the logged in
      // user.
      if(self::$patreon_token === null) {
        $patreon_tokens = $this->api(
          'patreon_token',
          'read',
          []
        );
        if(count($patreon_tokens) !== 1) {
          throw new Exception('No token for this user');
        }
        $patreon_token = $patreon_tokens[0];
      } else {
        $patreon_token = self::$patreon_token;
      }

      $curl['header'] = [
        'Authorization: Bearer ' . $patreon_token['access_token'],
        'Content-Type: application/x-www-form-urlencoded'
      ];
    }
    else {
      $full_endpoint = '/' . $full_endpoint;
    }
    $curl['url'] = 'https://www.patreon.com/api/oauth2' . $full_endpoint;

    if ($method === 'GET') {
      $curl['url'] .= '?' . http_build_query($arguments);
    }

    if ($method === 'POST') {
      // Attach the client_id to all POST requests. It errors if you include it
      // in a GET.
      $arguments['client_id'] = $this->setting->get('patreon_client_id');

      $curl['post_fields'] = http_build_query($arguments);
    }

    $curl_response = $this->curl($curl);

    $response = json_decode($curl_response, true);
    if ($response === null) {
      // If this hasn't already been logged, log the error.
      if($this::$log_mysql !== 'all') {
        $this->log_mysql($curl_response);
      }
      throw new Exception('Invalid JSON');
    }

    // If the token was expired, refresh it and try again. Trying again sets
    // auto_refresh_token to false to prevent accidental infinite refreshing if
    // something bad happens.
    if (isset($response['errors']) === true && $response['errors'][0]['status'] === '401') {
      // Authentication token has expired. Refresh your tokens.
      if ($auto_refresh_token === true) {
        self::$patreon_token = $this->api('patreon_token', 'refresh');
        return $this->patreon_api($method, $endpoint, $arguments, false);
      }
      else {
        if($this::$log_mysql !== 'all') {
          $this->log_mysql($curl_response);
        }
        throw new Exception($response['errors'][0]['detail']);
      }
    }
    else if (isset($response['errors']) === true) {
      // Any other error
      if($this::$log_mysql !== 'all') {
        $this->log_mysql($curl_response);
      }
      throw new Exception($response['errors'][0]['detail']);
    }
    else {
      return $response;
    }
  }
}
