<?php defined('BASEPATH') OR exit('No direct script access allowed');
/**
 * Migrations
 *
 * An open source utility for CodeIgniter inspired by Ruby on Rails.
 *
 * @package			Migrations
 * @author			Mat�as Montes
 * @modified		Phil Sturgeon
 * @license         http://philsturgeon.co.uk/code/dbad-license
 * @link			http://philsturgeon.co.uk/code/codeigniter-migrations
 *
 *	Credit for lots of the improvements:
 *
 * 	Spicer Matthews <spicer@cloudmanic.com>
 * 	Cloudmanic Labs, LLC
 * 	http://www.cloudmanic.com/
 *
 */

/**
 * @TUTORIAL
 * pentru a instala migration din extended trebuie apelat /migrate/install/0
 * sau /migrate/version/X/0
 */

// ------------------------------------------------------------------------

/**
 * Migration Interface
 *
 * All migrations should implement this, forces up() and down() and gives
 * access to the CI super-global.
 *
 * @package		Migrations
 * @author		Phil Sturgeon
 */
abstract class Migration {

	public abstract function up();

	public abstract function down();

	function __get($var)
	{
		return get_instance()->$var;
	}

}

// ------------------------------------------------------------------------

/**
 * Migrations Class
 *
 * Utility main controller.
 *
 * @package		Migrations
 * @author		Mat�as Montes
 */
class Migrations {

	private $_migrations_enabled = FALSE;
	private $_migrations_path = NULL;
	private $_migrations_version = 0;

	public $verbose = FALSE;
	public $error = '';

	function __construct($config = array())
	{
		if ( ! empty($config))
		{
			foreach ($config as $key => $val)
			{
				$this->{'_' . $key} = $val;
			}
		}

		log_message('debug', 'Migrations class initialized');

		// Are they trying to use migrations while it is disabled?
		$this->_migrations_enabled OR show_error('Migrations has been loaded but is disabled or set up incorrectly.');

		// If not set, set it
		$this->_migrations_path == '' OR $this->_migrations_path = APPPATH . 'migrations/';
		$this->_migrations_extended_path = '' OR $this->_migrations_extended_path = APPEXPATH . 'migrations/'; 

		// Add trailing slash if not set
		if (substr($this->_migrations_path, -1) != '/')
		{
			$this->_migrations_path .= '/';
		}

		$this->_ci =& get_instance();

		// They'll probably be using dbforge
		$this->_ci->load->dbforge();

		// If the schema_version table is missing, make it
		if ( ! $this->_ci->db->table_exists('schema_version'))
		{
			$this->_ci->dbforge->add_field(array(
				'version' => array('type' => 'INT', 'constraint' => 3),
				'sub_version' => array('type' => 'INT', 'constraint' => 3),
			));

			$this->_ci->dbforge->create_table('schema_version', TRUE);

			$this->_ci->db->insert('schema_version', array('version' => 0,'sub_version'=>0));
		}
	}

	// This will set if there should be verbose output or not
	public function set_verbose($state)
	{
		$this->verbose = $state;
	}

	/**
	 * Installs the schema up to the last version
	 *
	 * @access	public
	 * @return	void	Outputs a report of the installation
	 */
	public function install($main = TRUE)
	{
		// Load all *_*.php files in the migrations path
		$migration_path = ($main)?$this->_migrations_path:$this->_migrations_extended_path;
		
		$files = glob($migration_path . '*_*' . EXT);
		$file_count = count($files);

		for ($i = 0; $i < $file_count; $i++)
		{
			// Mark wrongly formatted files as FALSE for later filtering
			$name = basename($files[$i], EXT);
			if ( ! preg_match('/^\d{3}_(\w+)$/', $name))
			{
				$files[$i] = FALSE;
			}
		}

		$migrations = array_filter($files);

		if ( ! empty($migrations))
		{
			sort($migrations);
			$last_migration = basename(end($migrations));

			// Calculate the last migration step from existing migration
			// filenames and procceed to the standard version migration
			$last_version = substr($last_migration, 0, 3);
			return $this->version(intval($last_version, 10),$main);
		}

		else
		{
			$this->error = lang('no_migrations_found');
			return FALSE;
		}
	}

	// --------------------------------------------------------------------

	/**
	 * Migrate to a schema version
	 *
	 * Calls each migration step required to get to the schema version of
	 * choice
	 *
	 * @access	public
	 * @param $version integer	Target schema version
	 * @return	mixed	TRUE if already latest, FALSE if failed, int if upgraded
	 */
	function version($version,$main = TRUE)
	{
		$schema_version = $this->_get_schema_version($main);
		$start = $schema_version;
		$stop = $version;

		if ($version > $schema_version)
		{
			// Moving Up
			++$start;
			++$stop;
			$step = 1;
		}

		else
		{
			// Moving Down
			$step = -1;
		}

		$method = $step === 1 ? 'up' : 'down';
		
		if( $method == 'down'){
			die('Disable me if needed :)');
		}
		
		$migrations = array();

		$migration_path = ($main)?$this->_migrations_path:$this->_migrations_extended_path;
		// We now prepare to actually DO the migrations
		// But first let's make sure that everything is the way it should be
		for ($i = $start; $i != $stop; $i += $step)
		{
			$f = glob(sprintf($migration_path . '%03d_*' . EXT, $i));

			// Only one migration per step is permitted
			if (count($f) > 1)
			{
				$this->error = sprintf(lang('multiple_migrations_version'), $i);
				return FALSE;
			}

			// Migration step not found
			if (count($f) == 0)
			{
				// If trying to migrate up to a version greater than the last
				// existing one, migrate to the last one.
				if ($step == 1) break;

				// If trying to migrate down but we're missing a step,
				// something must definitely be wrong.
				$this->error = sprintf(lang('migration_not_found'), $i);
				return FALSE;
			}

			$file = basename($f[0]);
			$name = basename($f[0], EXT);

			// Filename validations
			if (preg_match('/^\d{3}_(\w+)$/', $name, $match))
			{
				$match[1] = strtolower($match[1]);
				
				// Cannot repeat a migration at different steps
				if (in_array($match[1], $migrations))
				{
					$this->error = sprintf(lang('multiple_migrations_name'), $match[1]);
					return FALSE;
				}

				include $f[0];
				$class = 'Migration_' . ucfirst($match[1]);

				if ( ! class_exists($class))
				{														
					$this->error = sprintf(lang('migration_class_doesnt_exist'), $class);
					
					return FALSE;
				}

				if ( ! is_callable(array($class, 'up')) || !is_callable(array($class, 'down')))
				{
					$this->error = sprintf(lang('wrong_migration_interface'), $class);
					return FALSE;
				}

				$migrations[] = $match[1];
			}
			else
			{
				$this->error = sprintf(lang('invalid_migration_filename'), $file);
				return FALSE;
			}
		}

		$version = $i + ($step == 1 ? -1 : 0);

		// If there is nothing to do, bitch and quit
		if ($migrations === array())
		{
			if ($this->verbose)
			{
				echo 'Nothing to do, bye!'.PHP_EOL;
			}

			return TRUE;
		}

		if ($this->verbose)
		{
			echo '<p>Current schema version: ' . $schema_version . '<br/>';
			echo 'Moving ' . $method . ' to version ' . $version . '</p>';
			echo '<hr/>';
		}

		// Loop through the migrations
		foreach ($migrations AS $migration)
		{
			if ($this->verbose)
			{
				echo $migration.':<br />';
				echo '<blockquote>';
			}

			$class = 'Migration_' . ucfirst($migration);
			call_user_func(array(new $class, $method));

			if ($this->verbose)
			{
				echo '</blockquote>';
				echo '<hr/>';
			}

			$schema_version += $step;
			$this->_update_schema_version($schema_version,$main);
		}

		if ($this->verbose)
		{
			echo '<p>All done. Schema is at version '.$schema_version.'.</p>';
		}

		return $schema_version;
	}

	// --------------------------------------------------------------------

	/**
	 * Set's the schema to the latest migration
	 *
	 * @access	public
	 * @return	mixed	TRUE if already latest, FALSE if failed, int if upgraded
	 */
	public function latest()
	{
		$version = $this->_migrations_version;
		return $this->version($version);
	}

	// --------------------------------------------------------------------

	/**
	 * Retrieves current schema version
	 *
	 * @access	private
	 * @return	integer	Current Schema version
	 */
	private function _get_schema_version($main)
	{
		$row = $this->_ci->db->get('schema_version')->row();
		if( $main ){
			return $row ? $row->version : 0;
		} else {
			return $row ? $row->sub_version : 0;
		}
	}

	// --------------------------------------------------------------------

	/**
	 * Stores the current schema version
	 *
	 * @access	private
	 * @param $schema_version integer	Schema version reached
	 * @return	void					Outputs a report of the migration
	 */
	private function _update_schema_version($schema_version,$main)
	{
		return $this->_ci->db->update('schema_version', array(
			($main)? 'version' : 'sub_version' => $schema_version
		));
	}
}