#include <platform.h>
#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#include <memman.h>
#include <zvuk.h>
#include <wav_mem.h>
#include <event.h>
#include "globals.h"
#include <math.h>
#include <strlite.h>

#include <unistd.h>
#define PL_RANDOM 1
#define PL_FORWARD 2
#define PL_FIRST 3

#define CHANNELS 20
#define TRACKS 512

#define SND_EFF_MAXVOL 32000
#define SND_EFF_DESCENT 8000

#define have_loop(x) ((x)->start_loop!=(x)->end_loop)

typedef unsigned short SND_FIND_TABLE[2];
typedef struct snd_info
  {
  const TMA_SOUND *data;              //4
  short xpos,ypos,side;         //10
  word volume,block;            //14
  }SND_INFO;

static short chan_state[CHANNELS];
static short track_state[TRACKS];
short sample_volume=255;

//static struct t_wave wav_last_head;
//static int wav_last_size;
static int mute_task=-1;
static char sound_enabled=1;

SND_INFO tracks[TRACKS];
SND_INFO playings[CHANNELS];
static word locks[32];

TSTR_LIST cur_playlist=NULL;
TSTR_LIST sound_table=NULL;
int playlist_size;
int playing_track=0;
int remain_play=0;
int play_list_mode=PL_RANDOM;

void init_tracks()
  {
  memset(tracks,0,sizeof(tracks));
  memset(playings,0,sizeof(playings));
  memset(chan_state,0xff,sizeof(chan_state));
  memset(track_state,0xff,sizeof(track_state));
  memset(locks,0,sizeof(locks));
  }



/*void pcspeak_uroven(char value,int time);
#pragma aux pcspeak_uroven parm[bh][ecx]=\
        "mov ah,last_beep_lev"\
    "lp2:add ah,bh"\
        "mov al,48h"\
        "jc lp1"\
        "mov al,4ah"\
    "lp1:out 61h,al"\
        "loop lp2"\
        "mov last_beep_lev,ah"\
   modify [eax]


static int get_pc_speed()
  {
  int ticks=0;
  int timer=get_timer_value();
  while (get_timer_value()-timer<50) pcspeak_uroven(127,1000),ticks+=1000;
  return ticks;
  }

void pc_speak_play_sample(char *sample,int size,char step,int freq)
  {
  static speed=0;
  int ticker;
  if (!speed) speed=get_pc_speed();
  _disable();
  ticker=speed/freq;
  sample+=step/2;
  while (size>0)
     {
     if (step==2)
        pcspeak_uroven(*sample ^ 0x80,ticker);
     else
        pcspeak_uroven(*sample,ticker);
     sample+=step;
     size-=step;
     }
  _enable();
  nosound();
  }

*/

int find_free_channel(int stamp)
  {
  int i,j;
  int minvol,left,right,mid;

  j=0;
  if (stamp) for(i=0;i<CHANNELS;i++) if (chan_state[i]==stamp) return i;
  minvol=0xffff;
  for(i=0;i<CHANNELS;i++)
     {
     if (!get_channel_state(i)) return i;
     get_channel_volume(i,&left,&right);
     mid=(left+right)/2;
     if (playings[i].side<0) mid*=2;
     if (mid<minvol)
        {
        minvol=mid;j=i;
        }
    }
  return j;
  }




void release_channel(int channel)
  {
  int i;

  i=chan_state[channel];
  if (i==-1) return;
  mute_channel(channel);
     {
     aunlock(playings[channel].block);
     chan_state[channel]=-1;
     track_state[i]=-1;
     }
  }

int calc_volume(int *x,int *y,int side)
  {
  int ds;

  side&=3;*x=-(*x);*y=-(*y);
  *x+=(side==1)*(*x>=0)-(side==3)*(*x<=0);
  *y+=(side==2)*(*y>=0)-(side==0)*(*y<=0);
  ds=abs(*x)+abs(*y);
  ds=SND_EFF_MAXVOL-(SND_EFF_DESCENT*8*ds)/(8+ds);
  return ds;
  }

int calcul_volume(int chan,int x,int y,int side,int volume)
  {
  int lv,rv;
  int ds,bal,i;

  if (side==-1) side=viewdir;
  side&=3;
  ds=calc_volume(&x,&y,side);
  if (ds<=0)
     {
     release_channel(chan);
     return -1;
     }
  for(i=0;i<viewdir;i++)
    {
    bal=x;
    x=y;
    y=-bal;
    }
  y=abs(y);
  if (abs(x)>y)
    if (x>0) bal=100-y*50/x;else bal=-100-y*50/x;
  else bal=50*x/y;
  ds=ds*volume/100;
  if (bal<0)
     {
     lv=ds*(100+bal)/100;rv=ds;
     }
  else
     {
     rv=ds*(100-bal)/100;lv=ds;
     }
  lv=(lv*sample_volume)>>8;
  rv=(rv*sample_volume)>>8;
  set_channel_volume(chan,lv,rv);
  return 0;
  }

void wav_load(void **p,int32_t *s)
  {
  char *sr;
  char *tg;
  void *tgr;
  struct t_wave x[3];

  sr=*p;
  sr=find_chunk(sr,WAV_FMT);
  read_chunk(sr,&x);
  sr=*p;
  sr=find_chunk(sr,WAV_DATA);
  *s=get_chunk_size(sr);
  tgr=tg=getmem(*s+sizeof(struct t_wave)+4);
  memcpy(tgr,x,sizeof(struct t_wave));
  tg+=sizeof(struct t_wave);
  *(int *)tg=*s;
  tg+=4;
  read_chunk(sr,tg);
  free(*p);
  *p=tgr;
  *s+=sizeof(struct t_wave)+4;
/*  if (x[0].freq!=x[0].bps)
     {
     char s;

     siz>>=1;
     s=siz & 1;
     siz>>=1;
     d=tg;
     for(;siz--;d++) *d^=0x80008000;
     if (s) {c=(char *)d;c[1]^=0x80;}
     }
  else
     {
     char s;

     s=siz & 3;
     siz>>=2;
     d=(int32_t *)tg;
     for(;siz--;d++) *d^=0x80808080;
     c=(char *)d;
     for(;s--;c++) *c^=0x80;
     }*/
  }

void play_effekt(int x,int y,int xd,int yd,int side,int sided,const TMA_SOUND *p)
  {
  int chan;
  int blockid;
  SND_INFO *track;
  char *s;

  if (!sound_enabled) return;
  side;
  chan=find_free_channel(p->soundid);
  release_channel(chan);
  track=&tracks[p->soundid];
  track->data=p;
  track->xpos=xd;
  track->ypos=yd;
  track->side=sided;
  track_state[p->soundid]=-1;
  if (p->bit16 & 0x8)
     {
     int vol=SND_EFF_MAXVOL*p->volume/100;
     if (rnd(100)>50) set_channel_volume(chan,rnd(vol),vol);
     else set_channel_volume(chan,vol,rnd(vol));
     }
  else {
     if (calcul_volume(chan,x-xd,y-yd,/*side-*/sided,p->volume)) return;
  }

    blockid = find_handle(p->filename, wav_load);
    if (blockid == -1) {
        def_handle(end_ptr, p->filename, wav_load, SR_ZVUKY);
        blockid = end_ptr++;
        if (level_preload)
            apreload(blockid);
    }

      alock(blockid);
      s=ablock(blockid);
      s+=p->offset+sizeof(struct t_wave)+4;
      play_sample(chan,s,p->end_loop-p->offset,p->start_loop-p->offset,p->freq,1+(p->bit16 & 1));
      playings[chan].data=p;
      playings[chan].xpos=xd;
      playings[chan].ypos=yd;
      playings[chan].side=sided;
      playings[chan].volume=p->volume;
      playings[chan].block=blockid;
      chan_state[chan]=p->soundid;
      track_state[p->soundid]=chan;
  }
void recalc_volumes(int sector,int side)
  {
  int i;
  int newx,newy;//,layer;

  if (sector>=mapsize) return;

  side;
  SEND_LOG("(SOUND) %s","Recalculating volumes");
  newx=map_coord[sector].x;
  newy=map_coord[sector].y;
//  layer=map_coord[sector].layer;
  for(i=0;i<CHANNELS;i++)
     if (chan_state[i]>=0 && playings[i].side>=0)
        {
        calcul_volume(i,newx-playings[i].xpos,newy-playings[i].ypos,/*side-*/playings[i].side,playings[i].volume);
        if (!get_channel_state(i)) release_channel(i);
        }
     else calcul_volume(i,0,0,-1,playings[i].volume);
  for(i=1;i<TRACKS;i++) if (track_state[i]<0 && tracks[i].data!=NULL)
     {
     if (tracks[i].side<0)
        {
        if (have_loop(tracks[i].data))
          play_effekt(0,0,0,0,-1,-1,tracks[i].data);
        }
     else
        {
       int x=newx-tracks[i].xpos, y=newy-tracks[i].ypos;
        if (calc_volume(&x,&y,tracks[i].side)>0)
           if (have_loop(tracks[i].data))play_effekt(newx,newy,tracks[i].xpos,tracks[i].ypos,side,tracks[i].side,tracks[i].data);
        }
     }
  mute_task=-1;
  }

void create_playlist(char *playlist)
  {
  char *c;
  char mode[20];
  char shift;
  int i=1,j;
  if (cur_playlist!=NULL) release_list(cur_playlist);
  cur_playlist=NULL;
  if (playlist==NULL) return;
  if (!playlist[0]) return;
  c=playlist;
  while (*c && *c==32) c++;
  sscanf(c,"%s",mode);
  strupr(mode);
  shift=1;
  if (!strcmp(mode,"RANDOM")) play_list_mode=PL_RANDOM;
  else if (!strcmp(mode,"FORWARD")) play_list_mode=PL_FORWARD;
  else if (!strcmp(mode,"FIRST")) play_list_mode=PL_FIRST;
  else shift=0;
  if (shift) c+=strlen(mode);else play_list_mode=PL_RANDOM;
  while (*c && *c==32) c++;
  playlist=c;
  if (!playlist[0]) return;
  for (c=playlist;c!=NULL;c=strchr(c+1,' ')) i++;
  playlist_size=i-1;
  cur_playlist=create_list(i);
  j=0;
  for (c=playlist;c!=NULL;c=strchr(c+1,' '))
     {
     char *e;
     char d[MAX_FILESYSTEM_PATH+2]="!";
     strncat(d,c+j,MAX_FILESYSTEM_PATH);d[MAX_FILESYSTEM_PATH+1]=0;j=1;
     if ((e=strchr(d,32))!=NULL) *e=0;
     str_add(&cur_playlist,d);
     }
  if (play_list_mode==PL_FIRST)
     {
     cur_playlist[0][0]=32;
     remain_play=1;
     play_list_mode=PL_RANDOM;
     }
  else
     {
     remain_play=0;
     }
  playing_track=-1;
  }

const char * end_of_song_callback(void *ctx) {
    return get_next_music_from_playlist();
}
const char *get_next_music_from_playlist()
  {
  int i,step;
  static char d[MAX_FILESYSTEM_PATH];

  if (cur_playlist==NULL) return NULL;
  if (!remain_play)
     for(i=0;cur_playlist[i]!=NULL;remain_play++,i++) cur_playlist[i][0]=32;
  if (play_list_mode==PL_RANDOM)
     step=rand()*(playlist_size-1)/32768+1;
  else
     step=1;
  i=playing_track;
  do
     {
     i++;
     if (cur_playlist[i]==NULL) i=0;
     if (cur_playlist[i][0]==32) step--;
     }
  while (step);
  playing_track=i;
  snprintf(d,sizeof(d),"%s%s",pathtable[SR_MUSIC],cur_playlist[i]+1);
  if (!check_file_exists(d)) {
      snprintf(d,sizeof(d),"%s%s",pathtable[SR_ORGMUSIC],cur_playlist[i]+1);
      if (!check_file_exists(d)) {
          return NULL;
      }
  }
  cur_playlist[i][0]=33;
  remain_play--;
  return d;
  }

void purge_playlist()
  {
  if (cur_playlist!=NULL)release_list(cur_playlist);
  cur_playlist=NULL;
  }

void play_sample_at_sector(int sample,int sector1,int sector2,int track, char loop)
  {
  int x,y,xd,yd,chan;
  char *s;
  struct t_wave *p;
  int siz;
	int oldtrack;

  if (!sound_enabled) return;
  if (map_coord[sector1].layer!=map_coord[sector2].layer) return;
  x=map_coord[sector1].x;
  y=map_coord[sector1].y;
  xd=map_coord[sector2].x;
  yd=map_coord[sector2].y;
  chan=find_free_channel(track);
	oldtrack=track_state[track];
  if (!track || oldtrack==-1) release_channel(chan);
  if (calcul_volume(chan,x-xd,y-yd,viewdir,100)) return;
  if (!track || oldtrack==-1)
     {
     alock(sample);
     s=ablock(sample);
     p=(struct t_wave *)s;
     s+=sizeof(struct t_wave);
     siz=*(int *)s;s+=4;
     play_sample(chan,s,siz,loop?0:siz,p->freq,(p->freq!=p->bps?2:1));
     playings[chan].data=NULL;
     }
  playings[chan].xpos=xd;
  playings[chan].ypos=yd;
  playings[chan].side=viewdir;
  playings[chan].volume=100;
  playings[chan].block=sample;
  chan_state[chan]=track;
  track_state[track]=chan;
  }

void play_sample_at_channel(int sample,int channel,int vol)
  {
  char *s;
  struct t_wave *p;
  int siz;

  if (!sound_enabled) return;
  channel+=CHANNELS;
  vol*=SND_EFF_MAXVOL/100;
  set_channel_volume(channel,vol,vol);
  if (locks[channel]) aunlock(locks[channel]);
  alock(sample);
  locks[channel]=sample;
  s=ablock(sample);
  p=(struct t_wave *)s;
  s+=sizeof(struct t_wave);
  siz=*(int *)s;s+=4;
  play_sample(channel,s,siz,siz,p->freq,(p->freq!=p->bps?2:1));
  }


void create_sound_table(char *template,int32_t size)
  {
  char *c,*s;
  int i=0;

  if (sound_table==NULL) sound_table=create_list(2);
  s=c=template;
  while (c-s<size)
     {
     if (c[0]!=0) str_replace(&sound_table,i,c);
     c=strchr(c,0)+1;
     i++;
     }
  }

void create_sound_table_old()
  {
  char *c,*s;
  int32_t pocet;
  int i=0;

  if (sound_table==NULL) sound_table=create_list(2);
  s=c=ablock(H_SOUND_DAT);
  memcpy(&pocet,s,sizeof(int32_t));c+=4;
  while (pocet--)
     {
     if (c[0]!=0) str_replace(&sound_table,i,c);
     c=strchr(c,0)+1;
     i++;
     }
  }


void stop_track(int track)
  {
  int chan;
  chan=track_state[track];
  if (chan==-1) return;
  chan_break_loop(chan);
  }

void stop_track_free(int track)
  {
  int chan;
  chan=track_state[track];
  if (chan==-1) return;
  chan_break_loop(chan);
  track_state[track]=-1;
  chan_state[chan]=0;
  }

void mute_all_tracks(char all)
  {
  int i;
  for(i=0;i<CHANNELS;i++)
     if (playings[i].side!=-1 || all) release_channel(i);
  mute_task=-1;
  SEND_LOG("(SOUND) %s (%d)","MUTE Tracks",all);
  }


void kill_all_sounds()
  {
  int i;
  SEND_LOG("(SOUND) Killing sound tracks...");
  for (i=0;i<CHANNELS;i++) release_channel(i);
  for (i=0;i<32;i++) if (locks[i]!=0) aunlock(locks[i]);
  }

char test_playing(int track)
  {
  return track_state[track]!=-1;
  }

static int flute_canal=30;

void start_play_flute(char note)
  {
  void *q;
  char *w;
  float realfrq;
  int vol=50;

  realfrq=16000*pow(2,note/12.0);
  if (check_snd_effect(SND_GFX))
     {
     q=ablock(H_FLETNA);
     w=q;w+=sizeof(struct t_wave)+4;
     vol*=SND_EFF_MAXVOL/100;
     set_channel_volume(flute_canal,vol,vol);
     play_sample(flute_canal,w,0x1665,0xADE,(int)(realfrq+0.5),1);
     }
  else
     {
     //sound((unsigned short)(realfrq/30.53));
     }
  }

void stop_play_flute()
  {
  void *q;
  char *w;

  if (check_snd_effect(SND_GFX))
     {
     q=ablock(H_FLETNA);
     w=q;w+=sizeof(struct t_wave);
     chan_break_ext(flute_canal,w+4,*(int *)w);
     flute_canal^=1;
     }
  else
     {
     //nosound();
     }
  }

char enable_sound(char enbl)
  {
  register char save;

  save=sound_enabled;
  sound_enabled=enbl;
  SEND_LOG("(SOUND) Sound status (en/dis) changed: new %d, old %d",enbl,save);
  return save;
  }
