/***********************************************
 *  listlogic.c                                 
 *                                            
 *  Copyright 2001 Eric Kratzer.              
 *  Includes Xaudio software Copyright (C)
 *           1996-2000 Xaudio Corporation
 *           All Rights Reserved.          
 *
 *  This file is part of imp3sh.
 *                                            
 *  imp3sh is free software; you can distribute
 *  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.
 *
 *  imp3sh 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 recieved a copy of the GNU
 *  General Public License along with imp3sh; if
 *  not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place, Suite 330, Boston MA
 *  02111-1307 USA
 ************************************************/

/************************************
 *          Include Files           *
 ************************************/

#include "imp3sh.h"

/************************************
 *       External References        *
 ************************************/

extern XA_PositionInfo pinfo;

extern ProcessState state;

extern DCharBuf *args;

extern ListDef *history, *active_list;

extern PlayerDef *player_list;

extern char *c_path;

extern flag_t show_time_stamp, inside_queue, key_press, external_player;
extern flag_t auto_report, user_stream, external_paused, ignore_stream_weight;

/************************************
 *        Local variables           *
 ************************************/

static char tmpstr_ll[COMM_SIZE];

/************************************
 *        Playlist logic            *
 ************************************/

int in_active_state()
{
    switch (state) {
    case PLAY:
    case PAUSED:
	return TRUE;
    case EXTERNAL:
	if (external_player)
	    return TRUE;
    default:
	break;
    }

    return FALSE;
}

int in_paused_state()
{
    switch (state) {
    case PAUSED:
	return TRUE;
    case EXTERNAL:
	if (external_paused)
	    return TRUE;
    default:
	break;
    }

    return FALSE;
}

int in_idle_state()
{
    switch (state) {
    case IDLE:
	return TRUE;
    case EXTERNAL:
	if (!external_player)
	    return TRUE;
    default:
	break;
    }

    return FALSE;
}

int need_key_update()
{
    if (key_press && (!show_time_stamp || state == EXTERNAL))
	return TRUE;
    else
	return FALSE;
}

int verify_stream(char *stream)
{
    struct stat sstat;

    if (!stream)
	return FALSE;

    if (is_url(stream))
	return TRUE;

    if (stat(stream, &sstat)) {
	add_console(ERROR_MSG " Error reading file \"%s\" : %s",
		    stream, strerror(errno));
	return FALSE;
    } else if (sstat.st_mode & S_IFDIR) {
	add_console(ERROR_MSG " \"%s\" is a directory", stream);
	return FALSE;
    }

    return TRUE;
}

PlayerDef *check_format(char *name)
{
    char tmptype[INFO_SIZE];
    PlayerDef *curr;
    int len, slen;
    char *cmem, *cm;

    curr = player_list;
    len = get_strlen(name);
    cmem = cm = NULL;

    if (!len)
	return NULL;
	
    while (curr) {
	safe_strcpy(tmptype, curr->sup_types, INFO_SIZE);
	cm = tokenize(tmptype, &cmem, COMMA);

	while (cm) {
	    slen = get_strlen(cm);
	    
	    if (len >= slen && strcasecmp(cm, &name[len - slen]) == 0)
		return curr;

	    cm = tokenize(NULL, &cmem, COMMA);
	}
	curr = curr->next;
    }

    return NULL;
}

int play_file(char *stream)
{
    PlayerDef *plyr;

    if (!verify_stream(stream))
	return FALSE;

    if (!(plyr = check_format(stream))) {
	show_user(ERROR_MSG " No suitable player defined for: \"%s\"",
		  stream);
	return FALSE;
    }

    if (external_player) {
	if (external_paused)
	    resume_external();
	stop_external();
    }

    clear_info();
    report_name(stream);
    post_event(SSTREAM_EVENT);

    if (plyr == player_list)
	play_internal(stream);
    else
	play_external(plyr, stream);

    return TRUE;
}

void play_queue(int pos)
{
    if (!history->dcb->len)
	return;

    inside_queue = TRUE;
    user_stream = FALSE;

    update_active_env(history);
    play_file(get_stream(history->dcb, pos, STR_FILE));
}

void play_lindex(ListDef * lst, int queue)
{
    DCharBuf *dcb;

    if (!lst)
	return;

    dcb = lst->dcb;
    inside_queue = FALSE;
    user_stream = FALSE;

    update_active_env(lst);
    if (play_file(get_stream(dcb, dcb->pos, STR_FILE)) && queue)
	add_queue(g_dbuf(dcb, dcb->pos));
}

void /* user */ play_comm()
{
    char *stream;

    stream = g_dbuf(args, 1);

    if (stream) {
	if (*stream != '/' && !is_url(stream)) {
	    sprintf(tmpstr_ll, "%s/%s", c_path, stream);
	    stream = tmpstr_ll;
	}

	if (play_file(stream)) {
	    add_queue(stream);
	    user_stream = TRUE;
	}
    } else if (in_paused_state())
	resume_file();
    else if (in_idle_state()) {
	if (in_queue() || inside_queue)
	    play_queue(history->dcb->pos);
	else if (active_list && active_list->dcb->len) {
	    if (history->dcb->len)
		play_lindex(active_list, FALSE);
	    else
		play_lindex(active_list, TRUE);
	}

	if (!show_time_stamp && key_press && auto_report)
	    update_prompt(FALSE);
    }
}

void /* user */ resume_file()
{
    if (state == EXTERNAL && external_player)
	resume_external();
    else
	resume_internal();
}

void /* user */ stop_file()
{
    if (external_player)
	stop_external();
    else
	stop_internal();

    ignore_stream_weight = FALSE;
    clear_time();

    if (key_press)
	update_prompt(TRUE);
}

void /* user */ pause_file()
{
    if (external_player)
	pause_external();
    else
	pause_internal();
}

void seek_file_static()
{
    char *tok1, *tok2;
    int offset, scale;

    tok1 = g_dbuf(args, 1);
    tok2 = g_dbuf(args, 2);

    if (!(tok1 && tok2 && is_all_digits(tok1) && is_all_digits(tok2))) {
	print_cmnd_help(SEEK_FILE);
	return;
    }

    offset = atoi(tok1);
    scale = atoi(tok2);

    if (offset > scale)
	show_user(ERROR_MSG " Scale must be larger than offset");
    else if (external_player)
	show_user(DEBUG_MSG
		  " Seeking unavailable when running external player");
    else
	seek_internal(offset, scale);
}

void seek_file_offset()
{
    char *tok;
    int inc;
    float jump;

    tok = g_dbuf(args, 1);

    if (!tok) {
	print_cmnd_help(SEEK_FILE);
	return;
    }

    if (*tok == '+')
	inc = atoi((tok + 1));
    else
	inc = atoi(tok);

    if (inc > 100 || inc < -100) {
	show_user(ERROR_MSG" Invalid offset: %d. Valid range is [-100, 100]",
		  inc);
	return;
    }

    if (external_player)
	show_user(DEBUG_MSG
		  " Seeking unavailable when running external player");
    else if (pinfo.range) {
	jump = (((float) inc) / 100.0) * pinfo.range;
	inc = ((int) jump) + pinfo.offset;

	if (inc < 0)
	    inc = 0;
	else if (inc > pinfo.range)
	    inc = pinfo.range;

	seek_internal(inc, pinfo.range);
    }
}

void /* user */ seek_file()
{
    if (args->len == 2)
	seek_file_offset();
    else if (args->len == 3)
	seek_file_static();
    else
	print_cmnd_help(SEEK_FILE);
}

int get_list_weight(ListDef * lst)
{
    if (lst && lst->dcb->len)
	return (lst->global_weight / lst->dcb->len);
    else
	return 0;
}

int get_final_learned_index(DCBNode * buf, int len, int weight)
{
    int i;

    for (i = 0; i < len; i++)
	if (buf[i].info >= weight)
	    return i;

    return -1;
}

int get_final_random_index(DCBNode * buf, int len)
{
    int i;

    for (i = 0; i < len; i++)
	if (buf[i].info == 0)
	    return i;

    return -1;
}

int get_random_index(int len)
{
    return (int) (((float) len) * ((float) random() / (float) RAND_MAX));
}

int get_learned(ListDef * lst)
{
    DCharBuf *dcb;
    DCBNode *buf;
    int len, weight, ndx, loops, tries;

    dcb = lst->dcb;
    buf = dcb->buf;
    len = dcb->len;
    weight = get_list_weight(lst);
    ndx = get_random_index(len);
    loops = tries = 0;

    while (buf[ndx].info < weight) {
	ndx = get_random_index(len);

	if (++tries > len) {
	    ndx = get_final_learned_index(buf, len, weight);
	    tries = 0;
	    loops++;

	    if (ndx == -1) {
		if (loops > 1)
		    clear_played(lst);
		else
		    clear_good_played(lst, weight);

		ndx = get_random_index(len);
	    }
	}
    }

    return ndx;
}

int get_smart_random(ListDef * lst)
{
    DCharBuf *dcb;
    DCBNode *buf;
    int len, ndx, loops, tries;

    dcb = lst->dcb;
    buf = dcb->buf;
    len = dcb->len;
    ndx = get_random_index(len);
    loops = tries = 0;

    while (buf[ndx].info) {
	ndx = get_random_index(len);

	if (++tries > len) {
	    ndx = get_final_random_index(buf, len);
	    tries = 0;
	    loops++;

	    if (ndx == -1) {
		if (loops > 1)
		    clear_played(lst);
		else
		    clear_good_played(lst, 1);

		post_event(LIST_EVENT);
		ndx = get_random_index(len);
	    }
	}
    }

    return ndx;
}

int get_next()
{
    int len, next, pmode;

    len = active_list->dcb->len;
    next = active_list->dcb->pos;
    pmode = active_list->playmode;

    switch (pmode) {
    case LEARN_MODE:
	next = get_learned(active_list);
	break;
    case SRANDOM_MODE:
	next = get_smart_random(active_list);
	break;
    case TRANDOM_MODE:
	next = get_random_index(len);
	break;
    default:
	if (next < (len - 1))
	    next++;
	else {
	    post_event(LIST_EVENT);
	    if (pmode == REPEAT_MODE)
		next = 0;
	    else
		next = -1;
	}
	break;
    }

    return next;
}

void /* user */ next_file()
{
    int next, ique;

    next = 0;
    ique = FALSE;

    if (in_active_state()) {
	if (in_queue()) {
	    play_queue(++history->dcb->pos);

	    if (need_key_update())
		update_prompt(TRUE);

	    return;
	}

	if (inside_queue) {
	    ique = TRUE;
	    inside_queue = FALSE;
	    post_event(QUEUE_EVENT);
	}

	if (active_list && active_list->dcb->len) {
	    if (!user_stream && !ique)
		remove_stream_weight(active_list);

	    if (-1 == (next = get_next()))
		return;

	    active_list->dcb->pos = next;
	    play_lindex(active_list, TRUE);

	    if (need_key_update())
		update_prompt(TRUE);
	}

    } else if (in_idle_state() && in_queue()) {
	inside_queue = TRUE;
	history->dcb->pos++;

	if (key_press)
	    update_prompt(TRUE);
    }
}

void /* user */ previous_file()
{
    if (!history->dcb->len || !history->dcb->pos)
	return;

    if (in_active_state()) {
	play_queue(--history->dcb->pos);

	if (need_key_update())
	    update_prompt(TRUE);
    } else if (in_idle_state()) {
	history->dcb->pos--;
	inside_queue = TRUE;

	if (key_press)
	    update_prompt(TRUE);
    }
}

void determine_next_stream()
{
    DCharBuf *dcb;

    dcb = NULL;

    if (in_queue())
	play_queue(++history->dcb->pos);
    else {
	if (active_list && active_list->dcb->len)
	    dcb = active_list->dcb;
	else {
	    set_state(IDLE);
	    goto prompt_return;
	}

	if (!user_stream && !inside_queue)
	    add_stream_weight(active_list);

	if (inside_queue) {
	    inside_queue = FALSE;
	    post_event(QUEUE_EVENT);
	}

	switch (active_list->playmode) {
	case LEARN_MODE:
	    dcb->pos = get_learned(active_list);
	    break;
	case SRANDOM_MODE:
	    dcb->pos = get_smart_random(active_list);
	    break;
	case TRANDOM_MODE:
	    dcb->pos = get_random_index(dcb->len);
	    break;
	case REPEAT_MODE:
	    if (dcb->pos < (dcb->len - 1))
		dcb->pos++;
	    else if (dcb->pos == (dcb->len - 1)) {
		post_event(LIST_EVENT);
		dcb->pos = 0;
	    }
	    break;
	default:
	    set_state(IDLE);
	    goto prompt_return;
	}

	play_lindex(active_list, TRUE);
    }

 prompt_return:

    if (have_tty() && (!show_time_stamp || state == EXTERNAL))
	update_prompt(TRUE);
}
