#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "osmodule.h"
#include "osfile.h"
#include "sound.h"
#include "toolbox.h"
#include "macros.h"
#include "datavox.h"

#include "options.h"

#include "ztypes.h"
#include "control.h"
#include "fileio.h"
#include "osdepend.h"
#include "riscosio.h"
#include "riscoswimp.h"
#include "text.h"
#include "rosound.h"
#include "event.h"
#include "v6.h"

#include "quetzal.h"
#include "blorb.h"

static void load_effect(int effect);
static void start_effect(int effect, int vol, int repeats, int routine);
static void stop_effects(void);
static void *claim_fixed_mem(int size);
static void free_fixed_mem(void *block);

typedef struct sound_header SoundHeader;

struct sound_header
{
    unsigned short data_length;
    unsigned char repeats;
    char base_note;
    unsigned short frequency;
    short unused;
    unsigned short sample_length;
    byte data[1];
};

static SoundHeader *current_sound;

static unsigned current_length, current_frequency;

static int current_effect;
static int datavox_channel;

static int datavox_loaded;

static int previous_voice=-1;

static bool speech_started;

#ifdef ALLOW_SPEECH
bool use_speech = 1;
char speech_buffer[SPEECHBUFSIZE + 1];
int speech_ptr;
#endif

int sound_no_input;

static int callback_pending;

static int callback_routine;
static int callback_time;

static void cancel_sound_callback(void)
{
    wimp_poll_flags flags;

    /* Enable null events */
    event_get_mask(&flags);
    event_set_mask(flags | wimp_MASK_NULL);

    callback_pending=FALSE;
}

static int callback_check(wimp_event_no event_code, wimp_block *event,
                            toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    if (callback_pending && os_read_monotonic_time() >= callback_time)
    {
        cancel_sound_callback();
        if (previous_voice != -1)
        {
    	    sound_attach_voice(1, previous_voice, 0, 0);
    	    previous_voice=-1;
    	}

        if (callback_routine)
            call_interrupt(callback_routine);
    }

    return 0;
}

static void arrange_sound_callback(int routine, int delay)
{
    wimp_poll_flags flags;

    /* Enable null events */
    event_get_mask(&flags);
    event_set_mask(flags &~ wimp_MASK_NULL);

    callback_routine=routine;
    callback_time=os_read_monotonic_time()+delay;

    callback_pending=TRUE;
}

/*
 * sound
 *
 * Play a sound file or a note.
 *
 * argc = 1: argv[0] = note# (range 1 - 2)
 *
 *           Play note.
 *
 * argc = 2: argv[0] = ID# of sound file
 *           argv[1] = 1
 *
 *           Prepare a sound.

 * argc = 2: argv[0] = 0
 *           argv[1] = 3
 *
 *           Stop playing current sound.
 *
 * argc = 2: argv[0] = 0
 *           argv[1] = 4
 *
 *           Free allocated resources.
 *
 * argc = 3: argv[0] = ID# of sound file to replay.
 *           argv[1] = 2
 *           argv[2] = Volume (lower byte 1-8, or 0xFF (=8))
 *                     upper byte=no of repeats (in V5)
 *
 * argc = 4: argv[0] = ID# of sound file to replay.
 *           argv[1] = 2
 *           argv[2] = Volume/repeats
 *           argv[3] = End routine to call when sound has finished
 *
 */

void z_sound_effect(int argc, unsigned *argv)
{
    /*
    char buffer[256];
    zword_t bufferu[256];

    sprintf(buffer, "[Sound: %d ", argv[0]);
    native_string_to_unicode(bufferu, buffer, '?');
    display_string(bufferu);
    if (argc >= 2)
    {
    	sprintf(buffer, "%d ", argv[1]);
        native_string_to_unicode(bufferu, buffer, '?');
    	display_string(bufferu);
    }
    if (argc >= 3)
    {
    	sprintf(buffer, "%d ", argv[2]);
        native_string_to_unicode(bufferu, buffer, '?');
    	display_string(bufferu);
    }
    if (argc >= 4)
    {
    	sprintf(buffer, "%d ", argv[3]);
        native_string_to_unicode(bufferu, buffer, '?');
    	display_string(bufferu);
    }
    native_string_to_unicode(bufferu, "]", '?');
    display_string(bufferu);
    */

    /* Supply default parameters */

    if (argc < 4)
        argv[3] = 0;
    if (argc < 3)
        argv[2] = 4;
    if (argc < 2)
        argv[1] = 2;
    if (argc < 1)
        argv[0] = 1;

    /* Wait for previous sounds to finish, for up to four seconds */

    if ((argv[0] >= 3 || argv[1] >= 3) && sound_no_input)
    {
        os_t till=os_read_monotonic_time()+400;

    	while (datavox_read_address(1) && os_read_monotonic_time() < till)
    	    ;
    }

    if (callback_pending)
    	cancel_sound_callback();

    switch (argv[1])
    {
      case 1:
      	load_effect(argv[0]);
      	break;

      case 2:
        /* Volume -1 is maximum (8) */
        if ((argv[2]&0xFF) == 0xFF)
            argv[2]=(argv[2]&~0xFF) | 8;

    	if (speech_started)
    	{
    	    speech_started=FALSE;
    	    spch_oldchannel();
    	}

        if (argv[0] == 1 || argv[0]==2)
        {
            if (previous_voice != -1)
            {
    	    	sound_attach_voice(1, previous_voice, 0, 0);
    	    	previous_voice=-1;
    	    }
            sound_control(1, 0x12F+10*(argv[2]&0xFF), argv[0]==1?0x5000:0x4955, 6);
        }
        else
            start_effect(argv[0], argv[2] & 0xFF, argv[2] >> 8, argv[3]);
        break;

      case 3:
    	if (speech_started)
    	{
    	    speech_started=FALSE;
    	    spch_oldchannel();
    	}
        stop_effects();
        break;

      case 4:
    	if (speech_started)
    	{
    	    speech_started=FALSE;
    	    spch_oldchannel();
    	}
      	lose_effects();
      	break;
    }

}

void beep(void)
{
    unsigned arg[4];

    arg[0]=1;

    z_sound_effect(1, arg);
}

static void start_effect(int effect, int vol, int repeats, int routine)
{
    int pitch;
    int duration;

    if (current_effect != effect)
    	lose_effects();

    if (current_effect == 0)
    	load_effect(effect);

    if (current_effect == 0)
    	return;

    datavox_channel=datavox_request_channel(1, 0x12345678);

    if (datavox_channel == 0)
    	return;

    datavox_set_memory(1, current_sound->data,
                          current_sound->data+current_length);

    datavox_type(1, blorb_map ? 2 : 1);

    pitch=datavox_sample_to_pitch(1000000/current_frequency, 0);

    if (vol==0xFF)
    	vol=8;

    if (h_type >= V5)
    {
    	if (repeats == 255)
    	    duration = -1;
    	else if (repeats == 0)
    	    duration = 1;
    	else
    	    duration=repeats;
    }
    else
    {
    	duration=current_sound->repeats;
    	if (duration == 0)
    	    duration = -1;
    }

    datavox_timed(datavox_channel, duration!=1);

    if (duration==-1)
    	duration=0xF0000000;
    else
    {
        duration=(1000000/current_frequency)
                   *duration
                   *current_length
                   /50000;
    }

    sound_attach_voice(1, 0, 0, &previous_voice);
    sound_attach_named_voice(1, "DataVox-Voice");

    sound_control(1, 0x12F+10*vol, pitch, duration);

    sound_no_input=1;

    if (duration!=0xF0000000 && h_type >= V5)
    	arrange_sound_callback(routine, duration*5);
}


static void load_old_effect(int effect)
{
    char buffer[256];
    fileswitch_object_type type;
    int size;
    char *leaf=strrchr(StoryName, '.')+1;

    sprintf(buffer, "<Zip2000$Dir>.Resources.Sounds.%s.%.6s%02d/SND",
                                 leaf,
                                 leaf,
                                 effect);

    type=osfile_read_stamped(buffer, NULL, NULL, &size, NULL, NULL);

    if (type!=fileswitch_IS_FILE)
    {
        strcpy(buffer, StoryName);
        sprintf(strrchr(buffer, '.')+1, "Sounds.%.6s%02d/SND", leaf, effect);
    	type=osfile_read_stamped(buffer, NULL, NULL, &size, NULL, NULL);

    	if (type!=fileswitch_IS_FILE)
    	{
            char buffer2[256];
            sprintf(buffer2, msgs_lookup("NoSnd"), effect);
            warning(buffer2);
            return;
        }
    }

    current_sound=claim_fixed_mem(size);

    osfile_load_stamped(buffer, (byte *) current_sound, 0, 0, 0, 0);

    current_effect=effect;
    current_length=ntohs(current_sound->sample_length);
    current_frequency=ntohs(current_sound->frequency);
}

#define aiff_FORM 0x464F524D
#define aiff_AIFF 0x41494646
#define aiff_COMM 0x434F4D4D
#define aiff_SSND 0x53534E44

static void load_blorb_effect(int effect)
{
    bb_err_t err;
    bb_result_t res;
    bb_aux_sound_t *aux;
    int form_len, chunk_len;
    unsigned tmp;
    int have_COMM = FALSE, have_SSND = FALSE;
    int pos, size;
    int numChannels, numSampleFrames, sampleSize;
    long double sampleRate;

    err = bb_load_resource_snd(blorb_map, bb_method_FilePos, &res, effect, &aux);
    if (err)
    {
        warning_lookup_1("BlorbErr", bb_err_to_string(err));
        return;
    }
    clearerr(bfp);
    fseek(bfp, res.data.startpos, SEEK_SET);
    if (fget32(bfp) != aiff_FORM) goto bad_aiff;
    form_len = fget32(bfp);
    if (fget32(bfp) != aiff_AIFF) goto bad_aiff;

    pos = 4;
    while ((!have_COMM || !have_SSND) && pos <= form_len - 8 && !feof(bfp))
    {
        tmp = fget32(bfp);
        chunk_len = fget32(bfp);
        pos += 8;
        switch (tmp)
        {
          case aiff_COMM:
            if (chunk_len < 18) goto bad_aiff;
            numChannels = fget16(bfp);
            numSampleFrames = fget32(bfp);
            sampleSize = fget16(bfp);
            sampleRate = fget80(bfp);
            if (chunk_len > 18)
                fseek(bfp, chunk_len - 18L, SEEK_CUR);
            have_COMM = TRUE;
            pos += chunk_len;
            break;

          case aiff_SSND:
          {
            int i,j, start_pos = pos;
            tmp = fget32(bfp);
            fget32(bfp);
            pos += 8 + tmp;
            if (tmp) fseek(bfp, tmp, SEEK_CUR);
            current_sound = claim_fixed_mem(numSampleFrames + sizeof(SoundHeader));
            for (i = 0; i < numSampleFrames; i++)
            {
                for (j = 0; j < numChannels; j++)
                {
                    int sample;
                    if (sampleSize <= 8)
                        sample = fgetc(bfp);
                    else if (sampleSize <= 16)
                        sample = fget16(bfp) >> 8;
                    else if (sampleSize <= 24)
                        sample = fget24(bfp) >> 16;
                    else
                        sample = fget32(bfp) >> 24;
                    if (j == 0)
                        current_sound->data[i] = sample;
                }
            }
            pos += numSampleFrames * numChannels * (sampleSize >> 3);
            have_SSND = TRUE;
            if (pos - start_pos < chunk_len)
                fseek(bfp, chunk_len - (pos - start_pos), SEEK_CUR);
            break;
          }

          default:
            fseek(bfp, chunk_len, SEEK_CUR);
            pos += chunk_len;
            break;
        }
        if (pos & 1) {
            fgetc(bfp);
            pos++;
        }
    }

    if (have_SSND)
    {
        current_effect=effect;
        current_length = numSampleFrames;
        current_frequency = (unsigned) sampleRate;
        current_sound->repeats = aux ? aux->repeats : 1;
        return;
    }
  bad_aiff:
    if (current_sound)
    {
        free_fixed_mem(current_sound);
        current_sound = 0;
    }
    warning_lookup("AIFFErr");
}

static void load_effect(int effect)
{
    if (effect < 3)
    	return;

    if (!datavox_loaded)
    {
    	os_cli("RMEnsure DataVox 3.67 RMLoad <Zip2000$Dir>.Modules.DataVox");
    	datavox_loaded=1;
    }

    lose_effects();

    if (blorb_map)
        load_blorb_effect(effect);
    else
        load_old_effect(effect);
}

static void stop_effects(void)
{
    sound_control(1, 0, 0, 0);

    if (datavox_channel)
    {
        datavox_unset(datavox_channel);
    	datavox_deallocate_channel(1, 0x12345678);
        datavox_channel=0;
    }

    if (previous_voice != -1)
    {
    	sound_attach_voice(1, previous_voice, 0, 0);
    	previous_voice=-1;
    }
}

void lose_effects(void)
{
    if (current_effect)
    {
        stop_effects();
    	free_fixed_mem(current_sound);
    	current_sound=0;
    	current_effect=0;
    }
}

static os_dynamic_area_no sound_area_no;

static void *claim_fixed_mem(int size)
{
    void *area;

    if (os_version >= RISC_OS_350)
        sound_area_no=osdynamicarea_create(-1, size, (byte *) -1,
                                     0xA0, /* Not cacheable, not draggable */
                                     size, 0, 0, msgs_lookup("DynArea"),
                                     (byte **) &area, 0);
    else
    	area=osmodule_alloc(size);

    return area;
}

static void free_fixed_mem(void *block)
{
    if (os_version >= RISC_OS_350)
    {
    	osdynamicarea_delete(sound_area_no);
    	sound_area_no=0;
    }
    else
    	osmodule_free(block);
}

void setup_sound(int effects)
{
    if (!effects)
    	return;

    if (h_type >= V5)
    	event_register_wimp_handler(event_ANY, wimp_NULL_REASON_CODE, callback_check, 0);
}

#ifdef ALLOW_SPEECH
#define MAX_SAY 100

void flush_speech()
{
    char *start = speech_buffer, *end, *mid;
    char temp;

    spch_reset();
    speech_buffer[speech_ptr]='\0';

    /*
     * We are limited to 100? characters max for Speech! (Yuck)
     */

    for (;;)
    {
        while (isspace(*start) && isspace(*(start+1)))
        	start++;

        end = MIN(start + MAX_SAY, speech_buffer + speech_ptr - 1);

        if (end == speech_buffer + speech_ptr - 1)
        {
        	spch_say(start);
        	speech_ptr=0;
        	speech_started=TRUE;
        	return;
        }

    	/* Where to split? Try after full stops or question marks */
        for (mid = end; *mid != '.' && *mid != '?' && mid > start; mid--)
       	    continue;

    	/* Nope? Try after a space */
        if (mid == start)
            for (mid = end; !isspace(*mid) && mid > start; mid--)
        	continue;

    	/* What? Just split anyway */
        if (mid == start)
            mid = end - 1;

    	temp = *++mid;

        *mid = '\0';
        spch_sayw(start);
        *mid = temp;
        start = mid;
    }
}
#endif
