/*
 *  util.c - Various support functions.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include "mod.h"
#include "message.h"

extern struct mod_info M;
extern char quit;
extern int seqfd, mixerfd;

SEQ_DECLAREBUF();

/*
 *  Wordswapping functions
 */

unsigned short SWAPSHORT(char *a)
{
    return (((*(unsigned short *)a&0xff)<<8)|((*(unsigned short *)a>>8)&0xff));
}

unsigned long SWAPLONG(char *a)
{
    return (SWAPSHORT(a)<<16 | SWAPSHORT(a+2));
}

/*
 *  Removes nonprintable characters and trailing spaces from a string.
 */

void fix_string(char *s)
{
    char *a;

    a=s;
    while(*s) {
	if(*s >= 32 && *s <=126)
	    s++;
	else
	    *s++=' ';
    }

    for(--s; s >=a; --s)  {
	if(*s == ' ')
	    *s=0;
	else
	    break;
    }
}

/* Fills out a string with spaces, or truncates it if its too long. */

char *fill_string(char *s, int len)
{
    static char buf[128];
    int i;

    strcpy(buf, s);

    i=strlen(s);
    if(i > len)
	i=len;

    for(; i < len; ++i)
	buf[i]=' ';
    
    buf[i]=0;
    return buf;
}

/*
 *  This is seqbuf_dump() used by the SEQ_DUMPBUF macro.
 */

void seqbuf_dump()
{
    int thisone=0; /* No need to initialize, but it removes a warning */
    fd_set wset;

    while(_seqbufptr) {
	FD_ZERO(&wset);
	FD_SET(seqfd, &wset);
	if(select(seqfd+1, 0, &wset, 0, 0) >= 0) {
	    if((thisone=write(seqfd, _seqbuf, _seqbufptr)) == -1)
		error("Unable to write(sequencer)");

	    if(thisone < _seqbufptr) {
		memmove(_seqbuf, &(_seqbuf[thisone]), _seqbufptr-thisone);
		_seqbufptr-=thisone;
	    }
	    else
		_seqbufptr=0;
	}
	else {
	    if(errno != EINTR)
		error("select() in seqbuf_dump failed (errno=%d)\n", errno);
	}
    }
}

/*
 *  Changes the master volume by amount. amount=0 mutes/demutes
 */

void change_mastervolume(int amount)
{
    static int muted=100;
    int current;

    if(mixerfd == -1)
	return;

    if(ioctl(mixerfd, SOUND_MIXER_READ_SYNTH, &current) == -1)
	return;

    /* Take average of left/right */
    current=((current&0xff)+((current>>8)&0xff))/2;
    if(amount == 0) {
	if(!current)
	    current=muted;
	else {
	    muted=current;
	    current=0;
	}
    }
    else {
	current=MIN(MAX(0, current+amount), 100);
    }
    current=current|(current<<8);

    if(ioctl(mixerfd, SOUND_MIXER_WRITE_SYNTH, &current) == -1)
	return;
}


/*
 *  Takes track-data and returns the corresponding track-index.
 *  This is where identical tracks are found.
 */

int get_track_idx(struct event *e)
{
    int i;
    
    for(i=0; i < M.nr_tracks; ++i)
	if(!bcmp(e, M.tracks[i], sizeof(struct event)*64))
	    return i;
    
    M.tracks[M.nr_tracks]=(struct event *)malloc(sizeof(struct event)*64);
    memcpy((void *)M.tracks[M.nr_tracks], (void *)e, sizeof(struct event)*64);
    return M.nr_tracks++;
}


/* Allows reading of a small amounts of bytes from a file, n <= 10. Buffers
 * data to minimize number of read()-calls.
 * n= 0  Init.
 * n=-1  Cleanup (only needed if you need the read-ahead flushed).
 * The function returns true if all bytes could be read, otherwise false.
 */

int get_bytes(int fd, char *dest, int n)
{
    static char buf[1024];
    static int bufpos, lastbufpos, nomore;
    int this;

    if(n == -1) {
	if(lseek(fd, -(lastbufpos-bufpos+1), SEEK_CUR) == -1)
	    error("Seek() failed.\n");
	return 1;
    }
    
    if(!n) {
	bufpos=0;
	lastbufpos=read(fd, buf, 1024);
	if(!lastbufpos || lastbufpos == -1) {
	    return 0;
	}
	else {
	    if(lastbufpos != 1024)
		nomore=1;
	    else
		nomore=0;
	    --lastbufpos;
	    return 1;
	}
    }

    if(bufpos+n-1 <= lastbufpos) {
	memcpy(dest, &buf[bufpos], n);
	bufpos+=n;
    }
    else {
	return 0;
    }

    if(bufpos > 1010 && !nomore) {
	memmove(buf, &buf[bufpos], 1024-bufpos);
	if((this=read(fd, &buf[1024-bufpos], bufpos)) == 0 || this == -1 ||
	   this != bufpos) {
	    nomore=1;
	    if(this <= 0)
		this=0;
	    lastbufpos=1024-bufpos+this-1;
	}
	bufpos=0;
    }
    return 1;
}


/*
 * Writes a string to a filedescriptor
 */

void safe_read(int fd, struct mod_message *buf, int len)
{
    int sofar, thisone;
    
    sofar=0;
    do {
	thisone=read(fd, ((char *)buf)+sofar, len-sofar);
	if(thisone >= 0) {
	    sofar+=thisone;
	    if(!thisone)
		debug("safe_read(): 0\n");
	}
	else {
	    if(errno != EAGAIN)
		error("Error reading pipe (%d)\n", errno);
	    debug("safe_read() returned EAGAIN, retrying...\n");
	}
    }
    while(sofar < len);
}


/*
 * Writes a string to a filedescriptor
 */

void safe_write(int fd, struct mod_message *buf, int len)
{
    int sofar, thisone;
    
    sofar=0;
    do {
	thisone=write(fd, ((char *)buf)+sofar, len-sofar);
	if(thisone >= 0) {
	    sofar+=thisone;
	    if(!thisone)
		debug("safe_write(): 0\n");
	}
	else {
	    if(errno != EAGAIN)
		error("Error writing pipe (%d)\n", errno);
	    error("safe_write() would block...\n");
	}
    }
    while(sofar < len);
}


/* Returns < 0 if we got SIG_QUIT otherwise exitstatus of command */

int my_system(char *a)
{
    int p, status;

    switch(p=fork()) {
      case -1:
	error("Fork failed in my_system.");
	break;
      case 0:
	exit(system(a));
	break;
      default:
	while(1) {
	    if(waitpid(p, &status, 0) >= 0) {
		if(WIFEXITED(status)) {
		    if(quit == QUIT_SIGNAL)
			return -1;
		    else
			return WEXITSTATUS(status);
		}
		else {
		    debug("Huh??!! system()-problems\n");
		    return 1;
		}
	    }
	    else {
		if(errno != EINTR && errno != ERESTARTSYS) {
		    error("Huh??!! waitpid()-problems\n");
		}
	    }
	}
    }
    error("Never reached");
    return 0; /* Remove warning */
}


/* Escapes characters for the shell */

char *escape_name(char *s, int a)
{
    static char buf1[PATH_MAX+1], buf2[PATH_MAX+1];
    char *d;
    
    d=(a ? buf2 : buf1);
    while(*s) {
	if(!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
	     (*s >= '0' && *s <= '9') || *s == '.' || *s == '/' || *s == '-'
	     || *s == '_'))
	    *d++='\\';
	*d++=*s++;
    }
    *d=0;
    return (a? buf2 : buf1);
}
