<?php

namespace Velis\Model\DataObject;

use LogicException;
use Velis\Arrays;
use Velis\Model\DataObject;

/**
 * Database structure description functionality
 * @author Olek Procki <olo@velis.pl>
 */
trait DbStructureTrait
{
    /**
     * Primary key definitions for DataObject derived classes
     * @var array
     */
    private static $_primaryKeyDefs = array();


    /**
     * Returns list datasource (table name by default)
     * @return string
     */
    protected function _getListDatasource()
    {
        return $this->_getTableName();
    }


    /**
     * Returns details datasource (list datasource by default)
     * @return string
     */
    protected function _getDetailsDatasource()
    {
        return $this->_getListDatasource();
    }


    /**
     * Returns primary key field name for object
     * @return string|string[]
     */
    protected function _getPrimaryKeyField()
    {
        $tableName = $this->_getTableName();

        return self::$_primaryKeyDefs[$tableName] ?? self::primaryKeyName($tableName);
    }


    /**
     * Returns primary key field name for table
     * @param string $tableName
     * @return string
     */
    public static function primaryKeyName($tableName)
    {
        if (isset(self::$_primaryKeyDefs[$tableName])) {
            return self::$_primaryKeyDefs[$tableName];
        } else {
            $tableDefKey = $tableName;

            if (strpos($tableName, '.')) {
                // remove schema name
                $tableName = substr($tableName, strpos($tableName, '.') + 1);
            }

            // remove '_tab' postfix
            return self::$_primaryKeyDefs[$tableDefKey] = str_replace('_tab', '', $tableName) . '_id';
        }
    }


    /**
     * Returns assoc array containing value of primary key
     * @return array
     */
    protected function _getPrimaryKeyParam()
    {
        if (is_array($this->_getPrimaryKeyField())) {
            return $this->id();
        } else {
            return array($this->_getPrimaryKeyField() => $this->id());
        }
    }


    /**
     * Returns primary key sequence name (for PG SERIAL field type)
     * @return string
     * @throws LogicException
     */
    protected function _getPrimaryKeySeq()
    {
        if (!is_array($this->_getPrimaryKeyField())) {
            return $this->_getTableName() . '_' . $this->_getPrimaryKeyField() . '_seq';
        } else {
            throw new LogicException('Multiple field PK cannot have sequence!');
        }
    }


    /**
     * Returns sequence name by database query
     * @return string
     */
    protected function _findPrimaryKeySeq()
    {
        return static::$_db->getOne(
            'SELECT pg_catalog.pg_get_serial_sequence(:table, :field)',
            array(
                'table' => $this->_getTableName(),
                'field' => $this->_getPrimaryKeyField()
            )
        );
    }


    /**
     * Returns true if $field is present in related table
     *
     * @param string $field
     * @param bool $useListDatasource
     *
     * @return bool
     */
    protected function _hasField($field, $useListDatasource = false)
    {
        return in_array($field, $this->_getObjectFields($useListDatasource));
    }


    /**
     * Returns true if $field is present in related table
     *
     * @param string $field
     * @param bool $useListDatasource
     *
     * @return bool
     */
    public static function hasField($field, $useListDatasource = false)
    {
        $instance = new static();
        return $instance->_hasField($field, $useListDatasource);
    }


    /**
     * Should return list of fields for data sanitization or nothing, if class is not sanitizable
     *
     * @param bool $useListDatasource
     * @return array
     */
    protected function _getObjectFields($useListDatasource = false)
    {
        if ($useListDatasource) {
            return array_keys(static::$_db->getColumns($this->_getListDatasource()) ?: []);
        } else {
            return array_keys(static::$_db->getColumns($this->_getTableName()) ?: []);
        }
    }


    /**
     * Returns database table fields for model
     * @return array
     */
    public static function getObjectFields()
    {
        $instance = new static();
        return $instance->_getObjectFields();
    }


    /**
     * Returns new is from sequence
     * @return int
     */
    protected function _getNextId()
    {
        return static::$_db->nextval($this->_getPrimaryKeySeq());
    }


    /**
     * Sets new ID form sequence
     * @return DataObject
     */
    protected function _setNextId()
    {
        return $this->_setId($this->_getNextId());
    }

    private function addPrimaryKeyFields(array $fields): array
    {
        $primaryKey = $this->_getPrimaryKeyField();
        $primaryKeyFields = Arrays::toArray($primaryKey);

        foreach ($primaryKeyFields as $primaryKeyField) {
            if (!in_array($primaryKeyField, $fields)) {
                $fields[] = $primaryKeyField;
            }
        }

        return $fields;
    }
}
