<?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.
	);

	// Our required abstract methods
	public function begin($mode = NULL) {}
	public function commit() {}
	public function rollback() {}
	public function set_charset($charset) {}

	/**
	 * Return the caching defined in the current configuration.
	 *
	 *     $cache_time = $db->caching("table");
	 *
	 * @return  string
	 */
	public function caching($table) {
		return ($this->_config['caching'] AND isset($this->_config['cache'][$table])) ? $this->_config['cache'][$table] : FALSE;
	}

	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(Kohana::config('auth.session_key'));
		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::current()->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 %s -id=%s -password=%s -displ=list -editor=no -dataonly=YES %s %s ',
					Kohana::config('config.client'),
					$database ? '-server='.$database : '',
					$username,
					$password,
					Kohana::config('config.client_errorlogname') ? sprintf('-errorlogname=%s',Kohana::config('config.client_errorlogname')) : '',
					$database ? sprintf('-se=%s',$database) : ''
				);

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

				if ($result)
					return TSM::instance()->set($username,$password,$result);
				else
					Request::current()->redirect(Request::detect_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']);
		}
	}

	private function clear() {
		$this->_query_msg_codes = array();
	}

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

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

		// We need to escape any back slashes, since the exec will transpose them
		// @todo Is there a better way of doing this?
		$sql = str_replace('\\','\\\\',$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();
			$rc = 0;
		}

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

			SystemMessage::TSM_Error($stdout,$sql);
			Request::current()->redirect('welcome');
		}

		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['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