<?php
namespace Spot\Entity;
use Spot;

/**
 * Entity Manager for storing information about entities
 *
 * @package Spot
 * @link http://spot.os.ly
 */
class Manager
{
    // Field and relation info
    protected static $_properties = array();
    protected static $_fields = array();
    protected static $_fieldsDefined = array();
    protected static $_fieldDefaultValues = array();
    protected static $_relations = array();
    protected static $_primaryKeyField = array();
    
    // Connection and datasource info
    protected static $_connection = array();
    protected static $_datasource = array();
    protected static $_datasourceOptions = array();
    
    
    /**
     * Get formatted fields with all neccesary array keys and values.
     * Merges defaults with defined field values to ensure all options exist for each field.
     *
     * @param string $entityName Name of the entity class
     * @return array Defined fields plus all defaults for full array of all possible options
     */
    public function fields($entityName)
    {
        if(!is_string($entityName)) {
            throw new \Spot\Exception(__METHOD__ . " only accepts a string. Given (" . gettype($entityName) . ")");
        }
        
        if(!is_subclass_of($entityName, '\Spot\Entity')) {
            throw new \Spot\Exception($entityName . " must be subclass of '\Spot\Entity'.");
        }
        
        if(isset(self::$_fields[$entityName])) {
            $returnFields = self::$_fields[$entityName];
        } else {
            // Datasource info
            $entityDatasource = null;
            $entityDatasource = $entityName::datasource();
            if(null === $entityDatasource || !is_string($entityDatasource)) {
                echo "\n\n" . $entityName . "::datasource() = " . var_export($entityName::datasource(), true) . "\n\n";
                throw new \InvalidArgumentException("Entity must have a datasource defined. Please define a protected property named '_datasource' on your '" . $entityName . "' entity class.");
            }
            self::$_datasource[$entityName] = $entityDatasource;

            // Datasource Options
            $entityDatasourceOptions = $entityName::datasourceOptions();
            self::$_datasourceOptions[$entityName] = $entityDatasourceOptions;
            
            // Connection info
            $entityConnection = $entityName::connection();
            // If no adapter specified, Spot will use default one from config object (or first one set if default is not explicitly set)
            self::$_connection[$entityName] = ($entityConnection) ? $entityConnection : false;
            
            // Default settings for all fields
            $fieldDefaults = array(
                'type' => 'string',
                'default' => null,
                'length' => null,
                'required' => false,
                'null' => true,
                'unsigned' => false,
                'fulltext' => false,
    
                'primary' => false,
                'index' => false,
                'unique' => false,
                'serial' => false,
    
                'relation' => false
                );
    
            // Type default overrides for specific field types
            $fieldTypeDefaults = array(
                'string' => array(
                    'length' => 255
                    ),
                'float' => array(
                    'length' => array(10,2)
                    ),
                'int' => array(
                    'length' => 10,
                    'unsigned' => true
                    )
                );
    
            // Get entity fields from entity class
            $entityFields = false;
            $entityFields = $entityName::fields();
            if(!is_array($entityFields) || count($entityFields) < 1) {
                throw new \InvalidArgumentException($entityName . " Must have at least one field defined.");
            }
                
            $returnFields = array();
            self::$_fieldDefaultValues[$entityName] = array();
            foreach($entityFields as $fieldName => $fieldOpts) {
                // Store field definition exactly how it is defined before modifying it below
                if($fieldOpts['type'] != 'relation') {
                    self::$_fieldsDefined[$entityName][$fieldName] = $fieldOpts;
                }
                
                // Format field will full set of default options
                if(isset($fieldOpts['type']) && isset($fieldTypeDefaults[$fieldOpts['type']])) {
                    // Include type defaults
                    $fieldOpts = array_merge($fieldDefaults, $fieldTypeDefaults[$fieldOpts['type']], $fieldOpts);
                } else {
                    // Merge with defaults
                    $fieldOpts = array_merge($fieldDefaults, $fieldOpts);
                }
    
                // Store primary key
                if(true === $fieldOpts['primary']) {
                    self::$_primaryKeyField[$entityName] = $fieldName;
                }
                // Store default value
                if(null !== $fieldOpts['default']) {
                    self::$_fieldDefaultValues[$entityName][$fieldName] = $fieldOpts['default'];
                } else {
                    self::$_fieldDefaultValues[$entityName][$fieldName] = null;
                }
    
                $returnFields[$fieldName] = $fieldOpts;
            }
            self::$_fields[$entityName] = $returnFields;
            
            // Relations
            $entityRelations = array();
            $entityRelations = $entityName::relations();
            if(!is_array($entityRelations)) {
                throw new \InvalidArgumentException($entityName . " Relation definitons must be formatted as an array.");
            }
            foreach($entityRelations as $relationAlias => $relationOpts) {
                self::$_relations[$entityName][$relationAlias] = $relationOpts;
            }
        }
        return $returnFields;
    }
    
    
    /**
     * Get field information exactly how it is defined in the class
     *
     * @param string $entityName Name of the entity class
     * @return array Array of field key => value pairs
     */
    public function fieldsDefined($entityName)
    {
        if(!isset(self::$_fieldsDefined[$entityName])) {
            $this->fields($entityName);
        }
        return self::$_fieldsDefined[$entityName];
    }
    
    
    /**
     * Get field default values as defined in class field definitons
     *
     * @param string $entityName Name of the entity class
     * @return array Array of field key => value pairs
     */
    public function fieldDefaultValues($entityName)
    {
        if(!isset(self::$_fieldDefaultValues[$entityName])) {
            $this->fields($entityName);
        }
        return self::$_fieldDefaultValues[$entityName];
    }
    
    
    /**
     * Get defined relations
     *
     * @param string $entityName Name of the entity class
     */
    public function relations($entityName)
    {
        $this->fields($entityName);
        if(!isset(self::$_relations[$entityName])) {
            return array();
        }
        return self::$_relations[$entityName];
    }
    
    
    /**
     * Get value of primary key for given row result
     *
     * @param string $entityName Name of the entity class
     */
    public function primaryKeyField($entityName)
    {
        if(!isset(self::$_primaryKeyField[$entityName])) {
            $this->fields($entityName);
        }
        return self::$_primaryKeyField[$entityName];
    }
    
    
    /**
     * Check if field exists in defined fields
     *
     * @param string $entityName Name of the entity class
     * @param string $field Field name to check for existence
     */
    public function fieldExists($entityName, $field)
    {
        return array_key_exists($field, $this->fields($entityName));
    }
    
    
    /**
     * Return field type
     *
     * @param string $entityName Name of the entity class
     * @param string $field Field name
     * @return mixed Field type string or boolean false
     */
    public function fieldType($entityName, $field)
    {
        $fields = $this->fields($entityName);
        return $this->fieldExists($entityName, $field) ? $fields[$field]['type'] : false;
    }
    
    
    /**
     * Get defined connection to use for entity
     *
     * @param string $entityName Name of the entity class
     */
    public function connection($entityName)
    {
        $this->fields($entityName);
        if(!isset(self::$_connection[$entityName])) {
            return false;
        }
        return self::$_connection[$entityName];
    }
    
    
    /**
     * Get name of datasource for given entity class
     *
     * @param string $entityName Name of the entity class
     * @return string
     */
    public function datasource($entityName)
    {
        if(!isset(self::$_datasource[$entityName])) {
            $this->fields($entityName);
        }
        return self::$_datasource[$entityName];
    }


    /**
     * Get datasource options for given entity class
     *
     * @param array Options to pass
     * @return string
     */
    public function datasourceOptions($entityName)
    {
        if(!isset(self::$_datasourceOptions[$entityName])) {
            $this->fields($entityName);
        }
        return self::$_datasourceOptions[$entityName];
    }
}