Merge branch 'develop'
diff --git a/.travis.yml b/.travis.yml
index 68b4dfe..9a6ff78 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@
 php:
   - 5.5
   - 5.4
-  - 5.3
 before_script:
   - pear channel-discover pear.phing.info
   - pear install phing/phing
diff --git a/README.md b/README.md
index 2ff11b9..aaeb7ea 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,12 @@
 Prerequisites
 =============
 
-* PHP 5.3+ (http://php.net/)
+* PHP 5.4+ (http://php.net/)
 * PHP: cURL (http://php.net/manual/en/book.curl.php)
 * Phing (http://www.phing.info/)
 * ApiGen (http://apigen.org/)
 
+Note: This SDK only supports Prediction IO version 0.8 or higher.
 
 Support
 =======
@@ -45,7 +46,7 @@
 
         {
             "require": {
-                "predictionio/predictionio": "~0.6.0"
+                "predictionio/predictionio": "~0.8.0"
             }
         }
 
@@ -62,25 +63,6 @@
         require_once("vendor/autoload.php");
 
 
-By Building Phar
-----------------
-
-1. Assuming you are cloning to your home directory:
-
-        cd ~
-        git clone git://github.com/PredictionIO/PredictionIO-PHP-SDK.git
-
-2. Build the Phar:
-
-        cd ~/PredictionIO-PHP-SDK
-        phing
-
-3. Once the build finishes you will get a Phar in `build/artifacts`, and a set of API documentation.
-   Assuming you have copied the Phar to your current working directory, to use the client, simply
-
-        require_once("predictionio.phar");
-
-
 Supported Commands
 ==================
 
@@ -94,39 +76,30 @@
 This package is a web service client based on Guzzle.
 A few quick examples are shown below.
 
-For a full user guide on how to take advantage of all Guzzle features, please refer to http://guzzlephp.org.
-Specifically, http://guzzlephp.org/tour/using_services.html#using-client-objects describes how to use a web service client.
-
-Many REST request commands support optional arguments.
-They can be supplied to these commands by the `set` method.
-
-
-Instantiate PredictionIO API Client
+Instantiate PredictionIO API Event Client
 -----------------------------------
 
 ```PHP
-use PredictionIO\PredictionIOClient;
-$client = PredictionIOClient::factory(array("appkey" => "<your app key>"));
+use predictionio\EventClient;
+$app_id = 8;
+$client = new EventClient($app_id, 'http://localhost:7070');
 ```
 
-
-Import a User Record from Your App
-----------------------------------
+Set a User Record from Your App
+-------------------------------
 
 ```PHP
 // assume you have a user with user ID 5
-$command = $client->getCommand('create_user', array('pio_uid' => 5));
-$response = $client->execute($command);
+$response = $client->setUser(5);
 ```
 
 
-Import an Item Record from Your App
------------------------------------
+Set an Item Record from Your App
+---------------------------------
 
 ```PHP
 // assume you have a book with ID 'bookId1' and we assign 1 as the type ID for book
-$command = $client->getCommand('create_item', array('pio_iid' => 'bookId1', 'pio_itypes' => 1));
-$response = $client->execute($command);
+$response = $client->setItem('bookId1', array('pio_itypes' => 1));
 ```
 
 
@@ -135,55 +108,19 @@
 
 ```PHP
 // assume this user has viewed this book item
-$client->identify('5');
-$client->execute($client->getCommand('record_action_on_item', array('pio_action' => 'view', 'pio_iid' => 'bookId1')));
+$client->recordUserActionOnItem('view', 5, 'bookId1');
 ```
 
 
-Retrieving Top N Recommendations for a User
--------------------------------------------
+Retrieving Prediction Result
+----------------------------
 
 ```PHP
-try {
-    // assume you have created an itemrec engine named 'engine1'
-    // we try to get top 10 recommendations for a user (user ID 5)
-    $client->identify('5');
-    $command = $client->getCommand('itemrec_get_top_n', array('pio_engine' => 'engine1', 'pio_n' => 10));
-    $rec = $client->execute($command);
-    print_r($rec);
-} catch (Exception $e) {
-    echo 'Caught exception: ', $e->getMessage(), "\n";
-}
-```
+// assume you have created an itemrank engine on localhost:8000
+// we try to get ranking of 5 items (item IDs: 1, 2, 3, 4, 5) for a user (user ID 7)
 
+$engineClient = new EngineClient('http://localhost:8000');
+$response = $engineClient->sendQuery(array('uid'=>7, 'iids'=>array(1,2,3,4,5)));
 
-Retrieving Top N Similar Items for an Item
-------------------------------------------
-
-```PHP
-try {
-    // assume you have created an itemsim engine named 'engine2'
-    // we try to get top 10 similar items for an item (item ID 6)
-    $command = $client->getCommand('itemsim_get_top_n', array('pio_iid' => '6', 'pio_engine' => 'engine2', 'pio_n' => 10));
-    $rec = $client->execute($command);
-    print_r($rec);
-} catch (Exception $e) {
-    echo 'Caught exception: ', $e->getMessage(), "\n";
-}
-```
-
-
-Retrieving Ranking of Items for a User
---------------------------------------
-
-```PHP
-try {
-    // assume you have created an itemrank engine named 'engine3'
-    // we try to get ranking of 5 items (item IDs: 1, 2, 3, 4, 5) for a user (user ID 7)
-    $command = $client->getCommand('itemrank_get_ranked', array('pio_uid' => '7', 'pio_iids' => '1,2,3,4,5', 'pio_engine' => 'engine3'));
-    $rec = $client->execute($command);
-    print_r($rec);
-} catch (Exception $e) {
-    echo 'Caught exception: ', $e->getMessage(), "\n";
-}
+print_r($rec);
 ```
diff --git a/build.xml b/build.xml
index d959425..4355efe 100644
--- a/build.xml
+++ b/build.xml
@@ -1,18 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<project name="predictionio" default="package">
+<project name="predictionio" default="install-dependencies">
 
-  <property name="dir.output" value="${project.basedir}/build/artifacts" />
-
-  <target name="clean">
-    <delete dir="${dir.output}" />
-  </target>
-
-  <target name="prepare" depends="clean">
-    <mkdir dir="${dir.output}" />
-  </target>
-
-  <target name="install-dependencies" depends="prepare">
+  <target name="install-dependencies">
     <if>
       <available file="composer.phar" />
       <then>
@@ -35,24 +25,7 @@
     <exec command="php composer.phar update --dev" passthru="true" />
   </target>
 
-  <target name="package" depends="install-dependencies" description="Create a PHAR with an autoloader">
-    <pharpackage
-    	destfile="${dir.output}/predictionio.phar"
-    	basedir="."
-    	stub="phar-stub.php">
-      <fileset dir=".">
-      	<include name="src/**/*.php" />
-      	<include name="src/**/*.json" />
-        <include name="vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php" />
-        <include name="vendor/symfony/event-dispatcher/**/*.php" />
-      	<include name="vendor/guzzle/guzzle/src/Guzzle/**/*.php" />
-        <include name="vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem" />
-        <include name="vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem.md5" />
-      </fileset>
-      <metadata>
-        <element name="author" value="PredictionIO" />
-      </metadata>
-    </pharpackage>
+  <target name="apigen">
     <exec command="apigen -s src -d docs --title 'PredictionIO API PHP Client'" passthru="true" />
   </target>
 
diff --git a/composer.json b/composer.json
index fd4343b..13dfce7 100644
--- a/composer.json
+++ b/composer.json
@@ -21,15 +21,15 @@
         }
     ],
     "require": {
-        "php": ">=5.3.2",
-        "guzzle/guzzle": "~3.8.0"
+        "php": ">=5.4.0",
+        "guzzlehttp/guzzle": "~4.0"
     },
     "require-dev": {
         "symfony/class-loader": "*"
     },
     "autoload": {
         "psr-0": {
-            "PredictionIO": "src/"
+            "predictionio": "src/"
         }
     }
 }
diff --git a/examples/ItemrankImport.php b/examples/ItemrankImport.php
new file mode 100644
index 0000000..f9dea16
--- /dev/null
+++ b/examples/ItemrankImport.php
@@ -0,0 +1,33 @@
+<?php
+require_once("vendor/autoload.php");
+
+use predictionio\EventClient;
+
+// check Event Server status
+$client = new EventClient(88);
+$response=$client->getStatus();
+echo($response);
+
+// set user - generate 10 users
+for ($u=1; $u<=10; $u++) {
+  $response=$client->setUser($u, array('age'=>20+$u, 'gender'=>'M'));
+  print_r($response);
+}
+
+// set item - generate 50 items
+for ($i=1; $i<=50; $i++) {
+  $response=$client->setItem($i, array('pio_itypes'=>array('1')));
+  print_r($response);
+}
+
+// record event - each user randomly views 10 items
+for ($u=1; $u<=10; $u++) {
+  for ($count=0; $count<10; $count++) {
+    $i = rand(1,50);
+    $response=$client->recordUserActionOnItem('view', $u, $i);
+    print_r($response);
+  }
+} 
+
+
+?>
diff --git a/examples/ItemrankQuery.php b/examples/ItemrankQuery.php
new file mode 100644
index 0000000..2d06346
--- /dev/null
+++ b/examples/ItemrankQuery.php
@@ -0,0 +1,16 @@
+<?php
+require_once("vendor/autoload.php");
+
+use predictionio\EngineClient;
+
+$client = new EngineClient();
+$response=$client->getStatus();
+//echo $response;
+
+// Rank item 1 to 5 for each user
+for ($i=1; $i<=10; $i++) {
+  $response=$client->sendQuery(array('uid'=>$i, 'iids'=>array(1,2,3,4,5)));
+  print_r($response);
+}
+
+?>
diff --git a/examples/SampleEvent.php b/examples/SampleEvent.php
new file mode 100644
index 0000000..65138e0
--- /dev/null
+++ b/examples/SampleEvent.php
@@ -0,0 +1,68 @@
+<?php
+require_once("vendor/autoload.php");
+
+use predictionio\EventClient;
+use predictionio\PredictionIOAPIError;
+
+try {
+  // check Event Server status
+  $client = new EventClient(25);
+  $response=$client->getStatus();
+  echo($response);
+
+  // set user with event time
+  $response=$client->setUser(9, array('age'=>10), 
+                        '2014-01-01T10:20:30.400+08:00');
+  print_r($response);
+
+  // set user 
+  $response=$client->setUser(8, array('age'=>20, 'gender'=>'M'));
+  print_r($response);
+
+  // unset user
+  $response=$client->unsetUser(8, array('age'=>20));
+  print_r($response);
+
+  // delete user
+  $response=$client->deleteUser(9);
+  print_r($response);
+  
+  // set item with event time
+  $response=$client->setItem(3, array('pio_itypes'=>array('1')), 
+                        '2013-12-20T05:15:25.350+08:00');
+  print_r($response);
+
+  // set item 
+  $response=$client->setItem(2, array('pio_itypes'=>array('1')));
+  print_r($response);
+
+  // unset item 
+  $response=$client->unsetItem(2, array('pio_itypes'=>array('1')));
+  print_r($response);
+
+  // delete item 
+  $response=$client->deleteItem(3, '2000-01-01T01:01:01.001+01:00');
+  print_r($response);
+
+  // record user action on item
+  $response=$client->recordUserActionOnItem('view', 8, 2);
+  print_r($response);
+
+  // create event
+  $response=$client->createEvent(array(
+                        'appId' => 25,
+                        'event' => 'my_event',
+                        'entityType' => 'pio_user',
+                        'entityId' => '8',
+                        'properties' => array('prop1'=>1, 'prop2'=>2),
+                   ));
+  print_r($response); 
+
+  // get event
+  $response=$client->getEvent('event_id_goes_here');
+  print_r($response);                       
+
+} catch (PredictionIOAPIError $e) {
+  echo $e->getMessage();
+}
+?>
diff --git a/examples/SampleImport.php b/examples/SampleImport.php
deleted file mode 100644
index 83c9adf..0000000
--- a/examples/SampleImport.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-require_once("../build/artifacts/predictionio.phar");
-
-use PredictionIO\PredictionIOClient;
-
-if (!isset($argv[1]) || !isset($argv[2])) {
-	print "Usage: $argv[0] <appkey> <data file>\n";
-	exit(1);
-}
-
-$appkey = $argv[1];
-$input = $argv[2];
-
-if (!file_exists($input)) {
-	print "$input not found!\n";
-	exit(1);
-}
-
-if (!is_readable($input)) {
-	print "$input cannot be read!\n";
-	exit(1);
-}
-
-// Instantiate a client
-$client = PredictionIOClient::factory(array("appkey" => $appkey));
-
-// Scan sample data file and import ratings
-$handle = fopen($input, "r");
-while (!feof($handle)) {
-	$line = fgets($handle);
-	$tuple = explode("\t", $line);
-	if (count($tuple) == 3) {
-		try {
-			$client->identify($tuple[0]);
-			$client->execute($client->getCommand("record_action_on_item", array("pio_action" => "rate", "pio_iid" => $tuple[1], "pio_rate" => intval($tuple[2]))));
-		} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
-			print $e->getResponse()->getBody()."\n\n";
-		}
-	}
-}
-fclose($handle);
-
-?>
diff --git a/examples/SampleTasks.php b/examples/SampleTasks.php
deleted file mode 100644
index c5f7218..0000000
--- a/examples/SampleTasks.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-require_once("../build/artifacts/predictionio.phar");
-
-use PredictionIO\PredictionIOClient;
-
-if (!isset($argv[1])) {
-	print "Usage: $argv[0] <appkey>\n";
-	exit(1);
-}
-
-$appkey = $argv[1];
-
-// Instantiate a client
-$client = PredictionIOClient::factory(array("appkey" => $appkey));
-
-try {
-	// Create, get and delete a user
-	print "Create, get and delete a user:\n";
-
-	$uid = "foobar";
-
-	$command = $client->getCommand('create_user', array('pio_uid' => $uid));
-	$command->setInactive("true");
-	$command->set("gender", "F");
-	$response =$client->execute($command);
-	print_r($response);
-
-	$command = $client->getCommand('get_user', array('pio_uid' => $uid));
-	$response = $client->execute($command);
-	print_r($response);
-
-	$command = $client->getCommand('delete_user', array('pio_uid' => $uid));
-	$response =$client->execute($command);
-	print_r($response);
-
-	print "\n";
-
-	// Create, get and delete an item
-	print "Create, get and delete an item:\n";
-
-	$iid = "barbaz";
-
-	$command = $client->getCommand('create_item', array('pio_iid' => $iid));
-	$command->setItypes(array("dead", "beef"));
-	$command->setPrice(9.99);
-	$command->set("weight", "10");
-	$response = $client->execute($command);
-	print_r($response);
-
-	$command = $client->getCommand('get_item', array('pio_iid' => $iid));
-	$response = $client->execute($command);
-	print_r($response);
-
-	$command = $client->getCommand('delete_item', array('pio_iid' => $iid));
-	$response = $client->execute($command);
-	print_r($response);
-
-	print "\n";
-
-	// Retrieving Top N Recommendations for a User
-	print "Get top 10 recommendations for a user:\n";
-
-	$client->identify("1");
-	$response = $client->execute($client->getCommand("itemrec_get_top_n", array("pio_engine" => "movies", "pio_n" => 10)));
-	print_r($response);
-} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
-	print $e->getResponse()->getBody()."\n\n";
-}
-
-?>
diff --git a/examples/sample1.txt b/examples/sample1.txt
deleted file mode 100644
index a466652..0000000
--- a/examples/sample1.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-1	3	5
-1	2	3
-1	4	5
-2	1	2
-2	3	4
-3	4	1
diff --git a/phar-stub.php b/phar-stub.php
deleted file mode 100644
index 950d682..0000000
--- a/phar-stub.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-Phar::mapPhar('predictionio.phar');
-
-require_once 'phar://predictionio.phar/vendor/symfony/class-loader/Symfony/Component/ClassLoader/UniversalClassLoader.php';
-
-$classLoader = new Symfony\Component\ClassLoader\UniversalClassLoader();
-$classLoader->registerNamespaces(array('PredictionIO' => 'phar://predictionio.phar/src',
-                                       'Symfony\\Component\\EventDispatcher' => 'phar://predictionio.phar/vendor/symfony/event-dispatcher',
-                                       'Guzzle' => 'phar://predictionio.phar/vendor/guzzle/guzzle/src'));
-$classLoader->register();
-
-__HALT_COMPILER();
\ No newline at end of file
diff --git a/src/PredictionIO/Command/CreateItem.php b/src/PredictionIO/Command/CreateItem.php
deleted file mode 100644
index ab02c35..0000000
--- a/src/PredictionIO/Command/CreateItem.php
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Create Item Information
- *
- * Create or re-create an information record for a item. Re-creation will remove all data of the current record.
- *
- * To supply custom item information, simply pass it in during command creation,
- * or use the "set" method inherited from Guzzle\Common\Collection.
- * <code>
- * $command = $client->getCommand('create_item', array('pio_iid' => 'foobar', 'custom1' => 'baz'));
- * $command->set('custom2', '0xdeadbeef');
- * </code>
- *
- * @guzzle iid type="string" required="true"
- * @guzzle itypes type="string" required="true"
- * @guzzle startT type="string"
- * @guzzle endT type="string"
- * @guzzle price type="float"
- * @guzzle profit type="float"
- * @guzzle latlng type="string"
- * @guzzle inactive type="boolean"
- */
-class CreateItem extends AbstractCommand
-{
-    /**
-     * Set the "iid" parameter for the current command
-     *
-     * @param string $iid Item ID
-     *
-     * @return CreateItem
-     */
-    public function setIid($iid)
-    {
-        return $this->set("pio_iid", $iid);
-    }
-
-    /**
-     * Set the "itypes" parameter for the current command
-     *
-     * $itypes can be supplied as an array of strings, or a "," delimited list of strings.
-     *
-     * @param array|string $itypes Item types
-     *
-     * @return CreateItem
-     */
-    public function setItypes($itypes)
-    {
-        if (is_array($itypes)) {
-            return $this->set("pio_itypes", implode(",", $itypes));
-        }
-
-        return $this->set("pio_itypes", $itypes);
-    }
-
-    /**
-     * Set the "startT" parameter for the current command
-     *
-     * @param string $startT Start time
-     *
-     * @return CreateItem
-     */
-    public function setStartT($startT)
-    {
-        return $this->set("pio_startT", $startT);
-    }
-
-    /**
-     * Set the "endT" parameter for the current command
-     *
-     * @param string $endT End time
-     *
-     * @return CreateItem
-     */
-    public function setEndT($endT)
-    {
-        return $this->set("pio_endT", $endT);
-    }
-
-    /**
-     * Set the "price" parameter for the current command
-     *
-     * @param float $price Price
-     *
-     * @return CreateItem
-     */
-    public function setPrice($price)
-    {
-        return $this->set("pio_price", $price);
-    }
-
-    /**
-     * Set the "profit" parameter for the current command
-     *
-     * @param float $profit Profit
-     *
-     * @return CreateItem
-     */
-    public function setProfit($profit)
-    {
-        return $this->set("pio_profit", $profit);
-    }
-
-    /**
-     * Set the "latlng" parameter for the current command
-     *
-     * In "latitude,longitude" format, e.g. "20.17,114.08"
-     *
-     * @param string $lat Latitude
-     * @param string $lng Longitude
-     *
-     * @return CreateItem
-     */
-    public function setLatlng($lat, $lng = null)
-    {
-        if (null === $lng) {
-            return $this->set("pio_latlng", $lat);
-        }
-
-        return $this->set("pio_latlng", sprintf("%s,%s", $lat, $lng));
-    }
-
-    /**
-     * Set the "inactive" parameter for the current command
-     *
-     * @param string $inactive Inactive flag (use "true" or "false" string)
-     *
-     * @return CreateItem
-     */
-    public function setInactive($inactive)
-    {
-        return $this->set("pio_inactive", $inactive);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(RequestInterface::POST, 'items', null, $this->getAll());
-    }
-}
diff --git a/src/PredictionIO/Command/CreateUser.php b/src/PredictionIO/Command/CreateUser.php
deleted file mode 100644
index 0a3c380..0000000
--- a/src/PredictionIO/Command/CreateUser.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Create User Information
- *
- * Create or re-create an information record for a user. Re-creation will remove all data of the current record.
- *
- * To supply custom user information, simply pass it in during command creation,
- * or use the "set" method inherited from Guzzle\Common\Collection.
- * <code>
- * $command = $client->getCommand('create_user', array('pio_uid' => 'foobar', 'custom1' => 'baz'));
- * $command->set('custom2', '0xdeadbeef');
- * </code>
- *
- * @guzzle uid type="string" required="true"
- * @guzzle latlng type="string"
- * @guzzle inactive type="boolean"
- */
-class CreateUser extends AbstractCommand
-{
-    /**
-     * Set the "uid" parameter for the current command
-     *
-     * @param string $uid User ID
-     *
-     * @return CreateUser
-     */
-    public function setUid($uid)
-    {
-        return $this->set("pio_uid", $uid);
-    }
-
-    /**
-     * Set the "latlng" parameter for the current command
-     *
-     * In "latitude,longitude" format, e.g. "20.17,114.08"
-     *
-     * @param string $lat Latitude
-     * @param string $lng Longitude
-     *
-     * @return CreateUser
-     */
-    public function setLatlng($lat, $lng = null)
-    {
-        if (null === $lng) {
-            return $this->set("pio_latlng", $lat);
-        }
-
-        return $this->set("pio_latlng", sprintf("%s,%s", $lat, $lng));
-    }
-
-    /**
-     * Set the "inactive" parameter for the current command
-     *
-     * @param bool $inactive Inactive flag (use "true" or "false" string)
-     *
-     * @return CreateUser
-     */
-    public function setInactive($inactive)
-    {
-        return $this->set("pio_inactive", $inactive);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(RequestInterface::POST, 'users', null, $this->getAll());
-    }
-}
diff --git a/src/PredictionIO/Command/DeleteItem.php b/src/PredictionIO/Command/DeleteItem.php
deleted file mode 100644
index c8df188..0000000
--- a/src/PredictionIO/Command/DeleteItem.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Delete Item Information
- *
- * Delete the information record of an item.
- *
- * @guzzle iid type="string" required="true"
- */
-class DeleteItem extends AbstractCommand
-{
-    /**
-     * Set the "iid" parameter for the current command
-     *
-     * @param string $iid Item ID
-     *
-     * @return DeleteItem
-     */
-    public function setIid($iid)
-    {
-        return $this->set('pio_iid', $iid);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(RequestInterface::DELETE, 'items/' . $this->get('pio_iid'));
-    }
-}
diff --git a/src/PredictionIO/Command/DeleteUser.php b/src/PredictionIO/Command/DeleteUser.php
deleted file mode 100644
index 4df9c1d..0000000
--- a/src/PredictionIO/Command/DeleteUser.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Delete User Information
- *
- * Delete the information record of a user.
- *
- * @guzzle uid type="string" required="true"
- */
-class DeleteUser extends AbstractCommand
-{
-    /**
-     * Set the "uid" parameter for the current command
-     *
-     * @param string $uid User ID
-     *
-     * @return DeleteUser
-     */
-    public function setUid($uid)
-    {
-        return $this->set('pio_uid', $uid);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(RequestInterface::DELETE, 'users/' . $this->get('pio_uid'));
-    }
-}
diff --git a/src/PredictionIO/Command/GetItem.php b/src/PredictionIO/Command/GetItem.php
deleted file mode 100644
index 368c595..0000000
--- a/src/PredictionIO/Command/GetItem.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Retrieve Item Information
- *
- * Get the information record of an item.
- * If startT/endT exists, it will be returned in UNIX UTC timestamp (milliseconds) format.
- *
- * @guzzle iid type="string" required="true"
- */
-class GetItem extends AbstractCommand
-{
-    /**
-     * Set the "iid" parameter for the current command
-     *
-     * @param string $iid Item ID
-     *
-     * @return GetItem
-     */
-    public function setIid($iid)
-    {
-        return $this->set('pio_iid', $iid);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(RequestInterface::GET, 'items/' . $this->get('pio_iid'));
-    }
-}
diff --git a/src/PredictionIO/Command/GetUser.php b/src/PredictionIO/Command/GetUser.php
deleted file mode 100644
index 0a1a0c4..0000000
--- a/src/PredictionIO/Command/GetUser.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Retrieve User Information
- *
- * Get the information record of a user.
- *
- * @guzzle uid type="string" required="true"
- */
-class GetUser extends AbstractCommand
-{
-    /**
-     * Set the "uid" parameter for the current command
-     *
-     * @param string $uid User ID
-     *
-     * @return GetUser
-     */
-    public function setUid($uid)
-    {
-        return $this->set('pio_uid', $uid);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(RequestInterface::GET, 'users/' . $this->get('pio_uid'));
-    }
-}
diff --git a/src/PredictionIO/Command/ItemrankGetRanked.php b/src/PredictionIO/Command/ItemrankGetRanked.php
deleted file mode 100644
index 317c41d..0000000
--- a/src/PredictionIO/Command/ItemrankGetRanked.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * ItemRank Engine Get Items Ranking for a User
- *
- * Retrieve ranking of items for this specific user.
- *
- * @guzzle engine type="string" required="true"
- * @guzzle iids type="string" required="true"
- */
-class ItemrankGetRanked extends AbstractCommand
-{
-    /**
-     * Set the "engine" parameter for the current command
-     *
-     * @param string $engine Engine Name
-     *
-     * @return ItemrankGetRanked
-     */
-    public function setEngine($engine)
-    {
-        return $this->set('pio_engine', $engine);
-    }
-
-    /**
-     * Set the "iids" parameter for the current command
-     *
-     * @param string $iids Comma-separated list of Item IDs
-     *
-     * @return ItemrankGetRanked
-     */
-    public function setIids($iids)
-    {
-        return $this->set('pio_iids', $iids);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->set('pio_uid', $this->client->getIdentity());
-        $this->request = $this->client->createRequest(
-            RequestInterface::GET,
-            'engines/itemrank/' . $this->get('pio_engine') . '/ranked',
-            null,
-            $this->getAll()
-        );
-    }
-}
diff --git a/src/PredictionIO/Command/ItemrecGetTopN.php b/src/PredictionIO/Command/ItemrecGetTopN.php
deleted file mode 100644
index a319483..0000000
--- a/src/PredictionIO/Command/ItemrecGetTopN.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * ItemRec Engine Get Top N Recommendations for a User
- *
- * Retrieve top N recommended items for this specific user.
- *
- * @guzzle engine type="string" required="true"
- * @guzzle n type="integer" required="true"
- * @guzzle itypes type="string"
- * @guzzle latlng type="string"
- * @guzzle within type="float"
- * @guzzle unit type="enum:km,mi"
- */
-class ItemrecGetTopN extends AbstractCommand
-{
-    /**
-     * Set the "engine" parameter for the current command
-     *
-     * @param string $engine Engine Name
-     *
-     * @return ItemrecGetTopN
-     */
-    public function setEngine($engine)
-    {
-        return $this->set('pio_engine', $engine);
-    }
-
-    /**
-     * Set the "n" parameter for the current command
-     *
-     * @param integer $n N
-     *
-     * @return ItemrecGetTopN
-     */
-    public function setN($n)
-    {
-        return $this->set('pio_n', $n);
-    }
-
-    /**
-     * Set the "itypes" parameter for the current command
-     *
-     * $itypes can be supplied as an array of integers, or a "," delimited list of integers.
-     *
-     * @param array|string $itypes Item types
-     *
-     * @return ItemrecGetTopN
-     */
-    public function setItypes($itypes)
-    {
-        if (is_array($itypes)) {
-            return $this->set('pio_itypes', implode(',', $itypes));
-        } else {
-            return $this->set('pio_itypes', $itypes);
-        }
-    }
-
-    /**
-     * Set the "latlng" parameter for the current command
-     *
-     * In "latitude,longitude" format, e.g. "20.17,114.08"
-     *
-     * @param string $lat Latitude
-     * @param string $lng Longitude
-     *
-     * @return ItemrecGetTopN
-     */
-    public function setLatlng($lat,$lng=null)
-    {
-        if ($lng === null) {
-            return $this->set("pio_latlng", $lat);
-        } else {
-            return $this->set("pio_latlng", sprintf("%s,%s", $lat, $lng));
-        }
-    }
-
-    /**
-     * Set the "within" parameter for the current command
-     *
-     * @param float $within Radius
-     *
-     * @return ItemrecGetTopN
-     */
-    public function setWithin($within)
-    {
-        return $this->set('pio_within', $within);
-    }
-
-    /**
-     * Set the "unit" parameter for the current command
-     *
-     * @param string $unit Unit of radius
-     *
-     * @return ItemrecGetTopN
-     */
-    public function setUnit($unit)
-    {
-        return $this->set('pio_unit', $unit);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->set('pio_uid', $this->client->getIdentity());
-        $this->request = $this->client->createRequest(
-            RequestInterface::GET,
-            'engines/itemrec/' . $this->get('pio_engine') . '/topn',
-            null,
-            $this->getAll()
-        );
-    }
-}
diff --git a/src/PredictionIO/Command/ItemsimGetTopN.php b/src/PredictionIO/Command/ItemsimGetTopN.php
deleted file mode 100644
index 217f810..0000000
--- a/src/PredictionIO/Command/ItemsimGetTopN.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * ItemSim Engine Get Top N Similar Items for an Item
- *
- * Retrieve top N similar items for this specific item.
- *
- * @guzzle engine type="string" required="true"
- * @guzzle iid type="string" required="true"
- * @guzzle n type="integer" required="true"
- * @guzzle itypes type="string"
- * @guzzle latlng type="string"
- * @guzzle within type="float"
- * @guzzle unit type="enum:km,mi"
- */
-class ItemsimGetTopN extends AbstractCommand
-{
-    /**
-     * Set the "engine" parameter for the current command
-     *
-     * @param string $engine Engine Name
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setEngine($engine)
-    {
-        return $this->set('pio_engine', $engine);
-    }
-
-    /**
-     * Set the "iid" parameter for the current command
-     *
-     * @param string $iid Item ID
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setIid($iid)
-    {
-        return $this->set('pio_iid', $iid);
-    }
-
-    /**
-     * Set the "n" parameter for the current command
-     *
-     * @param integer $n N
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setN($n)
-    {
-        return $this->set('pio_n', $n);
-    }
-
-    /**
-     * Set the "itypes" parameter for the current command
-     *
-     * $itypes can be supplied as an array of integers, or a "," delimited list of integers.
-     *
-     * @param array|string $itypes Item types
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setItypes($itypes)
-    {
-        if (is_array($itypes)) {
-            return $this->set('pio_itypes', implode(',', $itypes));
-        }
-
-        return $this->set('pio_itypes', $itypes);
-    }
-
-    /**
-     * Set the "latlng" parameter for the current command
-     *
-     * In "latitude,longitude" format, e.g. "20.17,114.08"
-     *
-     * @param string $lat Latitude
-     * @param string $lng Longitude
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setLatlng($lat,$lng=null)
-    {
-        if ($lng === null) {
-            return $this->set("pio_latlng", $lat);
-        } else {
-            return $this->set("pio_latlng", sprintf("%s,%s", $lat, $lng));
-        }
-    }
-
-    /**
-     * Set the "within" parameter for the current command
-     *
-     * @param float $within Radius
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setWithin($within)
-    {
-        return $this->set('pio_within', $within);
-    }
-
-    /**
-     * Set the "unit" parameter for the current command
-     *
-     * @param string $unit Unit of radius
-     *
-     * @return ItemsimGetTopN
-     */
-    public function setUnit($unit)
-    {
-        return $this->set('pio_unit', $unit);
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->request = $this->client->createRequest(
-            RequestInterface::GET,
-            'engines/itemsim/' . $this->get('pio_engine') . '/topn',
-            null,
-            $this->getAll()
-        );
-    }
-}
diff --git a/src/PredictionIO/Command/RecordActionOnItem.php b/src/PredictionIO/Command/RecordActionOnItem.php
deleted file mode 100644
index 09e3d57..0000000
--- a/src/PredictionIO/Command/RecordActionOnItem.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-namespace PredictionIO\Command;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * Record an action on an item by a user
- *
- * @guzzle action type="string" required="true"
- * @guzzle iid type="string" required="true"
- * @guzzle price type="float"
- * @guzzle t type="string"
- * @guzzle latlng type="string"
- */
-class RecordActionOnItem extends AbstractCommand
-{
-    /**
-     * Set the "action" parameter for the current command
-     *
-     * @param string $action Action name
-     *
-     * @return RecordActionOnItem
-     */
-    public function setAction($action)
-    {
-        return $this->set('pio_action', $action);
-    }
-
-    /**
-     * Set the "iid" parameter for the current command
-     *
-     * @param string $iid Item ID
-     *
-     * @return RecordActionOnItem
-     */
-    public function setIid($iid)
-    {
-        return $this->set('pio_iid', $iid);
-    }
-
-    /**
-     * Set the "t" parameter for the current command
-     *
-     * @param string $t Time
-     *
-     * @return RecordActionOnItem
-     */
-    public function setT($t)
-    {
-        return $this->set('pio_t', $t * 1000);
-    }
-
-    /**
-     * Set the "latlng" parameter for the current command
-     *
-     * In "latitude,longitude" format, e.g. "20.17,114.08"
-     *
-     * @param string $lat Latitude
-     * @param string $lng Longitude
-     *
-     * @return RecordActionOnItem
-     */
-    public function setLatlng($lat, $lng = null)
-    {
-        if (null === $lng) {
-            return $this->set("pio_latlng", $lat);
-        }
-
-        return $this->set("pio_latlng", sprintf("%s,%s", $lat, $lng));
-    }
-
-    /**
-     * Create the request object that will carry out the command. Used internally by Guzzle.
-     */
-    protected function build()
-    {
-        $this->set('pio_uid', $this->client->getIdentity());
-        $this->request = $this->client->createRequest(RequestInterface::POST, 'actions/u2i', null, $this->getAll());
-    }
-}
diff --git a/src/PredictionIO/PredictionIOClient.php b/src/PredictionIO/PredictionIOClient.php
deleted file mode 100644
index e8dd60b..0000000
--- a/src/PredictionIO/PredictionIOClient.php
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-
-namespace PredictionIO;
-
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Http\EntityBodyInterface;
-use Guzzle\Common\Collection;
-use Guzzle\Service\Client;
-use Guzzle\Service\Command\AbstractCommand;
-
-/**
- * PredictionIO API Client
- *
- * This package is a web service client based on Guzzle.
- * A few quick examples are shown below.
- * For a full user guide on how to take advantage of all Guzzle features, please refer to http://guzzlephp.org.
- * Specifically, http://guzzlephp.org/tour/using_services.html#using-client-objects describes
- * how to use a web service client.
- *
- * Many REST request commands support optional arguments. They can be supplied to these commands using the set method.
- *
- * <b>Instantiate PredictionIO API Client</b>
- * <code>
- * require_once("predictionio.phar");
- * use PredictionIO\PredictionIOClient;
- * $client = PredictionIOClient::factory(array("appkey" => "<your app key>"));
- * </code>
- *
- * <b>Import a User Record from Your App</b>
- * <code>
- * // (your user registration logic)
- * $uid = get_user_from_your_db();
- * $command = $client->getCommand('create_user', array('pio_uid' => $uid));
- * $response = $client->execute($command);
- * // (other work to do for the rest of the page)
- * </code>
- *
- * <b>Import a User Action (View) form Your App</b>
- * <code>
- * $client->identify('4');
- * $client->execute($client->getCommand('record_action_on_item', array('pio_action' => 'view', 'pio_iid' => '15')));
- * // (other work to do for the rest of the page)
- * </code>
- *
- * <b>Retrieving Top N Recommendations for a User</b>
- * <code>
- * $client->identify('4');
- * $client->execute($client->getCommand('itemrec_get_top_n', array('pio_engine' => 'test', 'pio_n' => 10)));
- * // (work you need to do for the page (rendering, db queries, etc))
- * </code>
- *
- * @author The PredictionIO Team (http://prediction.io)
- * @copyright 2013 TappingStone Inc.
- */
-class PredictionIOClient extends Client
-{
-    private $apiuid = NULL;
-
-    /**
-     * Factory method to create a new PredictionIOClient
-     *
-     * Configuration data is an array with these keys:
-     * * appkey - App key of your PredictionIO app (required)
-     * * apiurl - URL of API endpoint (optional)
-     *
-     * @param array|Collection $config Configuration data.
-     *
-     * @return PredictionIOClient
-     */
-    public static function factory($config = array())
-    {
-        $default = array('apiurl' => 'http://localhost:8000');
-        $required = array('appkey');
-        $config = Collection::fromConfig($config, $default, $required);
-
-        $client = new self($config->get('apiurl'), $config);
-
-        return $client;
-    }
-
-    /**
-     * Identify the user ID that will be used by all subsequent
-     * recording of actions on items, and recommendations retrieval.
-     *
-     * @param string $uid User ID.
-     */
-    public function identify($uid)
-    {
-        $this->apiuid = $uid;
-    }
-
-    /**
-     * Returns the identity (user ID) that will be used by all subsequent
-     * recording of actions on items, and recommendations retrieval.
-     *
-     * @return string
-     * @throws UnidentifiedUserException
-     */
-    public function getIdentity()
-    {
-        if (!isset($this->apiuid)) {
-            throw new UnidentifiedUserException("Must call identify() before performing any user-related commands.");
-        }
-
-        return $this->apiuid;
-    }
-
-    /**
-     * Create and return a new RequestInterface configured for the client.
-     *
-     * Used internally by the library. Do not use directly.
-     *
-     * @param string                                    $method  HTTP method. Default to GET.
-     * @param string|array                              $uri     Resource URI
-     * @param array|Collection                          $headers HTTP headers
-     * @param string|resource|array|EntityBodyInterface $body    Entity body of request (POST/PUT) or response (GET)
-     * @param array                                     $options Array of options to apply to the request
-     *
-     * @return RequestInterface
-     */
-    public function createRequest($method = RequestInterface::GET, $uri = null, $headers = null, $body = null, array $options = array())
-    {
-        if (is_array($body)) {
-            $body['pio_appkey'] = $this->getConfig()->get("appkey");
-        } else {
-            $body = array('pio_appkey' => $this->getConfig()->get("appkey"));
-        }
-
-        // Remove Guzzle internals to prevent them from going to the API
-        unset($body[AbstractCommand::HEADERS_OPTION]);
-        unset($body[AbstractCommand::ON_COMPLETE]);
-        unset($body[AbstractCommand::DISABLE_VALIDATION]);
-        unset($body[AbstractCommand::RESPONSE_PROCESSING]);
-        unset($body[AbstractCommand::RESPONSE_BODY]);
-        unset($body[AbstractCommand::HIDDEN_PARAMS]);
-
-        if ($method == RequestInterface::GET || $method == RequestInterface::DELETE) {
-            $request = parent::createRequest($method, $uri, $headers, null, $options);
-            $request->getQuery()->replace($body);
-        } else {
-            $request = parent::createRequest($method, $uri, $headers, $body, $options);
-        }
-        $request->setPath($request->getPath() . ".json");
-
-        return $request;
-    }
-}
\ No newline at end of file
diff --git a/src/PredictionIO/UnidentifiedUserException.php b/src/PredictionIO/UnidentifiedUserException.php
deleted file mode 100644
index 637fda1..0000000
--- a/src/PredictionIO/UnidentifiedUserException.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-namespace PredictionIO;
-
-/**
- * Thrown when user-related commands are called before identify() is called.
- */
-class UnidentifiedUserException extends \Exception
-{
-
-}
diff --git a/src/predictionio/BaseClient.php b/src/predictionio/BaseClient.php
new file mode 100644
index 0000000..83376de
--- /dev/null
+++ b/src/predictionio/BaseClient.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace predictionio;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\ClientException;
+
+/**
+ * Base client for Event and Engine client
+ *
+ */
+abstract class BaseClient {
+  private $baseUrl;
+  public $client;
+
+  /**
+   * @param string Base URL to the server
+   * @param float Timeout of the request in seconds. Use 0 to wait indefinitely
+   * @param float Number of seconds to wait while trying to connect to a server
+   */  
+  public function __construct($baseUrl, $timeout, $connectTimeout) {
+    $this->baseUrl = $baseUrl;
+    $this->client = new Client([
+           'base_url' => $this->baseUrl,
+           'defaults' => ['timeout' => $timeout, 
+                          'connect_timeout' => $connectTimeout]
+    ]);
+
+  }
+
+  /**
+   * Get the status of the Event Server or Engine Instance
+   *
+   * @return string status
+   */
+  public function getStatus() {
+    return $this->client->get('/')->getBody();
+  }
+
+  /**
+   * Send a HTTP request to the server
+   *
+   * @param string HTTP request method
+   * @param string Relative or absolute url
+   * @param string HTTP request body
+   *
+   * @return array JSON response
+   * @throws PredictionIOAPIError Request error
+   */
+  protected function sendRequest($method, $url, $body) {
+    $options = ['headers' => ['Content-Type' => 'application/json'],
+                'body' => $body]; 
+    $request = $this->client->createRequest($method, $url, $options);
+
+    try {
+      $response = $this->client->send($request);
+      return $response->json();
+    } catch (ClientException $e) {
+      throw new PredictionIOAPIError($e->getMessage()); 
+    }
+  }
+}
+?>
diff --git a/src/predictionio/EngineClient.php b/src/predictionio/EngineClient.php
new file mode 100644
index 0000000..f45ac35
--- /dev/null
+++ b/src/predictionio/EngineClient.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace predictionio;
+
+/**
+ * Client for connecting to an Engine Instance
+ *
+ */
+class EngineClient extends BaseClient {
+
+  /**
+   * @param string Base URL to the Engine Instance. Default is localhost:8000.
+   * @param float Timeout of the request in seconds. Use 0 to wait indefinitely
+   *              Default is 0.
+   * @param float Number of seconds to wait while trying to connect to a server.
+   *              Default is 5.
+   */
+  public function __construct($baseUrl="http://localhost:8000",
+                              $timeout=0, $connectTimeout=5 ) {
+    parent::__construct($baseUrl, $timeout, $connectTimeout);
+  }
+
+  /**
+   * Send prediction query to an Engine Instance
+   *
+   * @param array Query
+   *
+   * @return array JSON response
+   *
+   * @throws PredictionIOAPIError Request error
+   */
+  public function sendQuery(array $query) {
+    return $this->sendRequest("POST", "/queries.json", json_encode($query));
+  }
+}
+
+?>
diff --git a/src/predictionio/EventClient.php b/src/predictionio/EventClient.php
new file mode 100644
index 0000000..13918f5
--- /dev/null
+++ b/src/predictionio/EventClient.php
@@ -0,0 +1,271 @@
+<?php
+
+namespace predictionio;
+use GuzzleHttp\Client;
+use \DateTime;
+ 
+/**
+ * Client for connecting to an Event Server
+ *
+ */
+class EventClient extends BaseClient {
+  const DATE_TIME_FORMAT = DateTime::ISO8601;
+  private $appId;
+
+  /**
+   * @param string Base URL to the Event Server. Default is localhost:7070.
+   * @param float Timeout of the request in seconds. Use 0 to wait indefinitely
+   *              Default is 0.
+   * @param float Number of seconds to wait while trying to connect to a server.
+   *              Default is 5.                
+   */
+  public function __construct($appId, $baseUrl='http://localhost:7070',
+                              $timeout=0, $connectTimeout=5 ) {
+    parent::__construct($baseUrl, $timeout, $connectTimeout);
+    $this->appId = $appId;
+  }
+
+  private function getEventTime($eventTime) {
+    $result = $eventTime;
+    if (!isset($eventTime)) {
+      $result = (new DateTime('NOW'))->format(self::DATE_TIME_FORMAT);
+    } 
+
+    return $result;
+  }
+
+  /**
+   * Set a user entity
+   *
+   * @param int|string User Id 
+   * @param array Properties of the user entity to set
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function setUser($uid, array $properties=array(), $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+
+    // casting to object so that an empty array would be represented as {}
+    if (empty($properties)) $properties = (object)$properties;
+
+    $json = json_encode([
+        'event' => '$set',
+        'entityType' => 'pio_user',
+        'entityId' => $uid,
+        'appId' => $this->appId,
+        'properties' => $properties,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Unset a user entity
+   *
+   * @param int|string User Id 
+   * @param array Properties of the user entity to unset
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function unsetUser($uid, array $properties, $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+    if (empty($properties)) 
+      throw new PredictionIOAPIError('Specify at least one property'); 
+
+    $json = json_encode([
+        'event' => '$unset',
+        'entityType' => 'pio_user',
+        'entityId' => $uid,
+        'appId' => $this->appId,
+        'properties' => $properties,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Delete a user entity
+   *
+   * @param int|string User Id 
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function deleteUser($uid, $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+
+    $json = json_encode([
+        'event' => '$delete',
+        'entityType' => 'pio_user',
+        'entityId' => $uid,
+        'appId' => $this->appId,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+ 
+  /**
+   * Set an item entity
+   *
+   * @param int|string Item Id 
+   * @param array Properties of the item entity to set
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+   public function setItem($iid, array $properties=array(), $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+    if (empty($properties)) $properties = (object)$properties;
+    $json = json_encode([
+        'event' => '$set',
+        'entityType' => 'pio_item',
+        'entityId' => $iid,
+        'appId' => $this->appId,
+        'properties' => $properties,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Unset an item entity
+   *
+   * @param int|string Item Id 
+   * @param array Properties of the item entity to unset
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function unsetItem($iid, array $properties, $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+    if (empty($properties))
+        throw new PredictionIOAPIError('Specify at least one property'); 
+    $json = json_encode([
+        'event' => '$unset',
+        'entityType' => 'pio_item',
+        'entityId' => $iid,
+        'appId' => $this->appId,
+        'properties' => $properties,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Delete an item entity
+   *
+   * @param int|string Item Id 
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function deleteItem($iid, $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+
+    $json = json_encode([
+        'event' => '$delete',
+        'entityType' => 'pio_item',
+        'entityId' => $iid,
+        'appId' => $this->appId,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Record a user action on an item
+   *
+   * @param string Event name
+   * @param int|string User Id 
+   * @param int|string Item Id 
+   * @param array Properties of the event
+   * @param string Time of the event in ISO 8601 format
+   *               (e.g. 2014-09-09T16:17:42.937-08:00).
+   *               Default is the current time.
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+   public function recordUserActionOnItem($event, $uid, $iid, 
+                                         array $properties=array(),
+                                         $eventTime=null) {
+    $eventTime = $this->getEventTime($eventTime);
+    if (empty($properties)) $properties = (object)$properties;
+    $json = json_encode([
+        'event' => $event,
+        'entityType' => 'pio_user',
+        'entityId' => $uid,
+        'targetEntityType' => 'pio_item',
+        'targetEntityId' => $iid,
+        'appId' => $this->appId,
+        'properties' => $properties,
+        'eventTime' => $eventTime,
+    ]);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Create an event
+   *
+   * @param array An array describing the event
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function createEvent(array $data) {
+    $data['appId'] = $this->appId;
+    $json = json_encode($data);
+
+    return $this->sendRequest('POST', '/events.json', $json);
+  }
+
+  /**
+   * Retrieve an event
+   *
+   * @param string Event ID
+   *
+   * @return string JSON response
+   * 
+   * @throws PredictionIOAPIError Request error
+   */
+  public function getEvent($eventId) {
+    return $this->sendRequest('GET', "/events/$eventId.json", '');
+  }
+}
+
+?>
diff --git a/src/predictionio/PredictionIOAPIError.php b/src/predictionio/PredictionIOAPIError.php
new file mode 100644
index 0000000..ca4cacd
--- /dev/null
+++ b/src/predictionio/PredictionIOAPIError.php
@@ -0,0 +1,9 @@
+<?php
+namespace predictionio;
+/**
+ * Thrown when there is an error with the request.
+ */
+class PredictionIOAPIError extends \Exception {
+
+}
+?>
diff --git a/tests/Unit/Command/CreateItemTest.php b/tests/Unit/Command/CreateItemTest.php
deleted file mode 100644
index 09d9a67..0000000
--- a/tests/Unit/Command/CreateItemTest.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit\Command;
-
-use PredictionIO\Command\CreateItem;
-
-/**
- * Class CreateItemTest
- *
- * @package PredictionIO\Tests\Unit\Command
- */
-class CreateItemTest extends \PHPUnit_Framework_TestCase
-{
-    public function testCreateItemSetIid()
-    {
-        $mockIid = '1234';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setIid($mockIid);
-
-        $this->assertSame($mockIid, $createItemCommand->get('pio_iid'));
-    }
-
-    public function testCreateItemSetItypesImplodesAnArrayToCommaDelimitedString()
-    {
-        $mockTypes = array(
-            'happy',
-            'mocked',
-            'types',
-        );
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setItypes($mockTypes);
-
-        $this->assertSame(implode(',', $mockTypes), $createItemCommand->get('pio_itypes'));
-    }
-
-    public function testCreateItemSetItypesFromString()
-    {
-        $mockType = 'happy.mocked.type';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setItypes($mockType);
-
-        $this->assertSame($mockType, $createItemCommand->get('pio_itypes'));
-    }
-
-    public function testCreateItemSetStartTSuccess()
-    {
-        $mockStartT = 'mock.startT';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setStartT($mockStartT);
-
-        $this->assertSame($mockStartT, $createItemCommand->get('pio_startT'));
-    }
-
-    public function testCreateItemSetEndTSuccess()
-    {
-        $mockEndT = 'mock.endT';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setEndT($mockEndT);
-
-        $this->assertSame($mockEndT, $createItemCommand->get('pio_endT'));
-    }
-
-    public function testCreateItemSetPriceSuccess()
-    {
-        $mockPrice = '1234.56';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setPrice($mockPrice);
-
-        $this->assertSame($mockPrice, $createItemCommand->get('pio_price'));
-    }
-
-    public function testCreateItemSetProfitSuccess()
-    {
-        $mockProfit = '9876.54';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setProfit($mockProfit);
-
-        $this->assertSame($mockProfit, $createItemCommand->get('pio_profit'));
-    }
-
-    public function testCreateItemSetLatlngSuccess()
-    {
-        $mockLatlng = '20.17,114.08';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setLatlng($mockLatlng);
-
-        $this->assertSame($mockLatlng, $createItemCommand->get('pio_latlng'));
-    }
-
-    public function testCreateItemSetInactiveSuccess()
-    {
-        $mockInactive = 'true';
-        $createItemCommand = new CreateItem();
-        $createItemCommand->setInactive($mockInactive);
-
-        $this->assertSame($mockInactive, $createItemCommand->get('pio_inactive'));
-    }
-}
diff --git a/tests/Unit/Command/CreateUserTest.php b/tests/Unit/Command/CreateUserTest.php
deleted file mode 100644
index 15ff7ba..0000000
--- a/tests/Unit/Command/CreateUserTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit\Command;
-
-use PredictionIO\Command\CreateUser;
-
-/**
- * Class CreateUserTest
- *
- * @package PredictionIO\Tests\Unit\Command
- */
-class CreateUserTest extends \PHPUnit_Framework_TestCase
-{
-    public function testCreateUserSetIid()
-    {
-        $mockUid = '1234';
-        $createUserCommand = new CreateUser();
-        $createUserCommand->setUid($mockUid);
-
-        $this->assertSame($mockUid, $createUserCommand->get('pio_uid'));
-    }
-
-    public function testCreateUserSetLatlngSuccess()
-    {
-        $mockLatlng = '20.17,114.08';
-        $createUserCommand = new CreateUser();
-        $createUserCommand->setLatlng($mockLatlng);
-
-        $this->assertSame($mockLatlng, $createUserCommand->get('pio_latlng'));
-    }
-
-    public function testCreateUserSetInactiveSuccess()
-    {
-        $mockInactive = 'true';
-        $createUserCommand = new CreateUser();
-        $createUserCommand->setInactive($mockInactive);
-
-        $this->assertSame($mockInactive, $createUserCommand->get('pio_inactive'));
-    }
-}
diff --git a/tests/Unit/Command/DeleteItemTest.php b/tests/Unit/Command/DeleteItemTest.php
deleted file mode 100644
index 5ab8cad..0000000
--- a/tests/Unit/Command/DeleteItemTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit\Command;
-
-use PredictionIO\Command\DeleteItem;
-
-/**
- * Class DeleteItemTest
- *
- * @package PredictionIO\Tests\Unit\Command
- */
-class DeleteItemTest extends \PHPUnit_Framework_TestCase
-{
-    public function testDeleteItemSetIid()
-    {
-        $mockIid = '1234';
-        $deleteItemCommand = new DeleteItem();
-        $deleteItemCommand->setIid($mockIid);
-
-        $this->assertSame($mockIid, $deleteItemCommand->get('pio_iid'));
-    }
-}
diff --git a/tests/Unit/Command/DeleteUserTest.php b/tests/Unit/Command/DeleteUserTest.php
deleted file mode 100644
index 4b92e02..0000000
--- a/tests/Unit/Command/DeleteUserTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit\Command;
-
-use PredictionIO\Command\DeleteUser;
-
-/**
- * Class DeleteUserTest
- *
- * @package PredictionIO\Tests\Unit\Command
- */
-class DeleteUserTest extends \PHPUnit_Framework_TestCase
-{
-    public function testDeleteUserSetIid()
-    {
-        $mockUid = '1234';
-        $deleteUserCommand = new DeleteUser();
-        $deleteUserCommand->setUid($mockUid);
-
-        $this->assertSame($mockUid, $deleteUserCommand->get('pio_uid'));
-    }
-}
diff --git a/tests/Unit/Command/GetItemTest.php b/tests/Unit/Command/GetItemTest.php
deleted file mode 100644
index 3609abd..0000000
--- a/tests/Unit/Command/GetItemTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit\Command;
-
-use PredictionIO\Command\GetItem;
-
-/**
- * Class GetItemTest
- *
- * @package PredictionIO\Tests\Unit\Command
- */
-class GetItemTest extends \PHPUnit_Framework_TestCase
-{
-    public function testGetItemSetIid()
-    {
-        $mockIid = '1234';
-        $getItemCommand = new GetItem();
-        $getItemCommand->setIid($mockIid);
-
-        $this->assertSame($mockIid, $getItemCommand->get('pio_iid'));
-    }
-}
diff --git a/tests/Unit/Command/GetUserTest.php b/tests/Unit/Command/GetUserTest.php
deleted file mode 100644
index 0c6f8eb..0000000
--- a/tests/Unit/Command/GetUserTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit\Command;
-
-use PredictionIO\Command\GetUser;
-
-/**
- * Class GetUserTest
- *
- * @package PredictionIO\Tests\Unit\Command
- */
-class GetUserTest extends \PHPUnit_Framework_TestCase
-{
-    public function testGetUserSetIid()
-    {
-        $mockUid = '1234';
-        $getUserCommand = new GetUser();
-        $getUserCommand->setUid($mockUid);
-
-        $this->assertSame($mockUid, $getUserCommand->get('pio_uid'));
-    }
-}
diff --git a/tests/Unit/EngineClientTest.php b/tests/Unit/EngineClientTest.php
new file mode 100644
index 0000000..bfa74f5
--- /dev/null
+++ b/tests/Unit/EngineClientTest.php
@@ -0,0 +1,35 @@
+<?php
+namespace predictionio\tests\Unit;
+
+use GuzzleHttp\Subscriber\History;
+use GuzzleHttp\Client;
+use GuzzleHttp\Subscriber\Mock;
+use GuzzleHttp\Message\Response;
+use predictionio\EngineClient;
+
+class EngineClientTest extends \PHPUnit_Framework_TestCase {
+  protected $engineClient;
+  protected $history;
+
+  protected function setUp() {
+    $this->engineClient=new EngineClient();
+    $this->history=new History();
+    $mock = new Mock([new Response(200)]);
+    $this->engineClient->client->getEmitter()->attach($this->history);
+    $this->engineClient->client->getEmitter()->attach($mock);
+  }
+
+  public function testSendQuery() {
+    $this->engineClient->sendQuery(array('uid'=>5, 'iids'=>array(1,2,3)));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals(5,$body['uid']);
+    $this->assertEquals(array(1,2,3),$body['iids']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:8000/queries.json',$request->getUrl());
+  }
+
+}
+
+?>
diff --git a/tests/Unit/EventClientTest.php b/tests/Unit/EventClientTest.php
new file mode 100644
index 0000000..eaf72ed
--- /dev/null
+++ b/tests/Unit/EventClientTest.php
@@ -0,0 +1,282 @@
+<?php
+namespace predictionio\tests\Unit;
+
+use GuzzleHttp\Subscriber\History;
+use GuzzleHttp\Client;
+use GuzzleHttp\Subscriber\Mock;
+use GuzzleHttp\Message\Response;
+use predictionio\EventClient;
+
+class EventClientTest extends \PHPUnit_Framework_TestCase {
+  protected $eventClient;
+  protected $history;
+
+  protected function setUp() {
+    $this->eventClient=new EventClient(8);
+    $this->history=new History();
+    $mock = new Mock([new Response(200)]);
+    $this->eventClient->client->getEmitter()->attach($this->history);
+    $this->eventClient->client->getEmitter()->attach($mock);
+  }
+
+  public function testSetUser() {
+    $this->eventClient->setUser(1,array('age'=>20));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$set',$body['event']);
+    $this->assertEquals('pio_user',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertEquals(20,$body['properties']['age']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testSetUserWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->setUser(1,array('age'=>20), $eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$set',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  public function testUnsetUser() {
+    $this->eventClient->unsetUser(1,array('age'=>20));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$unset',$body['event']);
+    $this->assertEquals('pio_user',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertEquals(20,$body['properties']['age']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testUnsetUserWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->unsetUser(1,array('age'=>20), $eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$unset',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  /**
+   * @expectedException predictionio\PredictionIOAPIError
+   */
+  public function testUnsetUserWithoutProperties() {
+    $this->eventClient->unsetUser(1, array());
+  }
+  
+  public function testDeleteUser() {
+    $this->eventClient->deleteUser(1);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$delete',$body['event']);
+    $this->assertEquals('pio_user',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testDeleteUserWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->deleteUser(1, $eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$delete',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  public function testSetItem() {
+    $this->eventClient->setItem(1,array('type'=>'book'));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$set',$body['event']);
+    $this->assertEquals('pio_item',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertEquals('book',$body['properties']['type']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testSetItemWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->setItem(1,array('type'=>'book'), $eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$set',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  public function testUnsetItem() {
+    $this->eventClient->unsetItem(1,array('type'=>'book'));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$unset',$body['event']);
+    $this->assertEquals('pio_item',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertEquals('book',$body['properties']['type']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testUnsetItemWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->unsetItem(1,array('type'=>'book'), $eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$unset',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  /**
+   * @expectedException predictionio\PredictionIOAPIError
+   */
+  public function testUnsetItemWithoutProperties() {
+    $this->eventClient->unsetItem(1, array());
+  }
+
+  public function testDeleteItem() {
+    $this->eventClient->deleteItem(1);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$delete',$body['event']);
+    $this->assertEquals('pio_item',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testDeleteItemWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->deleteItem(1, $eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('$delete',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  public function testRecordAction() {
+    $this->eventClient->recordUserActionOnItem('view',1,888, array('count'=>2));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('view',$body['event']);
+    $this->assertEquals('pio_user',$body['entityType']);
+    $this->assertEquals(1,$body['entityId']);
+    $this->assertEquals('pio_item',$body['targetEntityType']);
+    $this->assertEquals(888,$body['targetEntityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertEquals(2,$body['properties']['count']);
+    $this->assertNotNull($body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testRecordActionWithEventTime() {
+    $eventTime='1982-09-25T01:23:45+0800';
+
+    $this->eventClient->recordUserActionOnItem('view',1, 8, array(),$eventTime);
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('view',$body['event']);
+    $this->assertEquals($eventTime, $body['eventTime']);
+  }
+
+  public function testCreateEvent() {
+    $this->eventClient->createEvent(array(
+                        'appId' => 8,
+                        'event' => 'my_event',
+                        'entityType' => 'user',
+                        'entityId' => 'uid',
+                        'properties' => array('prop1'=>1,
+                                              'prop2'=>'value2',
+                                              'prop3'=>array(1,2,3),
+                                              'prop4'=>true,
+                                              'prop5'=>array('a','b','c'),
+                                              'prop6'=>4.56
+                                        ),
+                        'eventTime' => '2004-12-13T21:39:45.618-07:00'
+                       ));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('my_event',$body['event']);
+    $this->assertEquals('user',$body['entityType']);
+    $this->assertEquals('uid',$body['entityId']);
+    $this->assertEquals(8,$body['appId']);
+    $this->assertEquals(1,$body['properties']['prop1']);
+    $this->assertEquals('value2',$body['properties']['prop2']);
+    $this->assertEquals(array(1,2,3),$body['properties']['prop3']);
+    $this->assertEquals(true,$body['properties']['prop4']);
+    $this->assertEquals(array('a','b','c'),$body['properties']['prop5']);
+    $this->assertEquals(4.56,$body['properties']['prop6']);
+    $this->assertEquals('2004-12-13T21:39:45.618-07:00',$body['eventTime']);
+    $this->assertEquals('POST',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events.json',$request->getUrl());
+  }
+
+  public function testCreateEventUsesAppIdInClient() {
+    $this->eventClient->createEvent(array(
+                        'appId' => 99,
+                        'event' => 'my_event',
+                        'entityType' => 'user',
+                        'entityId' => 'uid',
+                        'properties' => array('prop1'=>1),
+                        'eventTime' => '2004-12-13T21:39:45.618-07:00'
+                       ));
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    // ignores appId in data and uses the one defined in the event client
+    $this->assertEquals(8,$body['appId']);
+  }
+
+  public function testGetEvent() {
+    $this->eventClient->getEvent('event_id');
+    $request=$this->history->getLastRequest();
+    $body=json_decode($request->getBody(), true);
+
+    $this->assertEquals('GET',$request->getMethod());
+    $this->assertEquals('http://localhost:7070/events/event_id.json',
+                $request->getUrl());
+  }
+
+
+
+
+}
+?>
+
diff --git a/tests/Unit/PredictionIOClientTest.php b/tests/Unit/PredictionIOClientTest.php
deleted file mode 100644
index 2333eee..0000000
--- a/tests/Unit/PredictionIOClientTest.php
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-
-namespace PredictionIO\Tests\Unit;
-
-use Guzzle\Common\Collection;
-use Guzzle\Http\Message\RequestInterface;
-use Guzzle\Service\Command\AbstractCommand;
-use PredictionIO\PredictionIOClient;
-
-/**
- * Class PredictionIOClientTest
- *
- * @package PredictionIO\Tests
- */
-class PredictionIOClientTest extends \PHPUnit_Framework_TestCase
-{
-    /**
-     * @expectedException \Guzzle\Common\Exception\InvalidArgumentException
-     * @expectedMessage   Config must contain a 'appkey' key
-     */
-    public function testFactoryThrowsExceptionWithNoAppKey()
-    {
-        $config = array();
-        PredictionIOClient::factory($config);
-    }
-
-    public function testFactoryReturnsNewClient()
-    {
-        $config = array(
-            'appkey' => 'mock.appkey',
-        );
-        $client = PredictionIOClient::factory($config);
-
-        $this->assertInstanceOf('\PredictionIO\PredictionIOClient', $client);
-        $this->assertSame($config['appkey'], $client->getConfig('appkey'));
-    }
-
-    public function testFactoryWithDefaultApiUrlSuccess()
-    {
-        $defaultApiUrl = 'http://localhost:8000';
-        $config = array(
-            'appkey' => 'mock.appkey',
-        );
-        $client = PredictionIOClient::factory($config);
-
-        $this->assertSame($defaultApiUrl, $client->getConfig('apiurl'));
-    }
-
-    public function testFactoryWithDefinedApiUrlSuccess()
-    {
-        $config = array(
-            'appkey' => 'mock.appkey',
-            'apiurl' => 'http://mock.api:1234',
-        );
-        $client = PredictionIOClient::factory($config);
-
-        $this->assertSame($client->getConfig('apiurl'), $client->getConfig('apiurl'));
-    }
-
-    public function testFactoryWithCustomConfigSuccess()
-    {
-        $config = array(
-            'appkey' => 'mock.appkey',
-            'customconfig' => 'mock.option',
-        );
-        $client = PredictionIOClient::factory($config);
-
-        $this->assertSame($client->getConfig('customconfig'), $client->getConfig('customconfig'));
-    }
-
-    /**
-     * @expectedException        \PredictionIO\UnidentifiedUserException
-     * @expectedExceptionMessage Must call identify() before performing any user-related commands.
-     */
-    public function testGetIdentityThrowsExceptionWhenNotSet()
-    {
-        $client = new PredictionIOClient();
-        $client->getIdentity();
-    }
-
-    public function testSetGetIdentitySuccess()
-    {
-        $mockId = "0";
-        $client = new PredictionIOClient();
-        $client->identify($mockId);
-
-        $this->assertSame($mockId, $client->getIdentity());
-    }
-
-    public function testCreateRequestReturnsRequestObject()
-    {
-        $client = new PredictionIOClient();
-        $request = $client->createRequest();
-
-        $this->assertInstanceOf('\Guzzle\Http\Message\Request', $request);
-    }
-
-    public function testCreateRequestSetMethodSuccess()
-    {
-        $mockMethod = RequestInterface::POST;
-        $client = new PredictionIOClient();
-        $request = $client->createRequest($mockMethod);
-
-        $this->assertSame($mockMethod, $request->getMethod());
-    }
-
-    public function testCreateRequestSetUriSuccess()
-    {
-        $mockUri = 'http://mock.uri/method';
-        $expectedUri = $mockUri.'.json?pio_appkey=';
-        $client = new PredictionIOClient();
-        $request = $client->createRequest(RequestInterface::GET, $mockUri);
-
-        $this->assertSame($expectedUri, $request->getUrl());
-    }
-
-    public function testCreateRequestSetHeadersSuccess()
-    {
-        $headers = array(
-            'mock.header' => 'mock.value',
-        );
-        $client = new PredictionIOClient();
-        $request = $client->createRequest(RequestInterface::GET, null, $headers);
-
-        $this->assertTrue($request->getHeader('mock.header')->hasValue($headers['mock.header']));
-    }
-
-    public function testCreateRequestUnsetsGuzzleInternals()
-    {
-        $body = array(
-            AbstractCommand::HEADERS_OPTION => 'mock.HEADERS_OPTION',
-            AbstractCommand::ON_COMPLETE => 'mock.ON_COMPLETE',
-            AbstractCommand::DISABLE_VALIDATION => 'mock.DISABLE_VALIDATION',
-            AbstractCommand::RESPONSE_PROCESSING => 'mock.RESPONSE_PROCESSING',
-            AbstractCommand::RESPONSE_BODY => 'mock.RESPONSE_BODY',
-        );
-        $client = new PredictionIOClient();
-        $request = $client->createRequest(RequestInterface::GET, null, null, $body);
-
-        $this->assertNull($request->getQuery()->get(AbstractCommand::HEADERS_OPTION));
-        $this->assertNull($request->getQuery()->get(AbstractCommand::ON_COMPLETE));
-        $this->assertNull($request->getQuery()->get(AbstractCommand::DISABLE_VALIDATION));
-        $this->assertNull($request->getQuery()->get(AbstractCommand::RESPONSE_PROCESSING));
-        $this->assertNull($request->getQuery()->get(AbstractCommand::RESPONSE_BODY));
-    }
-}