<?php
/**
 * Copyright (C) 2013 Vitaliy Pylypiv <vitaliy.pylypiv@gmail.com>
 *
 * This file is part of FusionForge.
 *
 * FusionForge is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * FusionForge is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

define('RELEASE_OF_TASK', 1);
define('RELEASE_OF_USER_STORY', 2);

require_once $gfcommon.'include/FFError.class.php';
require_once $gfplugins.'taskboard/common/TaskBoardColumn.class.php';
require_once $gfplugins.'taskboard/common/TaskBoardRelease.class.php';

/**
 * Factory method which creates a taskboard from a taskboard ID
 *
 * @param	int	The taskboard ID
 * @param	array	The result array, if it's passed in
 * @return	object	TaskBoard object
 */
function &taskboard_get_object($taskboard_id, $data = false) {
	static $trackers = array();

	if( !array_key_exists($taskboard_id, $trackers)) {
		$res = db_query_params('SELECT * FROM plugin_taskboard WHERE taskboard_id=$1', array($taskboard_id));
		if (db_numrows($res) <1) {
			return false;
		}
		$data = db_fetch_array($res);

		$Group = group_get_object($data['group_id']);
		$trackers[$taskboard_id] = new TaskBoard($Group, $data);
	}
	return $trackers[$taskboard_id];
}

/**
 * Initialize a task board
 */
function &taskboard_init($group_id) {
	$res = db_query_params('INSERT INTO plugin_taskboard(group_id) VALUES($1)', array($group_id));
	if (!$res) {
		return false;
	}

	$Group = group_get_object($data['group_id']);

	$Taskboard = new TaskBoard($Group, $data);
	return $Taskboard;
}


class TaskBoard extends FFError {
	/**
	 * The Group object.
	 *
	 * @var	object	$Group.
	 */
	var $Group;

	/**
	 * Trackers adapter object
	 *
	 * @var	object
	 */
	var $TrackersAdapter;

	/**
	  * Array of artifact data.
	 *
	 * @var	array	$data_array.
	 */
	var $data_array;

	function TaskBoard($Group, $arr = false) {
		parent::__construct();
		if (!$Group || !is_object($Group)) {
			$this->setError('No Valid Group Object');
			return false;
		}
		if ($Group->isError()) {
			$this->setError('TaskBoard'._(': ').$Group->getErrorMessage());
			return false;
		}

		$this->Group = $Group;
		if (!$arr || !is_array($arr)) {
			if (!$this->fetchDataByGroup()) {
				return false;
			}
		} else {
			$this->data_array =& $arr;
			if ($this->data_array['group_id'] != $this->Group->getID()) {
				$this->setError('Group_id in db result does not match Group Object');
				$this->data_array = null;
				return false;
			}
		}

		$plugins_taskboard_trackers_adapter_class = forge_get_config('trackers_adapter_class', 'taskboard');
		if( !isset($plugins_taskboard_trackers_adapter_class) ) {
			$plugins_taskboard_trackers_adapter_class  = 'TaskBoardBasicAdapter';
		}
		global $gfplugins;
		$plugins_taskboard_trackers_adapter_module = $gfplugins.'taskboard/common/adapters/' . $plugins_taskboard_trackers_adapter_class . '.class.php';

		require_once( $plugins_taskboard_trackers_adapter_module );
		$this->TrackersAdapter = new $plugins_taskboard_trackers_adapter_class( $this );
	}


	/**
	 * create - create a row in the taskboards table
	 *
	 * @param	array	list of trackers IDs, linked to the taskboard
	 * @param	array	has of card background colors (key - tracker id, value - bg color)
	 * @param	string	Alias for of 'select' extra field used for release/sprint
	 * @param	string	Tracke type of extra field used for release/sprint (1 - task trackers, 2 - user story tracker)
	 * @param	string	Used for cost calculations together with remaining_cost_field_alias if specified
	 * @param	string	Used for cost calculations together with estimated_cost_field_alias if specified
	 *
	 * @return     true on success / false on failure.
	 */
	function create($trackers, $bgcolors, $release_field_alias = NULL, $release_field_tracker = 1,
			$estimated_cost_field_alias = NULL, $remaining_cost_field_alias = NULL,
			$user_stories_tracker = NULL, $user_stories_reference_field = NULL,
			$user_stories_sort_field = NULL, $first_column_by_default = 1) {
		//
		//      data validation
		//
		if (!session_loggedin()) {
			$this->setError(_('Must Be Logged In'));
			return false;
		}

		if (count($trackers) == 0) {
			$this->setError(_('Taskboard must be linked at least to one tracker'));
			return false;
		}

		$ret = true;
		db_begin();
		$res = db_query_params(
				'INSERT INTO plugin_taskboard(group_id, release_field_alias,  release_field_tracker, estimated_cost_field_alias,
					remaining_cost_field_alias, user_stories_group_artifact_id, user_stories_reference_field_alias,user_stories_sort_field_alias,
				first_column_by_default) VALUES($1, $2, $3, $4, $5, $6, $7, $8)',
				array( $release_field_alias, $release_field_tracker, $estimated_cost_field_alias,
					$remaining_cost_field_alias, ( $user_stories_tracker ? $user_stories_tracker: NULL ),
					$user_stories_reference_field, $user_stories_sort_field, $first_column_by_default)
			);
		if (!$res) {
			$this->setError(_('Cannot create taskboard'));
			$ret = false;
 		} else {
			$this->data_array['taskboard_id'] = db_insertid($res,'plugin_taskboard','taskboard_id');
		}

		if( $ret ) {
			foreach( $trackers as $tracker_id ) {
				$ret = $this->addUsedTracker( $tracker_id, ( array_key_exists($tracker_id, $bgcolors) ? $bgcolors[$tracker_id]  : NULL) );
			}
		}

		// TODO columns initialization

		if($ret) {
			db_commit();
		} else {
			db_rollback();
			$this->data_array['taskboard_id'] = NULL;
		}

		return $ret;
	}

	/**
	 * update - update a row in the taskboards table
	 *
	 * @param	array	list of trackers IDs, linked to the taskboard
	 * @param	array	has of card background colors (key - tracker id, value - bg color)
	 * @param	string	Alias for of 'select' extra field used for release/sprint
	 * @param	string	Tracke type of extra field used for release/sprint (1 - task trackers, 2 - user story tracker)
	 * @param	string	Used for cost calculations together with remaining_cost_field_alias if specified
	 * @param	string	Used for cost calculations together with estimated_cost_field_alias if specified
	 *
	 * @return	true on success / false on failure.
	 */
	function update($trackers, $bgcolors, $release_field_alias = NULL,  $release_field_tracker = 1,
			$estimated_cost_field_alias = NULL, $remaining_cost_field_alias = NULL,
			$user_stories_tracker = NULL, $user_stories_reference_field = NULL, $user_stories_sort_field = NULL, $first_column_by_default = 1 ) {
		//
		//      data validation
		//
		if (!session_loggedin()) {
			$this->setError(_('Must Be Logged In'));
			return false;
		}

		if( count($trackers) == 0 ) {
			$this->setError(_('Taskboard must be linked at least to one tracker'));
			return false;
		}

		$ret = true;
		db_begin();
		$res = db_query_params(
				'UPDATE plugin_taskboard SET release_field_alias=$1, release_field_tracker=$2, estimated_cost_field_alias=$3, remaining_cost_field_alias=$4,
				user_stories_group_artifact_id=$5, user_stories_reference_field_alias=$6, user_stories_sort_field_alias=$7,
				first_column_by_default=$8 WHERE taskboard_id=$9',
				array(
					$release_field_alias, $release_field_tracker , $estimated_cost_field_alias, $remaining_cost_field_alias,
					( $user_stories_tracker ? $user_stories_tracker: NULL), $user_stories_reference_field, $user_stories_sort_field,
					$first_column_by_default, $this->getID())
			);
		if (!$res) {
			$this->setError(_('Cannot update taskboard'));
			$ret = false;
		} else {
			$this->fetchData();
		}

		// update trackers
		if($ret) {
			$old_trackers = $this->getUsedTrackersIds();
			foreach($trackers as $tracker_id) {
				if(in_array($tracker_id, $old_trackers)) {
					// update tracker
					$ret = $this->updateUsedTracker(
						$tracker_id,
						(array_key_exists($tracker_id, $bgcolors) ? $bgcolors[$tracker_id] : NULL)
					);
				} else {
					// add tracker
					$ret = $this->addUsedTracker(
						$tracker_id,
						(array_key_exists($tracker_id, $bgcolors) ? $bgcolors[$tracker_id] : NULL)
					);
				}
			}

			foreach($old_trackers as $tracker_id){
				if(!in_array($tracker_id, $trackers)) {
					$ret = $this->deleteUsedTracker($tracker_id);
				}
			}
		}

		if($ret) {
			db_commit();
		} else {
			db_rollback();
			$this->data_array['taskboard_id'] = NULL;
		}

		return $ret;
	}


	/**
	 * _checkExtraFields() - check where extra field exists in the tracker
	 *
	 */
	private function _checkExtraFields($group_artifacts, $alias) {
		$ret = true;

		/* TODO How to get ArtifactType objects ?
		foreach($group_artifacts as $group_artifacts) {
			getExtraFields
		}
		*/

		return $ret;
	}

	/**
	 * fetchData - re-fetch the data for this TaskBoard from the database.
	 *
	 * @param	int	The taskboard ID.
	 * @return	boolean	success.
	 */
	function fetchData($taskboard_id = NULL) {
		if(!$taskboard_id) {
			$taskboard_id = $this->getID();
		}
		$res = db_query_params('SELECT * FROM plugin_taskboard WHERE taskboard_id=$1', array($taskboard_id));
		if (!$res || db_numrows($res) < 1) {
			$this->setError(_('TaskBoard')._(': ')._('Invalid TaskBoardID'));
			return false;
		}
		$this->data_array = db_fetch_array($res);
		db_free_result($res);
		return true;
	}

	/**
	 * fetchDataByGroup - re-fetch the data for this TaskBoard from the database by group ID.
	 *
	 * @return	boolean	success.
	 */
	function fetchDataByGroup() {
		$res = db_query_params('SELECT * FROM plugin_taskboard WHERE group_id=$1', array ($this->Group->getID()));
		if (!$res || db_numrows($res) < 1) {
			$this->setError(_('TaskBoard')._(': ')._('Not configured for this project yet. Please, initialize plugin on the plugin admin page.'));
			return false;
		}
		$this->data_array = db_fetch_array($res);
		db_free_result($res);
		return true;
	}

	/**
	 * getID - get this TaskBoardID.
	 *
	 * @return	int	The taskboard_id
	 */
	function getID() {
		return $this->data_array['taskboard_id'];
	}

	/**
	 * getReleaseField - get alias of field, used for release/sprint
	 *
	 * @return	string	extra field alias
	 */
	function getReleaseField() {
		return $this->data_array['release_field_alias'];
	}

	/**
	 * getReleaseFieldTracker - get a source tracker type of field, used for release/sprint
	 *
	 * @return	integer	1 - tasks tracker, 2 - user story tracker
	 */
	function getReleaseFieldTracker() {
		return $this->data_array['release_field_tracker'];
	}

	/**
	 * getEstimatedCostField - get alias of field, used for estimated cost value
	 *
	 * @return	string	extra field alias
	 */
	function getEstimatedCostField() {
		return $this->data_array['estimated_cost_field_alias'];
	}

	/**
	 * getRemainingCostField - get alias of field, used for remaining cost value
	 *
	 * @return	string	extra field alias
	 */
	function getRemainingCostField() {
		return $this->data_array['remaining_cost_field_alias'];
	}

	/**
	 * getUserStoriesTrackerID - get identifier of tracker, used for user stories
	 *
	 * @return	integer	tracker identifier
	 */
	function getUserStoriesTrackerID() {
		return $this->data_array['user_stories_group_artifact_id'];
	}

	/**
	 * getUserStoriesReferenceField - get alias of field, used as a reference to user stiry artifact
	 *
	 * @return	string	extra field alias
	 */
	function getUserStoriesReferenceField() {
		return $this->data_array['user_stories_reference_field_alias'];
	}

	/**
	 * getUserStoriesSortField - get alias of field, used as for user stories sorting (DESC)
	 *
	 * @return	string	extra field alias
	 */
	function getUserStoriesSortField() {
		return $this->data_array['user_stories_sort_field_alias'];
	}

	/**
	 * getFirstColumnByDefault
	 *
	 * @return	int
	 */
	function getFirstColumnByDefault() {
		return $this->data_array['first_column_by_default'];
	}


	/**
	 * getUsedTrackersIds - get identifiers of used trackers
	 *
	 * @return	array
	 */
	function getUsedTrackersIds() {
		$res = db_query_params('SELECT * FROM plugin_taskboard_trackers WHERE taskboard_id = $1', array ($this->getID()));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot get list of used trackers.'));
			return false;
		}

		$trackers = array();
		while($row =  db_fetch_array($res)) {
			$trackers[] = $row['group_artifact_id'];
		}
		db_free_result($res);
		return $trackers;
	}

	/**
	 * getUsedTrackersiData - get data of used trackers
	 *
	 * @return	array
	 */
	function getUsedTrackersData() {
		$res = db_query_params('SELECT * FROM plugin_taskboard_trackers WHERE taskboard_id = $1', array ($this->getID()));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot get list of used trackers.'));
			return false;
		}

		$trackers = array();
		while( $row =  db_fetch_array($res)) {
			$trackers[] = $row;
		}
		db_free_result($res);
		return $trackers;
	}

	/**
	 * cleanUsedTrackers - empty list of trackers, used with taskboard
	 *
	 * @return	bool
	 */
	function cleanUsedTrackers() {
		$res = db_query_params('DELETE FROM plugin_taskboard_trackers WHERE taskboard_id = $1', array ($this->getID()));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot empty list of used trackers.'));
				 return false;
		}

		return true;
	}

	/**
	 * addUsedTracker - add a tracker to use with taskboard
	 *
	 * @param	int	tracker identifier
	 * @param	string	optional card background color
	 *
	 * @return	bool
	 */
	function addUsedTracker($tracker_id, $bgcolor='') {
		$res = db_query_params('INSERT INTO plugin_taskboard_trackers(taskboard_id, group_artifact_id, card_background_color) VALUES($1, $2, $3)', array($this->getID(), $tracker_id, $bgcolor));
		 if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot add used tracker'));
			return false;
		}

		return true;
	}

	/**
	 * updateUsedTracker - update used tracker
	 *
	 * @param	int	tracker identifier
	 * @param	string	optional card background color
	 *
	 * @return bool
	 */
	function updateUsedTracker($tracker_id, $bgcolor = '') {
		 $res = db_query_params('UPDATE plugin_taskboard_trackers SET card_background_color = $1 WHERE group_artifact_id = $2', array($bgcolor, $tracker_id));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot update used tracker'));
			return false;
		}

		return true;
	}

	/**
	 * deleteUsedTracker - delete used tracker
	 *
	 * @param	int	tracker identifier
	 *
	 * @return	bool
	 */
	function deleteUsedTracker($tracker_id) {
		$res = db_query_params('DELETE FROM plugin_taskboard_trackers WHERE group_artifact_id = $1', array($tracker_id));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot delete used tracker'));
			return false;
		}

		return true;
	}

	/**
	 * getUserStories - get taskboard user stories
	 *
	 * @return	array
	 */
	function getUserStories($release = NULL, $assigned_to = NULL) {
		$stories=array(
			'0' => array(
				'id' => 0,
				'title' => _('Unlinked tasks'),
				'description' => _('Tasks, which are not linked to any user story'),
				'tasks' => array(),
				'url' => '#'
			)
		);

		$user_stories_sort_field = $this->getUserStoriesSortField();
		$user_stories_sort_extra_field_id = NULL;
		if ($user_stories_sort_field) {
			$ef =  $this->TrackersAdapter->getFieldsIds($this->getUserStoriesTrackerID());
			if ( array_key_exists($user_stories_sort_field ,$ef) ) {
				$user_stories_sort_extra_field_id = $ef[ $user_stories_sort_field ];
			} else {
				$this->setError(_('Configured extra field for userstories sorting is not found.'));
				return false;
			}
		}

		$task_release = NULL;
		$user_story_release = NULL;

		if ($this->getReleaseFieldTracker() == RELEASE_OF_TASK) {
			$task_release = $release;
		} else {
			$user_story_release = $release;
		}

		$us = $this->TrackersAdapter->getUserStories($release);

		foreach($us as $story) {
			$stories[$story->getID()] = array(
				'id' => $story->getID(),
				'title' => $story->getSummary(),
				'description' => str_replace("\n", '<br>', $story->getDetails()),
				'priority' => $story->getPriority(),
				'tasks' => array(),
				'url' => $this->TrackersAdapter->getTaskUrl($story)
			);

			if ($user_stories_sort_extra_field_id) {
				$efd = $story ->getExtraFieldData();
				$stories[$story->getID()]['order'] = $efd[$user_stories_sort_extra_field_id];
			} else {
				//sort by GF priority if another field for sorting is not defined
				$stories[$story->getID()]['order'] = $stories[$story->getID()]['priority'];
			}

			if($this->getReleaseFieldTracker() == RELEASE_OF_USER_STORY) {
				$tasks_trackers = $this->getUsedTrackersData();
				foreach($tasks_trackers as $tasks_tracker_data) {
					$tasks = $this->TrackersAdapter->getTasks($tasks_tracker_data['group_artifact_id'], $assigned_to, NULL, $story->getID());
					foreach($tasks as $task) {
						$task_maped = $this->getMappedTask($task);
						$stories[$story->getID()]['tasks'][] = $task_maped;
					}
				}
			}
		}

		if ($this->getReleaseFieldTracker() == RELEASE_OF_TASK) {
			$tasks_trackers = $this->getUsedTrackersData();
			foreach($tasks_trackers as $tasks_tracker_data) {
				$tasks = $this->TrackersAdapter->getTasks($tasks_tracker_data['group_artifact_id'], $assigned_to, $task_release);
				foreach($tasks as $task) {
					$task_maped = $this->getMappedTask($task);
					$stories[intval($task_maped['user_story'])]['tasks'][] = $task_maped;
				}
			}
		}

		$but = array_values($stories);

		//leave only stories, having not empty tasks list
		$ret_stories = array();
		foreach($but as $us) {
			if (count($us['tasks']) > 0 ) {
				$ret_stories[] = $us;
			}
		}

		usort($ret_stories, array($this, 'sortUserStories'));

		return $ret_stories;
	}

	/**
	 * getMappedTask - map artifact object into hash and add column and presentation specific fields
	 *
	 * @param	Artifact	artifact instance
	 *
	 * @return	array
	 */
	function getMappedTask($task) {
		static $_used_trackers_data = NULL;
		static $_first_column_id = NULL;

		if (!$_used_trackers_data) {
			foreach($this->getUsedTrackersData() as $tasks_tracker_data) {
				$_used_trackers_data[ $tasks_tracker_data['group_artifact_id'] ] = $tasks_tracker_data;
			}
		}

		$task_maped = $this->_mapTask($task);
		$column = taskboard_column_get_object_by_resolution($this, $task_maped['resolution']);
		if ($column) {
			 $task_maped['phase_id'] = $column->getID();
		} else {
			if($this->getFirstColumnByDefault()) {
				if(!$_first_column_id) {
					$columns = $this->getColumns();
					$_first_column_id = $columns[0]->getID();
				}
				$task_maped['phase_id'] = $_first_column_id;
			}
		}

		$card_title_background = $_used_trackers_data[$task->ArtifactType->getID()]['card_background_color'];
		if( method_exists($this->TrackersAdapter, 'cardBackgroundColor' ) ) {
			$task_maped['background'] = $this->TrackersAdapter->cardBackgroundColor($task, $card_title_background );
		} else {
			$task_maped['background'] = $card_title_background;
		}

		return $task_maped;
	}

	function sortUserStories($u1, $u2) {
		$ret = 0;

		usort( $u1['tasks'], array( $this, 'sortUserStoryTasks' ) );

		if (!array_key_exists('order', $u1)) {
			$ret = -1;
		} elseif (!array_key_exists('order', $u2)) {
			$ret = 1;
		} elseif ($u1['order'] < $u2['order']) {
			$ret = 1;
		} elseif ($u1['order'] > $u2['order']) {
			$ret = -1;
		}

		return $ret;
	}

	function sortUserStoryTasks($t1, $t2) {
		$ret = 0;

		if ($t1['priority'] < $t2['priority'] ) {
			$ret = 1;
		} elseif ( $t1['priority'] > $t2['priority'] ) {
			$ret = -1;
		}

		return $ret;
	}

	function getMandatoryFieldsMapping() {
		return array(
			'resolution' => 'resolution',
			'estimated_dev_effort' => $this->getEstimatedCostField(),
			'remaining_dev_effort' => $this->getRemainingCostField(),
			'user_story' => $this->getUserStoriesReferenceField()
		);
	}

	/**
	 * _mapTask - map artifact object into hash
	 *
	 * @param	Artifact	artifact instance
	 *
	 * @return	array
	 */
	private function _mapTask($task) {
		$ret = array();

		$ef_mapping = $this->getMandatoryFieldsMapping();

		$fields_ids = $this->TrackersAdapter->getFieldsIds($task->ArtifactType->getID());
		$extra_data = $task->getExtraFieldDataText();

		$ret['id'] = $task->getID();
		$ret['title'] = $task->getSummary();
		$ret['description'] = str_replace("\n", '<br>', $task->getDetails() );
		$ret['assigned_to'] = $task->getAssignedRealName();
		$ret['priority'] = $task->getPriority();
		foreach( $ef_mapping as $k => $f){
			$ret[$k] = '';
			if (array_key_exists($f, $fields_ids)) {
				if (array_key_exists($fields_ids[$f], $extra_data)) {
					$ret[$k] = $extra_data[$fields_ids[$f]]['value'];
				}
			}
		}

		if (!$ret['user_story']) {
			// task is not assigend to any user story
			$ret['user_story'] = 0;
		}

		$ret['url'] = $this->TrackersAdapter->getTaskUrl($task);

		return $ret;
	}

	/**
	 * getColumns - get taskboard columns
	 *
	 * @return	array
	 */
	function getColumns() {
		$res = db_query_params('SELECT * FROM plugin_taskboard_columns WHERE taskboard_id=$1 ORDER BY order_num', array ($this->getID()));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot get list of columns.'));
			return false;
		}

		$columns = array();
		while($row =  db_fetch_array($res)) {
			$columns[] = new TaskBoardColumn($this, $row);
		}
		db_free_result($res);
		return $columns;
	}

	/**
	 * addColumn - add taskboard column
	 *
	 * @return	boolean
	 */
	function addColumn($title, $title_bg_color, $column_bg_color, $max_tasks) {
		$column_id = false;

		$res = db_query_params('SELECT COUNT(*) as count FROM plugin_taskboard_columns WHERE taskboard_id=$1', array ($this->getID()));
		if (!$res) {
			return false;
		}

		$row =  db_fetch_array($res);
		$order = intval($row['count']) + 1;
		db_free_result($res);

		$res = db_query_params(
			'INSERT INTO plugin_taskboard_columns(taskboard_id, title, title_background_color, column_background_color, max_tasks, order_num) VALUES($1,$2,$3,$4,$5,$6)',
			array (
				$this->getID(),
				$title,
				$title_bg_color,
				$column_bg_color,
				intval($max_tasks),
				$order
			)
		) ;
		if (!$res) {
			return false;
		}
		$column_id = db_insertid($res, 'plugin_taskboard_columns', 'taskboard_column_id');
		db_free_result($res);

		return $column_id;
	}

	/**
	 * getExtraFields - get a list of extra fields, existing is all used task trackers
	 *
	 * @param	array	list of allowed extra fields types
	 * @param	array	list of excluded aliases
	 *
	 * @return	array	array of aliases
	 */
	function getExtraFields($allowed_types = array(), $used_trackers = NULL, $excluded_aliases = array('resolution')) {
		$atf = $this->TrackersAdapter->getArtifactTypeFactory();
		if (!$atf || !is_object($atf) || $atf->isError()) {
			return _('Could Not Get ArtifactTypeFactory');
		}

		if (!$used_trackers) {
			$used_trackers = $this->getUsedTrackersIds();
		}

		$common_fields = array();

		foreach ($allowed_types as $allowed_type) {
			$common_fields[$allowed_type] = array();
		}

		$at_arr = $atf->getArtifactTypes();
		$init = true;
		for ($j = 0; $j < count($at_arr); $j++) {
			if (!is_object($at_arr[$j])) {
				//just skip it
			} elseif ($at_arr[$j]->isError()) {
				return $at_arr[$j]->getErrorMessage();
			} else {
				$tracker_id = $at_arr[$j]->getID();

				if (in_array($tracker_id, $used_trackers)) {
					// select common fields for the give types
					$fields = $at_arr[$j]->getExtraFields($allowed_types);
					$tmp = array();
					foreach ($allowed_types as $allowed_type) {
						$tmp[$allowed_type] = array();
					}

					foreach ($fields as $field) {
						// exclude 'resolution' field
						if (!in_array($field['alias'],$excluded_aliases)) {
							if($init) {
								if (in_array($field['field_type'], $allowed_types)) {
									$tmp[$field['field_type']][$field['alias']] = $field['field_name'];
								}
							} elseif (
									in_array($field['alias'], array_keys($common_fields[$field['field_type']])) &&
									in_array($field['field_type'], $allowed_types)
							) {
								$tmp[$field['field_type']][$field['alias']] = $field['field_name'];
							}
						}
					}
					$common_fields = $tmp;
					$init = false;
				}
			}
		}
		return $common_fields;
	}

	/**
	 * getExtraFieldValues - get hash of values, available for the given extra field
	 *
	 * @param	string	extra field alias
	 *
	 * @return	array	hash element_name => element_id
	 */
	function getExtraFieldValues($extra_field_alias) {
		$ret = array();

		$tasks_trackers = $this->getUsedTrackersIds();
		foreach($tasks_trackers as $tracker_id) {
			$ef_values = $this->TrackersAdapter->getExtraFieldValues($tracker_id, $extra_field_alias);
			if (count($ret) == 0) {
				$ret = $ef_values;
			} else {
				$buf = array();
				foreach($ret as $name => $id) {
					if( array_key_exists($name, $ef_values)) {
						$buf[$name] = $id;
					}
				}
				$ret = $buf;
			}
		}

		return $ret;
	}

	/**
	 * getReleases - get taskboard releases
	 *
	 * @return	array
	 */
	function getReleases() {
		$res = db_query_params('SELECT * FROM plugin_taskboard_releases WHERE taskboard_id = $1 ORDER BY start_date, end_date', array($this->getID()));
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot get list of releases.'));
			return false;
		}

		$releases = array();
		while( $row =  db_fetch_array($res)) {
			$releases[] = new TaskBoardRelease($this, $row);
		}
		db_free_result($res);
		return $releases;
	}

	/**
	 * getCurrentRelease - get current or the most recent release object
	 *
	 * @return	object
	 */
	function getCurrentRelease() {
		$current_release = NULL;

		$res = db_query_params (
			'SELECT * FROM plugin_taskboard_releases WHERE taskboard_id=$1 AND start_date < $2 ORDER BY start_date DESC LIMIT 1',
				array(
					$this->getID(),
					strtotime(date('Y-m-d'))
				)
			);
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot get current release.'));
			return false;
		} else {
			$row =  db_fetch_array($res);
			if ($row) {
				$current_release = new TaskBoardRelease($this, $row);
			}
		}
		db_free_result($res);

		return $current_release;
	}

	/**
	 * getReleaseValues - get list of all available reslease values
	 *
	 * @return	array
	 */
	function getReleaseValues() {
		$ret = array();

		if ($this->getReleaseFieldTracker() == 1) {
			// get values from tasks trackers
			$ret = $this->getExtraFieldValues($this->getReleaseField());
		} else {
			// get values from user stories trackers
			$ret = $this->TrackersAdapter->getExtraFieldValues($this->getUserStoriesTrackerID(), $this->getReleaseField());
		}

		return $ret;
	}

	/**
	 * getAvailableResolutions - get list of all available resolution values
	 *
	 * @return	array
	 */
	function getAvailableResolutions() {
		return array_keys($this->getExtraFieldValues('resolution'));
	}

	/**
	 * getUsedResolutions - get list of resolutions that are already used with taskboard columns
	 *
	 * @return	array
	 */
	function getUsedResolutions() {
		$res = db_query_params(
				'SELECT R.* FROM plugin_taskboard_columns_resolutions as R, plugin_taskboard_columns as C
				WHERE C.taskboard_id=$1 AND R.taskboard_column_id=C.taskboard_column_id',
				array($this->getID())
			);
		if (!$res) {
			$this->setError(_('TaskBoard')._(': ')._('Cannot get used resolutions'));
			return false;
		}

		$resolutions= array();
		while($row = db_fetch_array($res)) {
			$resolutions[$row['taskboard_column_resolution']] = $row['taskboard_column_resolution'];
		}
		db_free_result($res);

		return array_keys($resolutions);
	}

	/**
	 * getUnusedResolutions - get list of resolutions that are not used with taskboard columns
	 *
	 * @return	array
	 */
	function getUnusedResolutions() {
		static $resolutions = NULL;
		
		if( !$resolutions ) {
			$all_resolutions = $this->getAvailableResolutions();
			$used_resolutions = $this->getUsedResolutions();
	
			$resolutions = array();
			if( $all_resolutions && count( $used_resolutions ) > 0 ) {
				$resolutions = array_diff( $all_resolutions, $used_resolutions );
			} else {
				$resolutions = $all_resolutions;
			}
		}


		return $resolutions;
	}
}
