/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program 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.
 *
 * This program 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
 */

#include "jobs_menu.h"

#include <stddef.h> /* NULL */
#include <stdio.h> /* snprintf() */
#include <string.h> /* strlen() strdup() */

#include "../compat/reallocarray.h"
#include "../modes/dialogs/msg_dialog.h"
#include "../ui/ui.h"
#include "../utils/str.h"
#include "../utils/string_array.h"
#include "../background.h"
#include "menus.h"

static int execute_jobs_cb(view_t *view, menu_data_t *m);
static KHandlerResponse jobs_khandler(view_t *view, menu_data_t *m,
		const wchar_t keys[]);
static int cancel_job(menu_data_t *m, bg_job_t *job);
static void reload_jobs_list(menu_data_t *m);
static char * format_job_item(bg_job_t *job);
static void show_job_errors(view_t *view, menu_data_t *m, bg_job_t *job);
static KHandlerResponse errs_khandler(view_t *view, menu_data_t *m,
		const wchar_t keys[]);

/* Menu jobs description. */
static menu_data_t jobs_m;

int
show_jobs_menu(view_t *view)
{
	menus_init_data(&jobs_m, view, strdup("Pid --- Command"),
			strdup("No jobs currently running"));
	jobs_m.execute_handler = &execute_jobs_cb;
	jobs_m.key_handler = &jobs_khandler;

	reload_jobs_list(&jobs_m);

	return menus_enter(&jobs_m, view);
}

/* Callback that is called when menu item is selected.  Should return non-zero
 * to stay in menu mode. */
static int
execute_jobs_cb(view_t *view, menu_data_t *m)
{
	/* TODO: write code for job control. */
	return 0;
}

/* Menu-specific shortcut handler.  Returns code that specifies both taken
 * actions and what should be done next. */
static KHandlerResponse
jobs_khandler(view_t *view, menu_data_t *m, const wchar_t keys[])
{
	if(wcscmp(keys, L"dd") == 0)
	{
		if(!cancel_job(m, m->void_data[m->pos]))
		{
			show_error_msg("Job cancellation", "The job has already stopped");
			return KHR_REFRESH_WINDOW;
		}

		menus_partial_redraw(m->state);
		return KHR_REFRESH_WINDOW;
	}
	else if(wcscmp(keys, L"e") == 0)
	{
		show_job_errors(view, m, m->void_data[m->pos]);
		return KHR_REFRESH_WINDOW;
	}
	else if(wcscmp(keys, L"r") == 0)
	{
		reload_jobs_list(m);
		menus_set_pos(m->state, m->pos);
		menus_partial_redraw(m->state);
		return KHR_REFRESH_WINDOW;
	}
	/* TODO: maybe use DD for forced termination? */
	return KHR_UNHANDLED;
}

/* Cancels the job if it's still running.  Returns non-zero if operation was
 * cancelled, otherwise it's already finished and zero is returned. */
static int
cancel_job(menu_data_t *m, bg_job_t *job)
{
	bg_job_t *p;

	/* We have to make sure the job pointer is still valid and the job is
	 * running. */
	for(p = bg_jobs; p != NULL; p = p->next)
	{
		if(p == job && bg_job_is_running(job))
		{
			if(bg_job_cancel(job))
			{
				put_string(&m->items[m->pos], format_job_item(job));
			}
			break;
		}
	}

	return (p != NULL);
}

/* (Re)loads list of jobs into the menu. */
static void
reload_jobs_list(menu_data_t *m)
{
	free(m->void_data);
	free_string_array(m->items, m->len);

	m->void_data = NULL;
	m->items = NULL;
	m->len = 0;

	bg_check(/*show_errors=*/0);

	int len = 0;
	bg_job_t *p;
	for(p = bg_jobs; p != NULL; p = p->next)
	{
		if(!bg_job_is_running(p))
		{
			continue;
		}

		char *item = format_job_item(p);
		if(item == NULL)
		{
			continue;
		}

		int new_i = put_into_string_array(&m->items, len, item);
		if(new_i != len + 1)
		{
			free(item);
			continue;
		}

		void **new_data = reallocarray(m->void_data, new_i, sizeof(*m->void_data));
		if(new_data == NULL)
		{
			free(item);
			continue;
		}

		m->void_data = new_data;
		m->void_data[len] = p;

		++len;
	}

	m->len = len;
}

/* Formats single menu line that describes state of the job.  Returns formatted
 * string or NULL on error. */
static char *
format_job_item(bg_job_t *job)
{
	char info_buf[24];
	if(job->type == BJT_COMMAND)
	{
		snprintf(info_buf, sizeof(info_buf), "%" PRINTF_ULL,
				(unsigned long long)job->pid);
	}
	else if(job->bg_op.total == BG_UNDEFINED_TOTAL)
	{
		snprintf(info_buf, sizeof(info_buf), "n/a");
	}
	else
	{
		snprintf(info_buf, sizeof(info_buf), "%d/%d", job->bg_op.done + 1,
				job->bg_op.total);
	}

	const char *cancelled = (bg_job_cancelled(job) ? "(cancelling...) " : "");
	return format_str("%-8s  %s%s", info_buf, cancelled, job->cmd);
}

/* Shows job errors if there is something and the job is still running.
 * Switches to separate menu description. */
static void
show_job_errors(view_t *view, menu_data_t *m, bg_job_t *job)
{
	/* Make sure the job pointer is still valid. */
	bg_job_t *p = bg_jobs;
	while(p != NULL && p != job)
	{
		p = p->next;
	}

	if(p == NULL)
	{
		show_error_msg("Job errors", "The job has already finished");
		return;
	}

	char *cmd = NULL, *errors = NULL;
	size_t errors_len = 0U;

	if(pthread_spin_lock(&job->errors_lock) == 0)
	{
		cmd = strdup(job->cmd);
		errors = (job->errors == NULL ? NULL : strdup(job->errors));
		errors_len = job->errors_len;
		(void)pthread_spin_unlock(&job->errors_lock);
	}

	if(is_null_or_empty(errors))
	{
		show_error_msg("Job errors", "No errors to show");
	}
	else
	{
		static menu_data_t m;

		menus_init_data(&m, view, format_str("Job errors (%s)", cmd), NULL);
		m.key_handler = &errs_khandler;
		m.items = break_into_lines(errors, errors_len, &m.len, 0);

		menus_rotate(&m, &jobs_m);
	}

	free(cmd);
	free(errors);
}

/* Menu-specific shortcut handler.  Returns code that specifies both taken
 * actions and what should be done next. */
static KHandlerResponse
errs_khandler(view_t *view, menu_data_t *m, const wchar_t keys[])
{
	if(wcscmp(keys, L"h") == 0)
	{
		menus_switch_to(&jobs_m);
		return KHR_REFRESH_WINDOW;
	}
	return KHR_UNHANDLED;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
