<?php

class ApiController extends CController
{

  const API_FORMAT_XML = 'xml';

  const API_FORMAT_JSON = 'json';

  const API_FORMAT_PHP = 'php';

  public function accessRules()
  {
    return array (
        array (
            'allow' 
        ) 
    );
  }

  protected function beforeAction($action)
  {
    set_time_limit ( 0 );
    return parent::beforeAction ( $action );
  }

  /**
   *
   * @param string $apiKey          
   * @param string $function          
   * @return boolean
   */
  private static function checkApiKey($apiKey, $function)
  {
    // logLine("checkApiKey($apiKey, $function)");
    if (empty ( $apiKey ))
    {
      // logLine("ERROR: apikey is empty");
      return false;
    }
    
    $user = User::model ()->find ( 'apikey=:ak', array (
        ':ak' => $apiKey 
    ) );
    if ($user == null)
    {
      // logLine("ERROR: apikey not found");
      return false;
    }
    
    $operation = 'api_' . $function;
    AuthItem::ensureExists ( $operation );
    
    if ($user->superuser)
    {
      // logLine("SUCCESS: apikey is admin");
      return true;
    }
    
    $auths = AuthAssignment::getAllAssignments ( $user->id );
    foreach ( $auths as $a )
    {
      if ($a->name == $operation)
      {
        // logLine("SUCCESS: apikey is authorized");
        return true;
      }
    }
    
    // logLine("ERROR: apikey is not authorized");
    return false;
  }

  /**
   *
   * @param string $function          
   * @param array $data          
   * @param string $format          
   * @return string
   */
  private static function serialize($function, $data, $format = ApiController::API_FORMAT_XML)
  {
    if (! in_array ( $format, self::GetFormats () ))
      $format = self::API_FORMAT_XML;
    switch ($format)
    {
      case ApiController::API_FORMAT_XML :
        header ( "Content-Type: text/xml" );
        return xmlSerializeSimple ( $data, $function )->asXML ();
      case ApiController::API_FORMAT_JSON :
        header ( "Content-Type: application/json" );
        return CJSON::encode ( array (
            'function' => $function,
            'data' => $data 
        ) );
      case ApiController::API_FORMAT_PHP :
        header ( "Content-Type: text/plain" );
        return serialize ( array (
            'function' => $function,
            'data' => $data 
        ) );
      default :
        throw new CHttpException ( 500, 'api::serialize error' );
    }
  }

  private static function GetFormats()
  {
    return array (
        'item0' => ApiController::API_FORMAT_XML,
        'item1' => ApiController::API_FORMAT_JSON,
        'item2' => ApiController::API_FORMAT_PHP 
    );
  }

  /**
   *
   * @param string $function          
   * @param string $message          
   * @param string $format          
   * @return string
   */
  private static function createError($function, $message, $format = ApiController::API_FORMAT_XML)
  {
    return ApiController::serialize ( $function, array (
        'error' => $message 
    ), $format );
  }

  /**
   * *************************************************************
   */
  /**
   * ********* API ***********************
   */
  /**
   * *************************************************************
   */
  public function actionEcho($message = 'hello world')
  {
    $this->layout = '//layouts/empty';
    if (YII_DEBUG)
      echo $message;
    app ()->end ();
  }

  public function actionGetApiKey($username, $password)
  {
    $this->layout = '//layouts/empty';
    
    $auth = new UserIdentity ( $username, $password );
    if ($auth->authenticate ())
    {
      $u = User::findByUsername ( $username );
      if ($u != null)
      {
        if (isEmptyOrWhitespace ( $u->apikey ))
        {
          $u->apikey = rand_str ( '0123456789QAZWSXEDCRFVTGBYHNUJMIKOLP', 32 );
          $u->update ();
        }
        echo $u->apikey;
      }
      else
        echo "ERROR";
    }
    else
      echo "ERROR";
    
    app ()->end ();
  }

  public function actionPlayground()
  {
    $this->layout = '//layouts/empty';
    if (YII_DEBUG)
    {
      $myApikey = null;
      $currentUser = user ()->model;
      if ($currentUser != null)
        $myApikey = $currentUser->apikey;
      
      $methods = array ();
      foreach ( get_class_methods ( 'ApiController' ) as $method )
        if ($method != 'actions' && startsWith ( $method, 'action' ))
          $methods [$method] = new ReflectionMethod ( 'ApiController', $method );
      
      if (! isset ( $_REQUEST ['apiview'] ) && ! isset ( $_REQUEST ['apirun'] ))
      {
        echo ("<h1><a href=\"" . url ( 'api/playground' ) . "\">API Playground</a></h1>");
        foreach ( $methods as $rmname => $rm )
        {
          $link = url ( 'api/playground', array (
              'apiview' => $rmname 
          ) );
          echo ("<a href=\"$link\">" . $rm->getName () . '( ');
          foreach ( $rm->getParameters () as $rmp )
          {
            echo ($rmp->getName () . " ");
          }
          echo (" )</a><br>\n");
        }
      }
      else if (isset ( $_REQUEST ['apiview'] ))
      {
        echo ("<h1><a href=\"" . url ( 'api/playground' ) . "\">API Playground</a></h1>");
        $apiname = $_REQUEST ['apiview'];
        if (! array_key_exists ( $apiname, $methods ))
          throw new CHttpException ( 404, "API not found!" );
        
        $method = $methods [$apiname];
        
        echo ("<h2>testing $apiname</h2>");
        echo ("<form>");
        $params = $method->getParameters ();
        if (count ( $params ) > 0)
        {
          echo ("<table border=1>");
          echo ("<tr><th>Parameter</th><th>Value</th></tr>");
          foreach ( $params as $p )
          { // $p=new ReflectionParameter($function, $parameter);
            $defValue = $p->isDefaultValueAvailable () ? $p->getDefaultValue () : null;
            if (strtolower ( $p->getName () ) == 'apikey')
              $defValue = $myApikey;
            else if (is_bool ( $defValue ))
              $defValue = $defValue ? 1 : 0;
            echo ('<tr><td>' . $p->getName () . '</td><td><input type="txt" name="' . $p->getName () . '" value="' . $defValue . '"></td></tr>');
          }
          echo ("</table>");
        }
        echo ('<input type="hidden" name="apirun" value="' . $apiname . '">');
        echo ('<input type="submit" name="Run" value="Run">');
        echo ("</form>");
      }
      else if (isset ( $_REQUEST ['apirun'] ))
      {
        $apiname = $_REQUEST ['apirun'];
        if (! array_key_exists ( $apiname, $methods ))
          throw new CHttpException ( 404, "API not found!" );
        $this->__named ( $apiname, $_REQUEST );
      }
      else
        throw new CHttpException ( 404, "API not found!" );
    }
    
    app ()->end ();
  }

  /**
   * Pass method arguments by name
   *
   * @param string $method          
   * @param array $args          
   * @return mixed
   */
  public function __named($method, array $args = array())
  {
    $reflection = new ReflectionMethod ( $this, $method );
    
    $pass = array ();
    foreach ( $reflection->getParameters () as $param )
    {
      /* @var $param ReflectionParameter */
      if (isset ( $args [$param->getName ()] ))
      {
        $pass [] = $args [$param->getName ()];
      }
      else
      {
        $pass [] = $param->getDefaultValue ();
      }
    }
    
    return $reflection->invokeArgs ( $this, $pass );
  }

  /*
   * public function actionCall($apiKey=null, $function=null)
   * {
   * $this->layout='//layouts/empty';
   * if(!ApiController::checkApiKey($apiKey, $function))
   * {
   * echo ApiController::createError($function, 'invalid api key', $format);
   * app()->end();
   * return;
   * }
   *
   * $methodName = 'apiAction_'.$function;
   *
   * $data = $this->$methodName();
   *
   * echo ApiController::serialize($function, $data, $format);
   * app()->end();
   * }
   */
  public function actionGetApiFormats($apiKey = null)
  {
    $this->layout = '//layouts/empty';
    $function = 'public';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    echo ApiController::serialize ( $function, self::GetFormats (), $format );
    app ()->end ();
  }

  public function actionGetVehicleTypes($apiKey = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_vehicle_types';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $lst = array (
        'Truck',
        'Vehicle',
        'Motorcycle',
        'Bus',
        'Kemler' 
    );
    
    echo ApiController::serialize ( $function, $lst, $format );
    app ()->end ();
  }

  public function actionGetGates($apiKey = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_gates';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $lst = array ();
    foreach ( Gate::getAllGates () as $gate )
    {
      $lst [] = $gate->attributes;
      // $lst['gate_'.$gate->Gate] = $gate->Descrizione;
    }
    
    echo ApiController::serialize ( $function, $lst, $format );
    app ()->end ();
  }

  public function actionGetMeanSpeed($apiKey = null, $gate = null, $timestamp = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_mean_speed';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $timestamp = new DateTimeEx ( $timestamp );
    
/*    
    $data = query ( "
        SELECT meanspeed
        FROM statstable 
        WHERE
        gate=:gt 
        AND
        startdatetime <= :tm
        AND
        :tm < enddatetime
        AND
        classe=:cl
        ", array (
        ':gt' => $gate,
        ':tm' => $timestamp->toString (),
        ':cl' => 'Vehicle' 
    ), false, true );
    
    $mean_speed = count ( $data ) > 0 ? doubleval ( $data [0]->meanspeed ) : 0.0;
    $end_time = $timestamp->toString ();
*/    
    
    $data = query ( "
        SELECT meanspeed, startdatetime, enddatetime
        FROM statstable 
        WHERE
        gate=:gt 
        AND
        enddatetime <=:tm 
        AND
        classe=:cl
        order by enddatetime desc
        limit 1
        ", array (
        ':gt' => $gate,
        ':tm' => $timestamp->toString (),
        ':cl' => 'Vehicle' 
    ), false, true );    
    
    
    
    $mean_speed = count ( $data ) > 0 ? doubleval ( $data [0]->meanspeed ) : 0.0;
    $end_time = count ( $data ) > 0 ? ( $data [0]->enddatetime ) : '2000-01-02 00:00:00';
    
    $result = array (
        'timestamp' => $timestamp->toString (),
        'mean_speed' => $mean_speed,
        'gate' => $gate 
    );
    
    echo ApiController::serialize ( $function, $result, $format );
    app ()->end ();
  }

  public function actionGetTravelTimes($apiKey = null, $fromGate = null, $toGate = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_travel_times';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $tmstamp = DateTimeEx::now ();
    $tmstamp->subFromString ( '15 minutes' );
    
    $request = null;
    
    $rows = query ( '
        SELECT
        *
        FROM crossing_times
        WHERE
        timestamp >= :tm
        AND
        id_gate_start = :sg
        AND
        id_gate_end = :eg
        ORDER BY
        timestamp DESC
        ', array (
        ':tm' => $tmstamp,
        ':sg' => $fromGate,
        ':eg' => $toGate 
    ), false, true );
    foreach ( $rows as $row )
    {
      $request = CrossingTime::model ()->findByPk ( $row->id );
      break;
    }
    
    $result = array (
        'done' => $request == null ? 0 : $request->done,
        'result_code' => $request == null ? 0 : $request->result_code,
        'result_message' => $request == null ? "Cannot find a suitable value" : $request->result_message,
        'traveltime' => $request == null ? - 1 : $request->result,
        'request' => $request == null ? - 1 : $request->id,
        'data_reliability' => $request == null ? 0 : $request->reliability * 100.0,
        'fromGate' => $fromGate,
        'toGate' => $toGate 
    );
    
    echo ApiController::serialize ( $function, $result, $format );
    app ()->end ();
  }

  public function actionGetTravelTimesRealtime($apiKey = null, $fromGate = null, $toGate = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_travel_times_realtime';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $request = new CrossingTime ();
    $request->epoch = DateTimeEx::now ();
    $request->id_gate_start = $fromGate;
    $request->id_gate_end = $toGate;
    $request->timestamp = DateTimeEx::now ();
    $request->done = CrossingTime::DONE_NO;
    $request->result_code = CrossingTime::RESPONSE_CODE_UNKNOWN;
    $request->insert ();
    
    while ( $request->done != CrossingTime::DONE_YES )
    {
      usleep ( USLEEP_SECOND * 0.2 ); // 200ms
      $request->refresh ();
    }
    
    $result = array (
        'done' => $request->done,
        'result_code' => $request->result_code,
        'result_message' => $request->result_message,
        'traveltime' => $request->result,
        'request' => $request->id,
        'data_reliability' => $request->reliability * 100.0,
        'fromGate' => $fromGate,
        'toGate' => $toGate 
    );
    
    echo ApiController::serialize ( $function, $result, $format );
    app ()->end ();
  }

  public function actionGetUsers($apiKey = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_users';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $lst = array ();
    foreach ( User::model ()->findAll () as $user )
    {
      $lst [] = $user->attributes;
    }
    
    echo ApiController::serialize ( $function, $lst, $format );
    app ()->end ();
  }

  public function actionRawQuery($apiKey = null, $query = 'SELECT * FROM none', $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'raw_query';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $lst = array ();
    foreach ( app ()->db->createCommand ( $query )
      ->queryAll () as $row )
    {
      $lst [] = $row;
    }
    
    echo ApiController::serialize ( $function, $lst, $format );
    app ()->end ();
  }

  public function actionQueryAsCsv($apiKey = null, $query)
  {
    $this->layout = '//layouts/empty';
    $function = 'query_as_csv';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    queryToCsv ( "QueryAsCsv_" . DateTimeEx::nows () . ".csv", $query );
    
    app ()->end ();
  }

  public function actionStats($apiKey = null, $vehiclesClass = 'Vehicle', $gates = '2004', $startTimestamp = 1405029600, $endTimestamp = 1405720799, $minSpeed = 10, $maxSpeed = 300, $granularity = 3600, $startTime = '00:00', $endTime = '23:59', $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'stats';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $vehiclesClass = explode ( ',', $vehiclesClass );
    $gates = explode ( ',', $gates );
    
    $statsQuery = new StatsQuery ();
    $dataProvider = $statsQuery->getStatsData ( array (
        'vehiclesClass' => $vehiclesClass,
        'gates' => $gates,
        'startTimestamp' => $startTimestamp,
        'endTimestamp' => $endTimestamp,
        'minSpeed' => $minSpeed,
        'maxSpeed' => $maxSpeed,
        'granularity' => $granularity,
        'startTime' => $startTime,
        'endTime' => $endTime 
    ) );
    
    $graphData = array ();
    foreach ( $dataProvider->getData () as $item )
      $graphData [] = $item->attributes;
      
      // $graphData = VehicleStatGraphEncoder::encode($dataProvider->getData(), 'StartDateTime', 'Counter', $granularity);
    
    echo ApiController::serialize ( $function, $graphData, $format );
    app ()->end ();
  }

  public function actionEval($apiKey = null, $code = null, $returnAuto = true, $rawOutput = false, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'eval';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $response = array ();
    
    if ($returnAuto)
      $code = "return $code;";
    
    if (isEmpty ( $code ))
      $result = null;
    else
      $result = @eval ( $code );
    
    if (! $rawOutput)
      $result = _2str ( $result );
    
    $response ['result'] = $result;
    
    echo ApiController::serialize ( $function, $response, $format );
    app ()->end ();
  }

  public function actionGetActions($apiKey = null, $format = ApiController::API_FORMAT_XML)
  {
    $this->layout = '//layouts/empty';
    $function = 'get_actions';
    if (! ApiController::checkApiKey ( $apiKey, $function ))
    {
      echo ApiController::createError ( $function, 'invalid api key', $format );
      app ()->end ();
      return;
    }
    
    $methods = array ();
    foreach ( get_class_methods ( 'ApiController' ) as $methodName )
      if ($methodName != 'actions' && startsWith ( $methodName, 'action' ))
      {
        $method = new ReflectionMethod ( 'ApiController', $methodName );
        $methodName = startsWithGet ( $methodName, 'action' );
        $methodName = lcfirst ( $methodName );
        
        $parameters = array ();
        foreach ( $method->getParameters () as $param )
        {
          // $param = new ReflectionParameter($function, $parameter);
          $paramName = $param->getName ();
          $hasDefaultValue = $param->isDefaultValueAvailable ();
          $defaultValue = $hasDefaultValue ? $param->getDefaultValue () : null;
          
          $hasDefaultValue = ( int ) $hasDefaultValue;
          
          if (strtolower ( $paramName ) == 'apikey')
            $hasDefaultValue = 0;
          
          if ($hasDefaultValue && is_bool ( $defaultValue ))
            $defaultValue = ( int ) $defaultValue;
          
          $parameters [$paramName] = array (
              'hasDefaultValue' => $hasDefaultValue,
              'defaultValue' => $defaultValue 
          );
        }
        
        $methods [$methodName] = $parameters;
      }
    
    $response = array ();
    
    $response ['methods'] = $methods;
    
    echo ApiController::serialize ( $function, $response, $format );
    app ()->end ();
  }
}