<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class API {

  public $http;

  public function __construct() {
    $this->http = new Telegraph\HTTP();
  }

  private function respond(Response $response, $code, $params, $headers=[]) {
    $response->setStatusCode($code);
    foreach($headers as $k=>$v) {
      $response->headers->set($k, $v);
    }
    $response->setContent(json_encode($params));
    return $response;
  }

  private static function toHtmlEntities($input) {
    return mb_convert_encoding($input, 'HTML-ENTITIES', mb_detect_encoding($input));
  }

  private static function generateStatusToken() {
    $str = dechex(date('y'));
    $chs = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $len = strlen($chs);
    for($i = 0; $i < 16; $i++) {
      $str .= $chs[mt_rand(0, $len - 1)];
    }
    return $str;
  }

  public function webmention(Request $request, Response $response) {

    # Require the token parameter
    if(!$token=$request->get('token')) {
      return $this->respond($response, 401, [
        'error' => 'authentication_required',
        'error_description' => 'A token is required to use the API'
      ]);
    }

    # Require source and target parameters
    if((!$source=$request->get('source')) || (!$target=$request->get('target'))) {
      return $this->respond($response, 400, [
        'error' => 'missing_parameters',
        'error_description' => 'The source or target parameters were missing'
      ]);
    }

    $urlregex = '/^https?:\/\/[^ ]+\.[^ ]+$/';

    # Verify source and target are URLs
    if(!preg_match($urlregex, $source) || !preg_match($urlregex, $target)) {
      return $this->respond($response, 400, [
        'error' => 'invalid_parameter',
        'error_description' => 'The source or target parameters were invalid'
      ]);
    }

    # If a callback was provided, verify it is a URL
    if($callback=$request->get('callback')) {
      if(!preg_match($urlregex, $source) || !preg_match($urlregex, $target)) {
        return $this->respond($response, 400, [
          'error' => 'invalid_parameter',
          'error_description' => 'The callback parameter was invalid'
        ]);
      }
    }

    # Verify the token is valid
    $role = ORM::for_table('roles')->where('token', $token)->find_one();

    if(!$role) {
      return $this->respond($response, 401, [
        'error' => 'invalid_token',
        'error_description' => 'The token provided is not valid'
      ]);
    }

    # Synchronously check the source URL and verify that it actually contains
    # a link to the target. This way we prevent this API from sending known invalid mentions.
    $sourceData = $this->http->get($source);

    $doc = new DOMDocument();
    @$doc->loadHTML(self::toHtmlEntities($sourceData['body']));

    if(!$doc) {
      return $this->respond($response, 400, [
        'error' => 'source_not_html',
        'error_description' => 'The source document could not be parsed as HTML'
      ]);
    }

    $xpath = new DOMXPath($doc);

    $found = false;
    foreach($xpath->query('//a[@href]') as $href) {
      if($href->getAttribute('href') == $target) {
        $found = true;
        continue;
      }
    }

    if(!$found) {
      return $this->respond($response, 400, [
        'error' => 'no_link_found',
        'error_description' => 'The source document does not have a link to the target URL'
      ]);
    }

    # Everything checked out, so write the webmention to the log and queue a job to start sending

    $w = ORM::for_table('webmentions')->create();
    $w->site_id = $role->site_id;
    $w->created_by = $role->user_id;
    $w->created_at = date('Y-m-d H:i:s');
    $w->token = self::generateStatusToken();
    $w->source = $source;
    $w->target = $target;
    $w->vouch = $request->get('vouch');
    $w->callback = $callback;
    $w->save();

    q()->queue('Telegraph\Webmention', 'send', [$w->id]);

    $statusURL = Config::$base . 'webmention/' . $w->token;

    return $this->respond($response, 201, [
      'result' => 'queued',
      'status' => $statusURL
    ], [
      'Location' => $statusURL
    ]);
  }

}