<?php
/**
 * PostgreSQLSchema class
 * Implements the ISQLSchema interface
 * @author Jerôme Brilland
 * @version 2.1
 * @copyright (c) 2019-2023, Jérôme Brilland
 * @license http://www.gnu.org/licenses/gpl.txt GNU General Public License
 */
class PostgreSQLSchema implements ISQLSchema {

    protected $database;
    protected $schema;

    protected $dataTypes = [
        'integer' => 'integer',
        'smallint' => 'smallint',
        'bigint' => 'bigint',
        'decimal' => 'decimal',
        'float' => 'float',
        'double' => 'double precision',
        'char' => 'char',
        'varchar' => 'varchar',
        'text' => 'text',
        'clob' => 'text',
        'boolean' => 'boolean',
        'date' => 'date',
        'time' => 'time',
        'datetime' => 'timestamp',
        'timestamp' => 'timestamp',
        'varbinary' => 'bytea'
    ];

    /**
     * Class constructor
     * @param Medoo $database
     * @param string $schema Schema
     * @access public
     */
    public function __construct( $database, $schema ) {
        $this->database = $database;
        $this->schema = $schema;
    }

    /**
     * @return array
     * @access public
     */
    public function getTables() {
        return $this->database->query( 'select * from information_schema.tables' )->fetchAll();
    }

    /**
     * @param string $tableName
     * @return array
     * @access public
     */
    public function getColumns( $tableName ) {
        return $this->database->query( 'select * from information_schema.columns where table_name = \''. $tableName . '\'' )->fetchAll();
    }

    /**
     * @param string $tableName
     * @return array
     * @access public
     */
    public function getColumnNames( $tableName ) {
        return array_column( $this->getColumns( $tableName ), 'column_name' );
    }

    /**
     * @param string $tableName
     * @param array $columns
     * @param mixed $parameters
     * @access public
     */
    public function createTable( $tableName, $columns, $parameters = '' ) {
        $query = 'CREATE TABLE ' . $this->schema . '.' . $tableName . ' (';
        $query .= join( ', ', array_map( [$this, 'getColumnSQL'], array_keys( $columns ), $columns ) );
        $query .= ')';
        $query .= $parameters;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
        if ( DEVELOPMENT_ENVIRONMENT ) {  
            $logger->write( sprintf( '[%s] [%s] [%s] %s', date( 'Y-m-d H:i:s' ), __FILE__, 'debug', $this->database->last() ) );
        }
    }

    /**
     * @param string $tableName
     * @access public
     */
    public function dropTable( $tableName ) {
        $query = 'DROP TABLE ' . $this->schema . '.' . $tableName;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }

    /**
     * @param string $tableName
     * @access public
     */
    public function dropTableIfexists( $tableName ) {
        $query = 'DROP TABLE IF EXISTS ' . $this->schema . '.' . $tableName;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }

    /**
     * @param string $tableName
     * @param string $columnName
     * @param mixed $options
     * @access public
     */
    public function addColumn( $tableName, $columnName, $datatype, $options = [] ) {
        $query = 'ALTER TABLE ' . $this->schema . '.' . $tableName . ' ADD ' . $this->getColumnSQL( $columnName, [$datatype, $options] );
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
        if ( DEVELOPMENT_ENVIRONMENT ) {  
            $logger->write( sprintf( '[%s] [%s] [%s] %s', date( 'Y-m-d H:i:s' ), __FILE__, 'debug', $this->database->last() ) );
        }
    }

    /**
     * @param string $tableName
     * @param string $columnName
     * @access public
     */
    public function dropColumn( $tableName, $columnName ) {
        $query = 'ALTER TABLE ' . $this->schema . '.' . $tableName . ' DROP COLUMN ' . $columnName;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }
    
    /**
     * @param string $identifier
     * @param string $tableName
     * @param mixed $columnNames
     * @access public
     */
    public function addPrimaryKey( $identifier, $tableName, $columnNames ) {
        $query = 'ALTER TABLE ' . $this->schema . '.' . $tableName . ' ADD CONSTRAINT ' . $identifier . ' PRIMARY KEY (';
        if ( is_string( $columnNames ) ) {
            $query .=  $columnNames;
        } else if ( is_array( $columnNames ) ) {
            $query .= join( ',', $columnNames );
        }
        $query .= ')';
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
        if ( DEVELOPMENT_ENVIRONMENT ) {  
            $logger->write( sprintf( '[%s] [%s] [%s] %s', date( 'Y-m-d H:i:s' ), __FILE__, 'debug', $this->database->last() ) );
        }
    }

    /**
     * @param string $identifier
     * @param string $tableName
     * @access public
     */
    function dropPrimaryKey( $identifier, $tableName ) {
        $query = 'ALTER TABLE ' . $this->schema . '.' . $tableName . ' DROP CONSTRAINT ' . $identifier;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }

    /**
     * @param string $identifier
     * @param string $tableName
     * @param string $columnName
     * @param string $referenceTableName
     * @param string $referenceColumnName
     * @param string $onDelete
     * @param string $onUpdate
     * @access public
     */
    public function addForeignKey( $identifier, $tableName, $columnName, $referenceTableName, $referenceColumnName, $onDelete = null, $onUpdate = null ) {
        $query = 'ALTER TABLE ' . $this->schema . '.' . $tableName . ' ADD CONSTRAINT ' . $identifier . ' FOREIGN KEY (' . $columnName . ') REFERENCES ' . $this->schema . '.' . $referenceTableName . '(' . $referenceColumnName . ')';
        if ( !empty( $onDelete ) ) {
            $query .= ' ON DELETE ' . $onDelete;
        }
        if ( !empty( $onUpdate ) ) {
            $query .= ' ON UPDATE ' . $onUpdate;
        }
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
        if ( DEVELOPMENT_ENVIRONMENT ) {  
            $logger->write( sprintf( '[%s] [%s] [%s] %s', date( 'Y-m-d H:i:s' ), __FILE__, 'debug', $this->database->last() ) );
        }
    }

    /**
     * @param string $identifier
     * @param string $tableName
     * @access public
     */
    public function dropForeignKey( $identifier, $tableName ) {
        $query = 'ALTER TABLE ' . $this->schema . '.' . $tableName . ' DROP CONSTRAINT ' . $identifier;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }

    /**
     * @param string $identifier
     * @param string $tableName
     * @param mixed $columnNames
     * @param boolean $unique
     */
    public function addIndex( $identifier, $tableName, $columnNames, $unique = false ) {
        $query = 'CREATE ';
        if ( $unique ) {
            $query .= 'UNIQUE ';
        }
        $query .= 'INDEX ' . $identifier . ' ON ' . $this->schema . '.' . $tableName . ' (';
        if ( is_string( $columnNames ) ) {
            $query .=  $columnNames;
        } else if ( is_array( $columnNames ) ) {
            $query .= join( ',', $columnNames );
        }
        $query .= ')';
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }

    /**
     * @param string $identifier
     * @param string $tableName
     */
    public function dropIndex( $identifier, $tableName ) {
        $query = 'DROP INDEX ' . $identifier;
        $this->database->query( $query );
        $logger = IoC::resolve( 'logger' );
        $error = $this->database->error;
        if ( $error ) {
            $logger->write( sprintf( '[%s] [%s] [%s] %s',  date( 'Y-m-d H:i:s' ), __FILE__, 'error', $error ) );
        }
    }

    /**
     * @param string $dataType
     * @return string The actual data type
     * @access public
     */
    public function getDataType( $dataType ) {
        if ( array_key_exists( $dataType, $this->dataTypes ) ) {
            return $this->dataTypes[$dataType];
        } else {
            return $dataType;
        }
    }

    /**
     * @param string $columnName
     * @param mixed $columnDefinition
     * @return string
     * @access protected
     */
    protected function getColumnSQL( $columnName, $columnDefinition ) {
        if ( is_string( $columnDefinition ) ) {
            return $columnName . ' ' . $columnDefinition;
        } else if ( is_array( $columnDefinition ) ){
            $dataType = $this->getDataType( $columnDefinition[0] );
            $options = '';
            if ( !empty( $columnDefinition[1] ) && is_array( $columnDefinition[1] ) ) {
                if ( !empty( $columnDefinition[1]['length'] ) ) {
                    $options .= '(' . $columnDefinition[1]['length'] . ')';
                }
                if ( !empty( $columnDefinition[1]['not_null'] ) ) {
                    $options .=' NOT NULL';
                } else {
                    $options .= ' NULL';
                }
                if ( !empty( $columnDefinition[1]['default'] ) ) {
                    $options .= ' DEFAULT ' . $columnDefinition[1]['default'];
                }
                if ( !empty( $columnDefinition[1]['primary_key'] ) ) {
                    $options .= ' PRIMARY KEY';
                }
                if ( !empty( $columnDefinition[1]['identity'] ) && 'integer' == $dataType ) {
                    $dataType = 'serial';
                }
                if ( !empty( $columnDefinition[1]['collation'] ) ) {
                    $options .= ' COLLATION ' . $columnDefinition[1]['collation'];
                }
            }
            return $columnName . ' ' . $dataType . $options;
        }
    }

}
