<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Db\RowGateway;

use 
ArrayAccess;
use 
Countable;
use 
Zend\Db\Sql\Sql;
use 
Zend\Db\Sql\TableIdentifier;

abstract class 
AbstractRowGateway implements ArrayAccessCountableRowGatewayInterface
{

    
/**
     * @var bool
     */
    
protected $isInitialized false;

    
/**
     * @var string|TableIdentifier
     */
    
protected $table null;

    
/**
     * @var array
     */
    
protected $primaryKeyColumn null;

    
/**
     * @var array
     */
    
protected $primaryKeyData null;

    
/**
     * @var array
     */
    
protected $data = array();

    
/**
     * @var Sql
     */
    
protected $sql null;

    
/**
     * @var Feature\FeatureSet
     */
    
protected $featureSet null;

    
/**
     * initialize()
     */
    
public function initialize()
    {
        if (
$this->isInitialized) {
            return;
        }

        if (!
$this->featureSet instanceof Feature\FeatureSet) {
            
$this->featureSet = new Feature\FeatureSet;
        }

        
$this->featureSet->setRowGateway($this);
        
$this->featureSet->apply('preInitialize', array());

        if (!
is_string($this->table) && !$this->table instanceof TableIdentifier) {
            throw new 
Exception\RuntimeException('This row object does not have a valid table set.');
        }

        if (
$this->primaryKeyColumn == null) {
            throw new 
Exception\RuntimeException('This row object does not have a primary key column set.');
        } elseif (
is_string($this->primaryKeyColumn)) {
            
$this->primaryKeyColumn = (array) $this->primaryKeyColumn;
        }

        if (!
$this->sql instanceof Sql) {
            throw new 
Exception\RuntimeException('This row object does not have a Sql object set.');
        }

        
$this->featureSet->apply('postInitialize', array());

        
$this->isInitialized true;
    }

    
/**
     * Populate Data
     *
     * @param  array $rowData
     * @param  bool  $rowExistsInDatabase
     * @return AbstractRowGateway
     */
    
public function populate(array $rowData$rowExistsInDatabase false)
    {
        
$this->initialize();

        
$this->data $rowData;
        if (
$rowExistsInDatabase == true) {
            
$this->processPrimaryKeyData();
        } else {
            
$this->primaryKeyData null;
        }

        return 
$this;
    }

    
/**
     * @param mixed $array
     * @return array|void
     */
    
public function exchangeArray($array)
    {
        return 
$this->populate($arraytrue);
    }

    
/**
     * Save
     *
     * @return integer
     */
    
public function save()
    {
        
$this->initialize();

        if (
$this->rowExistsInDatabase()) {

            
// UPDATE

            
$data $this->data;
            
$where = array();

            
// primary key is always an array even if its a single column
            
foreach ($this->primaryKeyColumn as $pkColumn) {
                
$where[$pkColumn] = $this->primaryKeyData[$pkColumn];
                if (
$data[$pkColumn] == $this->primaryKeyData[$pkColumn]) {
                    unset(
$data[$pkColumn]);
                }
            }

            
$statement $this->sql->prepareStatementForSqlObject($this->sql->update()->set($data)->where($where));
            
$result $statement->execute();
            
$rowsAffected $result->getAffectedRows();
            unset(
$statement$result); // cleanup

        
} else {

            
// INSERT
            
$insert $this->sql->insert();
            
$insert->values($this->data);

            
$statement $this->sql->prepareStatementForSqlObject($insert);

            
$result $statement->execute();
            if ((
$primaryKeyValue $result->getGeneratedValue()) && count($this->primaryKeyColumn) == 1) {
                
$this->primaryKeyData = array($this->primaryKeyColumn[0] => $primaryKeyValue);
            } else {
                
// make primary key data available so that $where can be complete
                
$this->processPrimaryKeyData();
            }
            
$rowsAffected $result->getAffectedRows();
            unset(
$statement$result); // cleanup

            
$where = array();
            
// primary key is always an array even if its a single column
            
foreach ($this->primaryKeyColumn as $pkColumn) {
                
$where[$pkColumn] = $this->primaryKeyData[$pkColumn];
            }

        }

        
// refresh data
        
$statement $this->sql->prepareStatementForSqlObject($this->sql->select()->where($where));
        
$result $statement->execute();
        
$rowData $result->current();
        unset(
$statement$result); // cleanup

        // make sure data and original data are in sync after save
        
$this->populate($rowDatatrue);

        
// return rows affected
        
return $rowsAffected;
    }

    
/**
     * Delete
     *
     * @return int
     */
    
public function delete()
    {
        
$this->initialize();

        
$where = array();
        
// primary key is always an array even if its a single column
        
foreach ($this->primaryKeyColumn as $pkColumn) {
            
$where[$pkColumn] = $this->primaryKeyData[$pkColumn];
        }

        
// @todo determine if we need to do a select to ensure 1 row will be affected

        
$statement $this->sql->prepareStatementForSqlObject($this->sql->delete()->where($where));
        
$result $statement->execute();

        
$affectedRows $result->getAffectedRows();
        if (
$affectedRows == 1) {
            
// detach from database
            
$this->primaryKeyData null;
        }

        return 
$affectedRows;
    }

    
/**
     * Offset Exists
     *
     * @param  string $offset
     * @return bool
     */
    
public function offsetExists($offset)
    {
        return 
array_key_exists($offset$this->data);
    }

    
/**
     * Offset get
     *
     * @param  string $offset
     * @return mixed
     */
    
public function offsetGet($offset)
    {
        return 
$this->data[$offset];
    }

    
/**
     * Offset set
     *
     * @param  string $offset
     * @param  mixed $value
     * @return RowGateway
     */
    
public function offsetSet($offset$value)
    {
        
$this->data[$offset] = $value;
        return 
$this;
    }

    
/**
     * Offset unset
     *
     * @param  string $offset
     * @return AbstractRowGateway
     */
    
public function offsetUnset($offset)
    {
        
$this->data[$offset] = null;
        return 
$this;
    }

    
/**
     * @return int
     */
    
public function count()
    {
        return 
count($this->data);
    }

    
/**
     * To array
     *
     * @return array
     */
    
public function toArray()
    {
        return 
$this->data;
    }

    
/**
     * __get
     *
     * @param  string $name
     * @return mixed
     */
    
public function __get($name)
    {
        if (
array_key_exists($name$this->data)) {
            return 
$this->data[$name];
        } else {
            throw new \
InvalidArgumentException('Not a valid column in this row: ' $name);
        }
    }

    
/**
     * __set
     *
     * @param  string $name
     * @param  mixed $value
     * @return void
     */
    
public function __set($name$value)
    {
        
$this->offsetSet($name$value);
    }

    
/**
     * __isset
     *
     * @param  string $name
     * @return bool
     */
    
public function __isset($name)
    {
        return 
$this->offsetExists($name);
    }

    
/**
     * __unset
     *
     * @param  string $name
     * @return void
     */
    
public function __unset($name)
    {
        
$this->offsetUnset($name);
    }

    
/**
     * @return bool
     */
    
public function rowExistsInDatabase()
    {
        return (
$this->primaryKeyData !== null);
    }

    
/**
     * @throws Exception\RuntimeException
     */
    
protected function processPrimaryKeyData()
    {
        
$this->primaryKeyData = array();
        foreach (
$this->primaryKeyColumn as $column) {
            if (!isset(
$this->data[$column])) {
                throw new 
Exception\RuntimeException('While processing primary key data, a known key ' $column ' was not found in the data array');
            }
            
$this->primaryKeyData[$column] = $this->data[$column];
        }
    }
}