<?php defined('SYSPATH') or die('No direct access allowed.');

/**
 * TSM database connection.
 *
 * @package    PTA
 * @subpackage TSM
 * @category   Drivers
 * @author     Deon George
 * @copyright  (c) 2010 phpTSMadmin Development Team
 * @license    http://phptsmadmin.sf.net/license.html
 */
class Database_TSM extends Database {

	// Database in use by each connection
	protected static $_current_databases = array();

	// Use SET NAMES to set the character set
	protected static $_set_names;

	// Identifier for this connection within the PHP driver
	protected $_connection_id;

	// TSM does not use a backtick for identifiers
	protected $_identifier = '';

	// Our TSM Message Format
	private $msg_format = '[A-Z]{3}[0-9]{4}[I|E|W|S]';

	// Our Message Codes stored in the last query
	private $_query_msg_codes = array();

	// Our return code error messages we can ignore
	private $ignore_codes = array(
		'ANS8001I', // Return code %s.
	);
	// Our return code error messages we can ignore
	private $nodata_codes = array(
		'ANR2034E', // SELECT: No match found using this criteria.
	);

	public function connect()
	{
		if ($this->_connection)
			return;

		// Extract the connection parameters, adding required variabels
		extract($this->_config['connection'] + array(
			'database'   => '',
			'hostname'   => '',
			'username'   => '',
			'password'   => '',
			'persistent' => FALSE,
		));

		// Get user login details from user session - these are set by login
		if (! $username)
			$username = Session::instance()->get_once('admin_name');
		if (! $password)
			$password = Session::instance()->get_once('password');

		// Prevent this information from showing up in traces
		unset($this->_config['connection']['username'], $this->_config['connection']['password']);

		if (! file_exists(Kohana::config('config.client'))) {
			system_message(array('title'=>'Cant find DSMADMC',
				'body'=>sprintf('Unable to find the dsmadmc at <b>%s</b>',Kohana::config('client.config')),
				'type'=>'error'));

			return FALSE;
		}

		if (! $username OR ! $password)
			Request::instance()->redirect('/login?need_login=1');

		try
		{
			if ($persistent)
			{
				// Create a persistent connection
				throw new Kohana_Exception('Cant do persistant connections');
			}
			else
			{
				// Create a connection and force it to be a new link
				$this->_connection = sprintf('%s -id=%s -password=%s -displ=list -dataonly=YES %s %s',
					Kohana::config('config.client'),
					$username,
					$password,
					Kohana::config('config.client_errorlogname') ? sprintf('-errorlogname=%s',Kohana::config('config.client_errorlogname')) : '',
					Kohana::config('config.client_stanza') ? sprintf('-se=%s',Kohana::config('config.client_stanza')) : ''
				);

				$result = $this->query(Database::SELECT,'SELECT server_name,platform,version,release,level,sublevel FROM status');

//echo Kohana::debug($result);die();
				if ($result)
					return TSM::instance()->set($username,$password,$result);
				else
					Request::instance()->redirect(Request::instance()->uri());
			}
		}
		catch (ErrorException $e)
		{
			// No connection exists
			$this->_connection = NULL;

			throw new Database_Exception(':error', array(
					':error' => sprintf('%s error in %s (%s)',$e->getMessage(),$e->getFile(),$e->getLine()),
				),
				$e->getCode());
		}

		// \xFF is a better delimiter, but the PHP driver uses underscore
		$this->_connection_id = sha1($hostname.'_'.$username.'_'.$password);

		if ( ! empty($this->_config['charset']))
		{
			// Set the character set
			$this->set_charset($this->_config['charset']);
		}
	}

	public function disconnect()
	{
		try
		{
			// Database is assumed disconnected
			$status = TRUE;

			if (is_resource($this->_connection))
			{
				if ($status = mysql_close($this->_connection))
				{
					// Clear the connection
					$this->_connection = NULL;
				}
			}
		}
		catch (Exception $e)
		{
			// Database is probably not disconnected
			$status = ! is_resource($this->_connection);
		}

		return $status;
	}

	public function set_charset($charset)
	{
		// Make sure the database is connected
		$this->_connection or $this->connect();

		// Nothing to do for TSM
		$status = TRUE;

		if ($status === FALSE)
		{
			throw new Database_Exception(':error',
				array(':error' => mysql_error($this->_connection)),
				mysql_errno($this->_connection));
		}
	}

	public function query($type, $sql, $as_object = FALSE, array $params = NULL)
	{
		// Make sure the database is connected
		$this->_connection or $this->connect();

		if ( ! empty($this->_config['profiling']))
		{
			// Benchmark this query for the current instance
			$benchmark = Profiler::start("Database ({$this->_instance})", $sql);
		}

		// Execute the query
		$stderr = exec($this->_connection.'"'.$sql.'"',$stdout,$rc);

		// Work out our message codes
		$result = array();
		foreach ($stdout as $line)
			if (! preg_match('/^('.$this->msg_format.')\s+/',$line,$matches))
				array_push($result,$line);
			elseif (! in_array($matches[1],$this->ignore_codes))
				array_push($this->_query_msg_codes,$matches[1]);

		// If we got a no data code
		if (array_intersect($this->_query_msg_codes,$this->nodata_codes)) {
			$result = array(0);
			$rc = 0;
		}

		if ($stderr AND $rc) {
			if (isset($benchmark))
			{
				// This benchmark is worthless
				Profiler::delete($benchmark);
			}

			SystemMessage::TSM_Error($stdout,$sql);
			return FALSE;
		}

		if (isset($benchmark))
		{
			Profiler::stop($benchmark);
		}

		// Set the last query
		$this->last_query = $sql;

		if ($type === Database::SELECT)
		{
			// Return an iterator of results
			return new Database_TSM_Result($result, $sql, $as_object, $params);
		}
		elseif ($type === Database::INSERT)
		{
			throw new Kohana_Exception('Database INSERTS are not supported');
		}
	}

	public function list_tables($like = NULL)
	{
		if (is_string($like))
		{
			// Search for table names
			$result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE);
		}
		else
		{
			// Find all table names
			$result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE);
		}

		$tables = array();
		foreach ($result as $row)
		{
			$tables[] = reset($row);
		}

		return $tables;
	}

	public function list_columns($table, $like = NULL, $add_prefix = TRUE)
	{
		// Quote the table name
		$table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table;

		if (is_string($like))
		{
			// Search for column names
			$result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE);
		}
		else
		{
			// Find all column names
			$result = $this->query(Database::SELECT, sprintf('SELECT * FROM SYSCAT.COLUMNS WHERE TABNAME=\'%s\'',$table), FALSE);
		}

		$count = 0;
		$columns = array();
		foreach ($result as $row)
		{
			list($type, $length) = $this->_parse_type($row['TYPENAME']);

			$column = $this->datatype($type);

			$column['column_name']      = $row['COLNAME'];
//			$column['column_default']   = $row['Default'];
			$column['data_type']        = $type;
			$column['is_nullable']      = ($row['NULLS'] == 'TRUE');
			$column['ordinal_position'] = ++$count;

			switch (strtolower($column['data_type']))
			{
				case 'float':
					if (isset($length))
					{
						list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length);
					}
				break;
				case 'int':
					if (isset($length))
					{
						// MySQL attribute
						$column['display'] = $length;
					}
				break;
				case 'string':
					switch ($column['data_type'])
					{
						case 'binary':
						case 'varbinary':
							$column['character_maximum_length'] = $length;
						break;
						case 'char':
						case 'varchar':
							$column['character_maximum_length'] = $length;
						case 'text':
						case 'tinytext':
						case 'mediumtext':
						case 'longtext':
							$column['collation_name'] = $row['Collation'];
						break;
						case 'enum':
						case 'set':
							$column['collation_name'] = $row['Collation'];
							$column['options'] = explode('\',\'', substr($length, 1, -1));
						break;
					}
				break;
			}

			// TSM attributes
			$column['comment']      = $row['REMARKS'];

			$columns[$row['COLNAME']] = $column;
		}

		return $columns;
	}

	public function escape($value)
	{
		// Make sure the database is connected
		$this->_connection or $this->connect();

		// SQL standard is to use single-quotes for all values
		return "'$value'";
	}

} // End Database_TSM
