<?php
/**
 * @file
 * Helps you with the setup of the connection of client to server and
 * server to client.
 */


/**
 * Implements hook_setup_info().
 */
function bassets_setup_setup_info() {
  $types = array();
  $types['bassets_connection_intro'] = array(
    'form callback' => 'bassets_connection_intro_form_callback',
  );
  $types['bassets_connection_client_server'] = array(
    'form callback' => 'bassets_connection_client_server_form_callback',
  );
  $types['bassets_connection_test'] = array(
    'form callback' => 'bassets_connection_test_form_callback',
  );
  $types['bassets_connection_server_client'] = array(
    'form callback' => 'bassets_connection_server_client_form_callback',
  );
  $types['bassets_connection_final'] = array(
    'form callback' => 'bassets_connection_final_form_callback',
  );
  return $types;
}

/**
 * Implements hook_menu().
 */
function bassets_setup_menu() {
  $items['bassetssetup'] = array(
    'title' => 'Bassets setup',
    'access arguments' => array('access setup'),
    'page callback' => 'bassets_setup_process_steps',
    'type' => MENU_CALLBACK,
  );
  $items['bassetssetup/access'] = array(
    'title' => 'Bassets setup oauth',
    'access arguments' => array('access setup'),
    'page callback' => 'bassets_setup_process_oauth',
    'type' => MENU_CALLBACK,
  );
  if (module_exists('bassets_client')) {
    $items['bassetssetup/start'] = array(
      'title' => 'Start Bassets setup',
      'access arguments' => array('access setup'),
      'page callback' => 'bassets_setup_start',
      'type' => MENU_NORMAL_ITEM,
    );
  }
  return $items;
}

/**
 * Form callback for the intro page.
 */
function bassets_connection_intro_form_callback($form, $form_state) {
  drupal_set_title(t('Introduction'), PASS_THROUGH);
  $text = '<h2>' . t('Welcome to the setup of Bassets') . '</h2>';
  $text .= '<p>' . t('The setup will help you to connect your local Bassets client with your Bassets server and also to connect your Bassets server to the local Bassets client.') . '</p>';
  $text .= '<p><strong>' . t('Please be sure that you are logged in on your Bassets server before you begin with the setup.') . '</strong></p>';
  $form['intro'] = array(
    '#markup' => $text,
  );
  return $form;
}

/**
 * Form callback for the final page.
 */
function bassets_connection_final_form_callback($form, $form_state) {
  drupal_set_title(t('Finish'), PASS_THROUGH);
  $text = '<h2>' . t('Congratulation!') . '</h2>';
  $text .= '<p>' . t('Your Bassets client is now connected with the Bassets server. And the Bassets server with the Bassets client.') . '</p>';
  $form['final'] = array(
    '#markup' => $text,
  );
  return $form;
}

/**
 * Form callback for the test page.
 */
function bassets_connection_test_form_callback($form, $form_state) {
  $step = arg(1);
  $orders = $form_state['form_info']['order'];
  static $prev;
  foreach ($orders as $order => $label) {
    if ($order !== $step) {
      $prev = $order;
    }
    else {
      break;
    }
  }
  // Redirect the user to the previous page.
  drupal_goto('setup/' . $prev);
}

/**
 * Form callback for client to server connection.
 */
function bassets_connection_client_server_form_callback($form, &$form_state) {
  $parameters = drupal_get_query_parameters();
  if (isset($parameters['connection_test'])) {
    // TODO Link to handbook page.
    drupal_set_message(t('Connection test was not successfully, consult the online handbook to see what the connection test does.'), 'error', FALSE);
  }
  // The 'setup_enabled' variable needs to be set to FALSE or
  // we can not use a different path.
  variable_set('setup_enabled', FALSE);
  // We can not set up the connections in maintenance mode.
  variable_set('maintenance_mode', FALSE);
  drupal_set_title(t('Connection client to server'), PASS_THROUGH);
  $title = t('Bassets server');
  $form['title'] = array(
    '#markup' => '<h3>' . $title . '</h3>',
  );
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Administrative title'),
    '#required' => TRUE,
    '#default_value' => !empty($form_state['object']['client_server']['name']) ? $form_state['object']['client_server']['name'] : $title,
    '#description' => t('This will appear in the administrative interface to easily identify it.'),
  );
  $form['url'] = array(
    '#type' => 'textfield',
    '#title' => t('Website'),
    '#required' => TRUE,
    '#description' => t('Enter a fully-qualified URL, such as http://www.example.com/.'),
    '#default_value' => isset($form_state['object']['client_server']['url']) ? $form_state['object']['client_server']['url'] : '',
  );
  $form['force_port'] = array(
    '#type' => 'textfield',
    '#title' => t('Force port'),
    '#description' => t('If required force port in URL which is used for calculating signature. I.e. 80 for https connection.'),
    '#default_value' => isset($form_state['object']['client_server']['force_port']) ? $form_state['object']['client_server']['force_port'] : '',
  );
  $form['skipcert'] = array(
    '#type' => 'checkbox',
    '#title' => t('Skip SSL Cert verification'),
    '#description' => t("Don't verify remote certificate."),
    '#default_value' => isset($form_state['object']['client_server']['skipcert']) ? $form_state['object']['client_server']['skipcert'] : 0,
  );
  $form['fileowner'] = array(
    '#type' => 'textfield',
    '#title' => t('File owner'),
    '#maxlength' => 60,
    '#autocomplete_path' => 'user/autocomplete',
    '#default_value' => '',
    '#description' => t('This is the default file owner of a Bassets file in the use case a new file will not selected via Bassets search. Leave blank for user 1.'),
  );
  $scheme_options = array();
  foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
    $scheme_options[$scheme] = $stream_wrapper['name'];
  }
  $form['scheme'] = array(
    '#type' => 'select',
    '#title' => t('File system'),
    '#options' => $scheme_options,
    '#default_value' => file_default_scheme(),
    '#description' => t('Derivates of images will be stored in this file system. But nevertheless delivered through the "Bassets" stream wrapper.'),
  );
  return $form;
}

/**
 * Validate handler for bassets_connection_client_server_form_callback().
 */
function bassets_connection_client_server_form_callback_validate($form, &$form_state) {
  if (!valid_url($form_state['values']['url'], TRUE)) {
    form_set_error('url', 'The url is not properly formatted.');
  }
}

/**
 * Submit handler for bassets_connection_client_server_form_callback().
 */
function bassets_connection_client_server_form_callback_submit(&$form, &$form_state) {
  global $user, $base_url;
  $url = rtrim($form_state['values']['url'], '/');
  variable_set('services_client_id', services_client_get_id());
  $client_data = array(
    'name' => variable_get('site_name'),
    'url' => rtrim($base_url, '/'),
  );
  $server_data = array(
    'name' => $form_state['values']['name'],
    'url' => $url,
    'force_port' => $form_state['values']['force_port'],
    'fileowner' => $form_state['values']['fileowner'],
    'scheme' => $form_state['values']['scheme'],
    'skipcert' => $form_state['values']['skipcert'],
  );
  $form_state['object']['client_server'] = $server_data;
  setup_cache_set($form_state['object_id'], $form_state['object']);
  drupal_goto($url . '/bassetssetup', array(
    'query' => array(
      'client_data' => $client_data,
      'server_data' => $server_data,
      'step' => 1,
    ))
  );
}

/**
 * Form callback for server to client connection.
 */
function bassets_connection_server_client_form_callback($form, &$form_state) {
  drupal_set_title(t('Connection server to client'), PASS_THROUGH);
  global $base_url;
  $parameters = drupal_get_query_parameters();
  if (isset($parameters['connection_test'])) {
    // TODO link to handbook page.
    drupal_set_message(t('Connection test was not successfully, consult the online handbook to see what the connection test does.'), 'error', FALSE);
  }
  $title = t('Bassets client');
  $form['title'] = array(
    '#markup' => '<h3>' . $title . '</h3>',
  );
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Administrative title'),
    '#required' => TRUE,
    '#default_value' => !empty($form_state['object']['server_client']['name']) ? $form_state['object']['server_client']['name'] : $title,
    '#description' => t('This will appear in the administrative interface to easily identify it.'),
  );
  $form['skipcert'] = array(
    '#type' => 'checkbox',
    '#title' => t('Skip SSL Cert verification'),
    '#description' => t("Don't verify remote certificate."),
    '#default_value' => isset($form_state['object']['server_client']['skipcert']) ? $form_state['object']['server_client']['skipcert'] : 0,
  );
  $form['url'] = array(
    '#type' => 'item',
    '#title' => t('Website'),
    '#markup' => $base_url,
  );
  $form['force_port'] = array(
    '#type' => 'textfield',
    '#title' => t('Force port'),
    '#description' => t('If required force port in URL which is used for calculating signature. I.e. 80 for https connection.'),
    '#default_value' => isset($form_state['object']['server_client']['force_port']) ? $form_state['object']['server_client']['force_port'] : '',
  );
  return $form;
}

/**
 * Submit handler for bassets_connection_server_client_form_callback().
 */
function bassets_connection_server_client_form_callback_submit($form, &$form_state) {
  global $user, $base_url;
  $url = rtrim($base_url, '/');
  $cache = setup_cache_get($form_state['object_id']);
  variable_set('services_client_id', services_client_get_id());
  $client_data = array(
    'name' => $form_state['values']['name'],
    'url' => $url,
    'services_client_id' => services_client_get_id(),
    'force_port' => $form_state['values']['force_port'],
    'skipcert' => $form_state['values']['skipcert'],
  );
  $form_state['object']['server_client'] = $client_data;
  setup_cache_set($form_state['object_id'], $form_state['object']);
  $server_data = array(
    'name' => $cache['client_server']['name'],
    'url' => $cache['client_server']['url'],
  );
  drupal_goto('bassetssetup', array(
    'query' => array(
      'client_data' => $client_data,
      'server_data' => $server_data,
      'step' => 3,
    ),
  ));
}

/**
 * Menu callback to start the Bassets setup.
 */
function bassets_setup_start() {
  // The 'setup_enabled' variable needs to be set to FALSE or
  // we can not use a different path.
  variable_set('setup_enabled', FALSE);

  // The 'setup_source' variable needs to be set to the relevant source, so the
  // knows which module wants the setup under example.com/setup.
  variable_set('setup_source', 'module-bassets_setup');
  drupal_goto('setup');
}

/**
 * Processes the different steps.
 *
 * Steps:
 * - Setup redirect user to server. (/bassetssetup)
 * - Create a consumer on the server for the client.
 * - Redirect to the client again. (/bassetssetup)
 * - Create a connection on the client to the server.
 * - Make a SCC request from client to server
 *   (same as services_client_oauth_request() would do).
 * - Connection test client to server. (ping resource)
 * - Redirect to the client again. (setup/connection1)
 * - Create a consumer on the client for the server.
 * - Redirect to the server again.
 * - Create a connection on the server to client.
 * - Make a SCC request from server to client
 *   (same as services_client_oauth_request() would do).
 * - Connection test server to client. (ping resource)
 * - Final redirect to the client again. (setup/final)
 */
function bassets_setup_process_steps() {
  $parameters = drupal_get_query_parameters();
  $step = isset($parameters['step']) ? (int) $parameters['step'] : NULL;

  if ($step == 1) {
    variable_set('services_client_id', services_client_get_id());
    $client_data = $parameters['client_data'];
    // Create a consumer on the server for the client.
    $consumer = _bassets_setup_get_consumer($client_data['url'], $client_data['name']);
    $client_consumer = array();
    $client_consumer['consumer_key'] = $consumer->key;
    $client_consumer['consumer_secret'] = $consumer->secret;
    $query = array(
      'client_consumer' => $client_consumer,
      'client_data' => $client_data,
      'server_data' => array_merge($parameters['server_data'], array('services_client_id' => services_client_get_id())),
      'step' => 2,
    );
    drupal_goto($client_data['url'] . '/bassetssetup', array('query' => $query));
  }
  elseif ($step == 2) {
    // Create a connection on the client to the server.
    $connection = _bassets_setup_scc_save($parameters['server_data']['name'], $parameters['server_data']['url']);
    $connection->services_client_id = $parameters['server_data']['services_client_id'];
    $account = user_load_by_name(array('name' => $parameters['server_data']['fileowner']));
    $connection->config['bassets']['fileowner'] = $account->uid;
    $connection->config['bassets']['scheme'] = $parameters['server_data']['scheme'];
    $connection->config['request']['config']['ssl_verifypeer_skip'] = $parameters['server_data']['skipcert'];
    services_client_connection_save($connection);
    // Make a SCC request from client to server
    // (same as services_client_oauth_request() would do).
    _bassets_setup_oauth_request(
      $parameters['client_consumer']['consumer_key'],
      $parameters['client_consumer']['consumer_secret'],
      $parameters['server_data']['url'],
      $parameters['server_data']['force_port'],
      $connection->name
    );
  }
  elseif ($step == 3) {
    // Create a consumer on the client for the server.
    $consumer = _bassets_setup_get_consumer($parameters['server_data']['url'], $parameters['server_data']['name']);
    $server_consumer['consumer_key'] = $consumer->key;
    $server_consumer['consumer_secret'] = $consumer->secret;
    $query = array(
      'server_consumer' => $server_consumer,
      'client_data' => $parameters['client_data'],
      'server_data' => $parameters['server_data'],
      'step' => 4,
    );
    drupal_goto($parameters['server_data']['url'] . '/bassetssetup', array('query' => $query));
  }
  elseif ($step == 4) {
    // Create a connection on the server to client.
    $connection = _bassets_setup_scc_save($parameters['client_data']['name'], $parameters['client_data']['url'], 'client');
    $connection->services_client_id = $parameters['client_data']['services_client_id'];
    $connection->config['request']['config']['ssl_verifypeer_skip'] = $parameters['client_data']['skipcert'];

    services_client_connection_save($connection);
    cache_set('bassets_setup', $parameters['client_data']['url']);
    // Make a SCC request from server to client
    // (same as services_client_oauth_request() would do).
    _bassets_setup_oauth_request(
      $parameters['server_consumer']['consumer_key'],
      $parameters['server_consumer']['consumer_secret'],
      $parameters['client_data']['url'],
      $parameters['client_data']['force_port'],
      $connection->name
    );
  }
  // Redirect to the frontpage in case of some errors.
  return drupal_goto();
}

/**
 * Starts the oauth request token and redirect the user to the next step.
 */
function bassets_setup_process_oauth() {
  $cache = cache_get('bassets_setup');
  try {
    $connection_name = $_SESSION['services_client_oauth']['connection_name'];
    _bassets_setup_oauth_access_callback();
    $success = _bassets_setup_ping_connection($connection_name);
    $query = array('connection_test' => -1);
    // We are on the client.
    if (empty($cache)) {
      if ($success) {
        drupal_goto('setup/server_client');
      }
      drupal_goto('setup/client_server', array('query' => $query));
    }
    else {
      cache_clear_all('bassets_setup', 'cache');
      if ($success) {
        drupal_goto($cache->data . '/setup/final');
      }
      drupal_goto($cache->data . '/setup/server_client', array('query' => $query));
    }
  }
  catch (Exception $e) {
    drupal_set_message($e->getMessage(), 'error');
  }
}

/**
 * Does the same workflow like services_client_oauth would do.
 *
 * @see services_client_oauth_request_validate()
 * @see services_client_oauth_request_submit()
 */
function _bassets_setup_oauth_request($key, $secret, $target_url, $force_port, $connection_name) {
  try {
    $consumer = new DrupalOAuthConsumer($key, $secret);
    // Get request token.
    $client = new ServicesClientDrupalOAuthClient($consumer);
    $request_token = $client->getRequestToken($target_url . '/oauth/request_token', array(
      'callback' => url('bassetssetup/access', array('absolute' => TRUE)),
      'force_port' => $force_port,
    ));
    if (!$request_token) {
      drupal_set_message(t('Error when retrieving request token.'), 'error');
      return '';
    }
    $url = $client->getAuthorizationUrl($target_url . '/oauth/authorize', array(
      'callback' => url('bassetssetup/access', array('absolute' => TRUE)),
    ));
    if (!$url) {
      drupal_set_message(t('Error when retrieving authorization URL.'), 'error');
      return '';
    }
    $_SESSION['services_client_oauth'] = array(
      'consumer' => $consumer,
      'key' => $request_token->key,
      'secret' => $request_token->secret,
      'connection_name' => $connection_name,
      'access_url' => $target_url . '/oauth/access_token',
      'force_port' => $force_port,
    );
    drupal_goto($url);
  }
  catch (Exception $e) {
    drupal_set_message($e->getMessage(), 'error');
    return '';
  }
}

/**
 * Request callback to process and store access token.
 *
 * Copy of services_client_oauth_access_callback().
 * We need another url.
 *
 * @see services_client_oauth_access_callback()
 *
 * @throws Exception
 */
function _bassets_setup_oauth_access_callback() {
  // Get authorized connection name.
  $connection_name = $_SESSION['services_client_oauth']['connection_name'];
  // Retrieve consumer and request token.
  $consumer = $_SESSION['services_client_oauth']['consumer'];
  $request_token = new DrupalOAuthToken(
    $_SESSION['services_client_oauth']['key'],
    $_SESSION['services_client_oauth']['secret'],
    $consumer
  );

  // Validate request and retrieve access token.
  $client = new ServicesClientDrupalOAuthClient($consumer, $request_token);
  $access_token = $client->getAccessToken($_SESSION['services_client_oauth']['access_url'], array(
    'verifier' => isset($_GET['oauth_verifier']) ? $_GET['oauth_verifier'] : NULL,
    'force_port' => !empty($_SESSION['services_client_oauth']['force_port']) ? $_SESSION['services_client_oauth']['force_port'] : NULL,
  ));
  if (!$access_token) {
    throw new Exception(t('Getting access token failed.'));
  }

  // Update services client connection.
  $connection = services_client_connection_load($connection_name);
  $connection->config['auth']['config'] = array(
    'consumer_key' => $consumer->key,
    'consumer_secret' => $consumer->secret,
    'access_key' => $access_token->key,
    'access_secret' => $access_token->secret,
    'force_port' => !empty($_SESSION['services_client_oauth']['force_port']) ? $_SESSION['services_client_oauth']['force_port'] : NULL,
  );
  services_client_connection_save($connection);

  // Remove old data.
  unset($_SESSION['services_client_oauth']);
}

/**
 * Requests a resources on the client/server to test the connection.
 *
 * @param string $connection_name
 *   Machine name of the connection.
 *
 * @return bool
 *   Return TRUE when the test connection was successfully or FALSE.
 */
function _bassets_setup_ping_connection($connection_name) {
  $connection = services_client_connection_load($connection_name);
  $success = FALSE;
  if ($connection) {
    try{
      $client = services_client_connection_get($connection_name);
      if ($connection->config['bassets']['connection_type'] == 'server') {
        $resource = BASSETS_SERVER_RESOURCE;
      }
      else {
        $resource = BASSETS_CLIENT_RESOURCE;
      }
      $client->action($resource, 'ping');
      $response = $client->getResponse();
      if (!empty($response->data) && $response->data['status'] == WATCHDOG_NOTICE) {
        $success = TRUE;
      }
    }
    catch (Exception $e) {
    }
  }
  return $success;
}

/**
 * Helper function to reuse a existing consumer or to create a new consumer.
 */
function _bassets_setup_get_consumer($url, $admin_title) {
  $_consumer = NULL;
  $callback_url = rtrim($url, '/') . '/bassetssetup/access';
  module_load_include('inc', 'oauth_common');
  $consumers = oauth_common_user_consumers($GLOBALS['user']->uid);
  if (!empty($consumers)) {
    foreach ($consumers as $consumer) {
      if ($consumer->callback_url == $callback_url) {
        $_consumer = $consumer;
        break;
      }
    }
  }
  if (empty($_consumer)) {
    $_consumer = new DrupalOAuthConsumer(user_password(32), user_password(32), array(
      'callback_url' => $callback_url,
      'uid' => $GLOBALS['user']->uid,
      'provider_consumer' => TRUE,
      'context' => 'basic',
      'name' => $admin_title,
    ));

    $_consumer->write();
  }
  return $_consumer;
}

/**
 * Helper function to create a new connection.
 */
function _bassets_setup_scc_default($admin_title, $url, $type = 'server') {
  $_connection_name = 'bassetsserver';
  if ($type == 'client') {
    $_connection_name = 'bassetsclient';
  }
  $_connection_name .= REQUEST_TIME;
  $_endpoint = rtrim($url, '/') . '/bassetsapi';

  $connection = new stdClass();
  $connection->disabled = TRUE;
  $connection->api_version = 1;
  $connection->name = $_connection_name;
  $connection->admin_description = '';
  $connection->version = 3;
  $connection->endpoint = $_endpoint;
  $connection->config = array(
    'auth' => array(
      'plugin' => 'ServicesClientConnectionOAuthAuth',
      'config' => array(),
    ),
    'server' => array(
      'plugin' => 'ServicesClientConnectionRestServer',
      'config' => array(
        'request_formatter' => 'urlencode',
        'response_parser' => 'json',
      ),
    ),
    'request' => array(
      'plugin' => 'ServicesClientConnectionCurlRequest',
      'config' => array(
        'request_timeout' => '50',
        'ssl_verifypeer_skip' => 0,
      ),
    ),
  );
  $connection->debug = 0;
  $connection->admin_title = $admin_title;
  $connection->endpoint = rtrim($url) . '/bassetsapi';
  $connection->config['bassets'] = array(
    'connection_type' => ($type == 'client') ? 'client' : 'server',
    'scheme' => 'public',
    'fileowner' => 1,
  );
  return $connection;
}

/**
 * Reuses the existing connection or create a new connection.
 *
 * @param string $admin_title
 *   The human readable name of the connection.
 *
 * @param string $url
 *   The base url to build the endpoint.
 *
 * @param string $type
 *   The type of the connection 'client' or 'server'.
 *
 * @return object
 *   The bassets connection object.
 */
function _bassets_setup_scc_save($admin_title, $url, $type = 'server') {
  $connections = _bassets_scc_connections_load($type);
  $endpoint = rtrim($url, '/') . '/bassetsapi';
  $_connection = NULL;
  if (!empty($connections)) {
    foreach ($connections as $connection) {
      if ($connection->endpoint == $endpoint) {
        $_connection = $connection;
        $_connection->cid = (int) $_connection->cid;
        break;
      }
    }
  }
  if (!$_connection) {
    $_connection = _bassets_setup_scc_default($admin_title, $url, $type);
  }
  services_client_connection_save($_connection);
  return $_connection;
}
