<?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\Metadata\Source;

use 
Zend\Db\Adapter\Adapter;
use 
Zend\Db\Metadata\MetadataInterface;
use 
Zend\Db\Metadata\Object;

abstract class 
AbstractSource implements MetadataInterface
{
    const 
DEFAULT_SCHEMA '__DEFAULT_SCHEMA__';

    
/**
     *
     * @var Adapter
     */
    
protected $adapter null;

    
/**
     *
     * @var string
     */
    
protected $defaultSchema null;

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

    
/**
     * Constructor
     *
     * @param Adapter $adapter
     */
    
public function __construct(Adapter $adapter)
    {
        
$this->adapter $adapter;
        
$this->defaultSchema = ($adapter->getCurrentSchema()) ?: self::DEFAULT_SCHEMA;
    }

    
/**
     * Get schemas
     *
     */
    
public function getSchemas()
    {
        
$this->loadSchemaData();

        return 
$this->data['schemas'];
    }

    
/**
     * Get table names
     *
     * @param  string $schema
     * @param  bool   $includeViews
     * @return string[]
     */
    
public function getTableNames($schema null$includeViews false)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadTableNameData($schema);

        if (
$includeViews) {
            return 
array_keys($this->data['table_names'][$schema]);
        }

        
$tableNames = array();
        foreach (
$this->data['table_names'][$schema] as $tableName => $data) {
            if (
'BASE TABLE' == $data['table_type']) {
                
$tableNames[] = $tableName;
            }
        }
        return 
$tableNames;

    }

    
/**
     * Get tables
     *
     * @param  string $schema
     * @param  bool   $includeViews
     * @return Object\TableObject[]
     */
    
public function getTables($schema null$includeViews false)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$tables = array();
        foreach (
$this->getTableNames($schema$includeViews) as $tableName) {
            
$tables[] = $this->getTable($tableName$schema);
        }
        return 
$tables;
    }

    
/**
     * Get table
     *
     * @param  string $tableName
     * @param  string $schema
     * @return Object\TableObject
     */
    
public function getTable($tableName$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadTableNameData($schema);

        if (!isset(
$this->data['table_names'][$schema][$tableName])) {
            throw new \
Exception('Table "' $tableName '" does not exist');
        }

        
$data $this->data['table_names'][$schema][$tableName];
        switch (
$data['table_type']) {
            case 
'BASE TABLE':
                
$table = new Object\TableObject($tableName);
                break;
            case 
'VIEW':
                
$table = new Object\ViewObject($tableName);
                
$table->setViewDefinition($data['view_definition']);
                
$table->setCheckOption($data['check_option']);
                
$table->setIsUpdatable($data['is_updatable']);
                break;
            default:
                throw new \
Exception('Table "' $tableName '" is of an unsupported type "' $data['table_type'] . '"');
        }
        
$table->setColumns($this->getColumns($tableName$schema));
        
$table->setConstraints($this->getConstraints($tableName$schema));
        return 
$table;
    }

    
/**
     * Get view names
     *
     * @param string $schema
     * @return array
     */
    
public function getViewNames($schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadTableNameData($schema);

        
$viewNames = array();
        foreach (
$this->data['table_names'][$schema] as $tableName => $data) {
            if (
'VIEW' == $data['table_type']) {
                
$viewNames[] = $tableName;
            }
        }
        return 
$viewNames;
    }

    
/**
     * Get views
     *
     * @param string $schema
     * @return array
     */
    
public function getViews($schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$views = array();
        foreach (
$this->getViewNames($schema) as $tableName) {
            
$views[] = $this->getTable($tableName$schema);
        }
        return 
$views;
    }

    
/**
     * Get view
     *
     * @param string $viewName
     * @param string $schema
     * @return \Zend\Db\Metadata\Object\TableObject
     */
    
public function getView($viewName$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadTableNameData($schema);

        
$tableNames $this->data['table_names'][$schema];
        if (isset(
$tableNames[$viewName]) && 'VIEW' == $tableNames[$viewName]['table_type']) {
            return 
$this->getTable($viewName$schema);
        }
        throw new \
Exception('View "' $viewName '" does not exist');
    }

    
/**
     * Gt column names
     *
     * @param  string $table
     * @param  string $schema
     * @return array
     */
    
public function getColumnNames($table$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadColumnData($table$schema);

        if (!isset(
$this->data['columns'][$schema][$table])) {
            throw new \
Exception('"' $table '" does not exist');
        }

        return 
array_keys($this->data['columns'][$schema][$table]);
    }

    
/**
     * Get columns
     *
     * @param  string $table
     * @param  string $schema
     * @return array
     */
    
public function getColumns($table$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadColumnData($table$schema);

        
$columns = array();
        foreach (
$this->getColumnNames($table$schema) as $columnName) {
            
$columns[] = $this->getColumn($columnName$table$schema);
        }
        return 
$columns;
    }

    
/**
     * Get column
     *
     * @param  string $columnName
     * @param  string $table
     * @param  string $schema
     * @return Object\ColumnObject
     */
    
public function getColumn($columnName$table$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadColumnData($table$schema);

        if (!isset(
$this->data['columns'][$schema][$table][$columnName])) {
            throw new \
Exception('A column by that name was not found.');
        }

        
$info $this->data['columns'][$schema][$table][$columnName];

        
$column = new Object\ColumnObject($columnName$table$schema);
        
$props = array(
            
'ordinal_position''column_default''is_nullable',
            
'data_type''character_maximum_length''character_octet_length',
            
'numeric_precision''numeric_scale''numeric_unsigned',
            
'erratas'
        
);
        foreach (
$props as $prop) {
            if (isset(
$info[$prop])) {
                
$column->{'set' str_replace('_'''$prop)}($info[$prop]);
            }
        }

        
$column->setOrdinalPosition($info['ordinal_position']);
        
$column->setColumnDefault($info['column_default']);
        
$column->setIsNullable($info['is_nullable']);
        
$column->setDataType($info['data_type']);
        
$column->setCharacterMaximumLength($info['character_maximum_length']);
        
$column->setCharacterOctetLength($info['character_octet_length']);
        
$column->setNumericPrecision($info['numeric_precision']);
        
$column->setNumericScale($info['numeric_scale']);
        
$column->setNumericUnsigned($info['numeric_unsigned']);
        
$column->setErratas($info['erratas']);

        return 
$column;
    }

    
/**
     * Get constraints
     *
     * @param  string $table
     * @param  string $schema
     * @return array
     */
    
public function getConstraints($table$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadConstraintData($table$schema);

        
$constraints = array();
        foreach (
array_keys($this->data['constraints'][$schema][$table]) as $constraintName) {
            
$constraints[] = $this->getConstraint($constraintName$table$schema);
        }

        return 
$constraints;
    }

    
/**
     * Get constraint
     *
     * @param  string $constraintName
     * @param  string $table
     * @param  string $schema
     * @return Object\ConstraintObject
     */
    
public function getConstraint($constraintName$table$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadConstraintData($table$schema);

        if (!isset(
$this->data['constraints'][$schema][$table][$constraintName])) {
            throw new \
Exception('Cannot find a constraint by that name in this table');
        }

        
$info $this->data['constraints'][$schema][$table][$constraintName];
        
$constraint = new Object\ConstraintObject($constraintName$table$schema);

        foreach (array(
            
'constraint_type'         => 'setType',
            
'match_option'            => 'setMatchOption',
            
'update_rule'             => 'setUpdateRule',
            
'delete_rule'             => 'setDeleteRule',
            
'columns'                 => 'setColumns',
            
'referenced_table_schema' => 'setReferencedTableSchema',
            
'referenced_table_name'   => 'setReferencedTableName',
            
'referenced_columns'      => 'setReferencedColumns',
            
'check_clause'            => 'setCheckClause',
        ) as 
$key => $setMethod) {
            if (isset(
$info[$key])) {
                
$constraint->{$setMethod}($info[$key]);
            }
        }

        return 
$constraint;
    }

    
/**
     * Get constraint keys
     *
     * @param  string $constraint
     * @param  string $table
     * @param  string $schema
     * @return array
     */
    
public function getConstraintKeys($constraint$table$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadConstraintData($table$schema);

        
// organize references first
        
$references = array();
        foreach (
$this->data['constraint_references'][$schema] as $refKeyInfo) {
            if (
$refKeyInfo['constraint_name'] == $constraint) {
                
$references[$refKeyInfo['constraint_name']] = $refKeyInfo;
            }
        }

        
$keys = array();
        foreach (
$this->data['constraint_keys'][$schema] as $constraintKeyInfo) {
            if (
$constraintKeyInfo['table_name'] == $table && $constraintKeyInfo['constraint_name'] === $constraint) {
                
$keys[] = $key = new Object\ConstraintKeyObject($constraintKeyInfo['column_name']);
                
$key->setOrdinalPosition($constraintKeyInfo['ordinal_position']);
                if (isset(
$references[$constraint])) {
                    
//$key->setReferencedTableSchema($constraintKeyInfo['referenced_table_schema']);
                    
$key->setForeignKeyUpdateRule($references[$constraint]['update_rule']);
                    
$key->setForeignKeyDeleteRule($references[$constraint]['delete_rule']);
                    
//$key->setReferencedTableSchema($references[$constraint]['referenced_table_schema']);
                    
$key->setReferencedTableName($references[$constraint]['referenced_table_name']);
                    
$key->setReferencedColumnName($references[$constraint]['referenced_column_name']);
                }
            }
        }

        return 
$keys;
    }

    
/**
     * Get trigger names
     *
     * @param string $schema
     * @return array
     */
    
public function getTriggerNames($schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadTriggerData($schema);

        return 
array_keys($this->data['triggers'][$schema]);
    }

    
/**
     * Get triggers
     *
     * @param string $schema
     * @return array
     */
    
public function getTriggers($schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$triggers = array();
        foreach (
$this->getTriggerNames($schema) as $triggerName) {
            
$triggers[] = $this->getTrigger($triggerName$schema);
        }
        return 
$triggers;
    }

    
/**
     * Get trigger
     *
     * @param string $triggerName
     * @param string $schema
     * @return Object\TriggerObject
     */
    
public function getTrigger($triggerName$schema null)
    {
        if (
$schema === null) {
            
$schema $this->defaultSchema;
        }

        
$this->loadTriggerData($schema);

        if (!isset(
$this->data['triggers'][$schema][$triggerName])) {
            throw new \
Exception('Trigger "' $triggerName '" does not exist');
        }

        
$info $this->data['triggers'][$schema][$triggerName];

        
$trigger = new Object\TriggerObject();

        
$trigger->setName($triggerName);
        
$trigger->setEventManipulation($info['event_manipulation']);
        
$trigger->setEventObjectCatalog($info['event_object_catalog']);
        
$trigger->setEventObjectSchema($info['event_object_schema']);
        
$trigger->setEventObjectTable($info['event_object_table']);
        
$trigger->setActionOrder($info['action_order']);
        
$trigger->setActionCondition($info['action_condition']);
        
$trigger->setActionStatement($info['action_statement']);
        
$trigger->setActionOrientation($info['action_orientation']);
        
$trigger->setActionTiming($info['action_timing']);
        
$trigger->setActionReferenceOldTable($info['action_reference_old_table']);
        
$trigger->setActionReferenceNewTable($info['action_reference_new_table']);
        
$trigger->setActionReferenceOldRow($info['action_reference_old_row']);
        
$trigger->setActionReferenceNewRow($info['action_reference_new_row']);
        
$trigger->setCreated($info['created']);

        return 
$trigger;
    }

    
/**
     * Prepare data hierarchy
     *
     * @param string $type
     * @param string $key ...
     */
    
protected function prepareDataHierarchy($type)
    {
        
$data = &$this->data;
        foreach (
func_get_args() as $key) {
            if (!isset(
$data[$key])) {
                
$data[$key] = array();
            }
            
$data = &$data[$key];
        }
    }

    protected function 
loadSchemaData()
    {
    }

    protected function 
loadTableNameData($schema)
    {
        if (isset(
$this->data['table_names'][$schema])) {
            return;
        }

        
$this->prepareDataHierarchy('table_names'$schema);
    }

    protected function 
loadColumnData($table$schema)
    {
        if (isset(
$this->data['columns'][$schema][$table])) {
            return;
        }

        
$this->prepareDataHierarchy('columns'$schema$table);
    }

    protected function 
loadConstraintData($table$schema)
    {
        if (isset(
$this->data['constraints'][$schema])) {
            return;
        }

        
$this->prepareDataHierarchy('constraints'$schema);
    }

    protected function 
loadTriggerData($schema)
    {
        if (isset(
$this->data['triggers'][$schema])) {
            return;
        }

        
$this->prepareDataHierarchy('triggers'$schema);
    }
}