//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2011 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: FLAC file handling (parsing/decoding with decoders\ffmpegac lib)

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_INFILE_FLAC

#include "in_rawau.h"
#include "tagging.h"

#define FLAC_METADATA_TYPE_STREAMINFO    0
#define FLAC_METADATA_TYPE_PADDING       1
#define FLAC_METADATA_TYPE_SEEKTABLE     3
#define FLAC_METADATA_TYPE_VORBISCOMMENT 4
#define FLAC_METADATA_TYPE_CUESHEET      5
#define FLAC_METADATA_TYPE_MAX           6
#define FLAC_METADATA_BLOCKS_MAX        10 // ??? should be not more than type_max+1

#define FLAC_METADATA_SIZE_STREAMINFO   34

#define FLAC_METADATA_GET_LASTBIT(val) (val>>31)
#define FLAC_METADATA_GET_TYPE(val)    ((val>>24)&0x7f)
#define FLAC_METADATA_GET_SIZE(val)    (val&0x00ffffff)
#define FLAC_METADATA_MAKE_HEAD(last,type,size) (((last)<<31)|((type)<<24)|(size))

#define FFLAC_BITSTREAM_BUFSIZE 65536

typedef struct flac_metadata_block_s{
 mpxp_uint32_t block_header;   // packed 4 bytes info (lastbit,type,size)
 mpxp_uint8_t *block_data;     // with the 4 bytes header
 mpxp_uint32_t block_size_old; // with the 4 bytes header
 mpxp_uint32_t block_offset;   // file begin position of the block
}flac_metadata_block_s;

typedef struct flac_seek_point_s{
 mpxp_uint64_t sample_offset;
 mpxp_uint64_t byte_offset;
 mpxp_uint16_t sample_num;
}flac_seek_point_s;

typedef struct flac_demuxer_data_s{
 struct mpxplay_bitstreambuf_s *bs;
 mpxp_filesize_t flacheaderbegin; // possible bullshit id3v2 header
 unsigned long max_blocksize,min_framesize,max_framesize;
 mpxp_uint8_t *extradata;
 unsigned long extradata_size;
 mpxp_int64_t  pcmdatalen;
 unsigned long seekpoint_num;
 struct flac_seek_point_s *fspi;
 unsigned int metadata_block_count;
 struct flac_metadata_block_s *metadata_blocks;
 struct vorbiscomment_info_s vci;
}flac_demuxer_data_s;

static int flac_parse_header(struct flac_demuxer_data_s *flaci,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode);

static int infflac_assign_values(struct flac_demuxer_data_s *flaci,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 unsigned int bytes_per_sample;

 if((adi->outchannels<PCM_MIN_CHANNELS) || (adi->outchannels>PCM_MAX_CHANNELS))
  return 0;
 if((adi->bits<PCM_MIN_BITS) || (adi->bits>PCM_MAX_BITS))
  return 0;

 miis->timemsec=(float)flaci->pcmdatalen*1000.0/(float)adi->freq;

 miis->longname="  FLAC  ";

 adi->bitratetext=malloc(MPXPLAY_ADITEXTSIZE_BITRATE+8);
 if(!adi->bitratetext)
  return 0;

 bytes_per_sample=(adi->bits+7)/8;
 sprintf(adi->bitratetext,"%2d/%2.1f%%",adi->bits,100.0*(float)miis->filesize/(float)flaci->pcmdatalen/(float)bytes_per_sample/(float)adi->filechannels);
 adi->bitratetext[MPXPLAY_ADITEXTSIZE_BITRATE]=0;

 spi->bs_framesize=flaci->max_framesize;

 return 1;
}

static int INFLAC_infile_open(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,char *filename,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 struct mpxplay_streampacket_info_s *spi=miis->audio_stream;
 struct flac_demuxer_data_s *flaci;
 unsigned long id3v2size;

 if(!fbfs->fopen(fbds,filename,O_RDONLY|O_BINARY,0))
  return MPXPLAY_ERROR_INFILE_FILEOPEN;

 miis->filesize=fbfs->filelength(fbds);
 if(miis->filesize<127)
  goto err_out_open;

 flaci=calloc(1,sizeof(struct flac_demuxer_data_s));
 if(!flaci)
  return MPXPLAY_ERROR_INFILE_MEMORY;
 miis->private_data=flaci;

 flaci->bs=mpxplay_bitstream_alloc(FFLAC_BITSTREAM_BUFSIZE);
 if(!flaci->bs)
  goto err_out_open;

 if(mpxplay_bitstream_fill(flaci->bs,fbfs,fbds,MPXPLAY_TAGGING_ID3V2_HEADSIZE)!=MPXPLAY_ERROR_MPXINBUF_OK)
  goto err_out_open;

 id3v2size=mpxplay_tagging_id3v2_totalsize(mpxplay_bitstream_getbufpos(flaci->bs));
 if(id3v2size){
  if(fbfs->fseek(fbds,id3v2size,SEEK_SET)>0){
   flaci->flacheaderbegin=id3v2size;
   mpxplay_bitstream_reset(flaci->bs);
  }
 }

 if(!flac_parse_header(flaci,fbfs,fbds,miis,openmode))
  goto err_out_open;

 if(!infflac_assign_values(flaci,fbfs,fbds,miis))
  goto err_out_open;

 if(openmode&MPXPLAY_INFILE_OPENMODE_INFO_DECODER){
  spi->streamtype=MPXPLAY_SPI_STREAMTYPE_AUDIO;
  spi->wave_id=MPXPLAY_WAVEID_FLAC;
  funcbit_enable(spi->flags,(MPXPLAY_SPI_FLAG_NEED_DECODER|MPXPLAY_SPI_FLAG_NEED_PARSING));
  spi->extradata=flaci->extradata;
  spi->extradata_size=flaci->extradata_size;
 }

 return MPXPLAY_ERROR_INFILE_OK;

err_out_open:
 return MPXPLAY_ERROR_INFILE_CANTOPEN;
}

static void INFLAC_infile_close(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 struct flac_demuxer_data_s *flaci=(struct flac_demuxer_data_s *)miis->private_data;
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 unsigned int i;
 if(flaci){
  mpxplay_bitstream_free(flaci->bs);
  if(flaci->extradata)
   free(flaci->extradata);
  if(flaci->fspi)
   free(flaci->fspi);
  if(flaci->metadata_blocks){
   for(i=0;i<flaci->metadata_block_count;i++)
    if(flaci->metadata_blocks[i].block_data)
     free(flaci->metadata_blocks[i].block_data);
   free(flaci->metadata_blocks);
  }
  if(adi->bitratetext)
   free(adi->bitratetext);
  free(flaci);
 }
 fbfs->fclose(fbds);
}

long INFLAC_infile_fseek(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,long newframenum)
{
 struct flac_demuxer_data_s *flaci=(struct flac_demuxer_data_s *)miis->private_data;
 struct flac_seek_point_s *fspi;
 long i,sdiff,bdiff;
 mpxp_filesize_t newfilepos;
 mpxp_int64_t newpcmpos;
 //char sout[256];

 if(!flaci)
  return -1;

 i=flaci->seekpoint_num;
 fspi=flaci->fspi;
 if((i<2) || !fspi || !newframenum) // !!! ??? flac decoder crashes without the latest
  return INRAWAU_fseek(fbfs,fbds,miis,newframenum);

 newpcmpos=(mpxp_int64_t)((float)flaci->pcmdatalen*(float)newframenum/(float)miis->allframes);
 //sprintf(sout,"i:%d np:%lld pl:%lld lp:%8.8X",i,newpcmpos,flaci->pcmdatalen,(fspi+i-1)->sample_offset);
 //display_message(1,0,sout);
 do{
  if((newpcmpos>=fspi->sample_offset) && (newpcmpos<(fspi+1)->sample_offset))
   break;
  fspi++;
 }while(--i);
 //sprintf(sout,"i:%d np:%lld so:%lld bp:%8.8X",i,newpcmpos,fspi->sample_offset,(long)fspi->byte_offset);
 //display_message(1,0,sout);
 if(!i)
  return MPXPLAY_ERROR_INFILE_EOF;
 sdiff=(fspi+1)->sample_offset-fspi->sample_offset;

 if(sdiff>(fspi->sample_num*2)){
  bdiff=(long)((fspi+1)->byte_offset-fspi->byte_offset);
  newfilepos=(mpxp_filesize_t)((float)bdiff*(float)(newpcmpos-fspi->sample_offset)/(float)sdiff);
  newfilepos+=fspi->byte_offset;
  //sprintf(sout,"bd:%d sd:%d nf:%lld",bdiff,sdiff,newfilepos);
  //display_message(1,0,sout);
 }else{
  if(!(miis->seektype&MPX_SEEKTYPE_BACKWARD) && (i>=2) && (newpcmpos>fspi->sample_offset))
   fspi++;
  newfilepos=fspi->byte_offset;
  newpcmpos=fspi->sample_offset;
 }
 //sprintf(sout,"i:%d np:%lld so:%lld bp:%lld fp:%lld",i,newpcmpos,fspi->sample_offset,fspi->byte_offset,newfilepos);
 //display_message(1,0,sout);
 if(fbfs->fseek(fbds,newfilepos,SEEK_SET)<0)
  return MPXPLAY_ERROR_INFILE_EOF;

 newframenum=(long)((float)miis->allframes*(float)newpcmpos/(float)flaci->pcmdatalen);
 return newframenum;
}

//--------------------------------------------------------------------------

static void flac_metadata_streaminfo(struct flac_demuxer_data_s *flaci,struct mpxplay_infile_info_s *miis)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;

 if(!flaci->extradata){
  flaci->extradata=malloc(FLAC_METADATA_SIZE_STREAMINFO+MPXPLAY_SPI_EXTRADATA_PADDING);
  if(flaci->extradata){
   pds_memcpy(flaci->extradata,mpxplay_bitstream_getbufpos(flaci->bs),FLAC_METADATA_SIZE_STREAMINFO);
   pds_memset(flaci->extradata+FLAC_METADATA_SIZE_STREAMINFO,0,MPXPLAY_SPI_EXTRADATA_PADDING);
   flaci->extradata_size=FLAC_METADATA_SIZE_STREAMINFO;
  }
 }
                        mpxplay_bitstream_skipbits(flaci->bs,  16);
 flaci->max_blocksize = mpxplay_bitstream_getbits_be24(flaci->bs, 16);

 flaci->min_framesize = mpxplay_bitstream_getbits_be24(flaci->bs, 24);
 flaci->max_framesize = mpxplay_bitstream_getbits_be24(flaci->bs, 24);

 adi->freq         = mpxplay_bitstream_getbits_be24(flaci->bs, 20);
 adi->filechannels = mpxplay_bitstream_getbits_be24(flaci->bs,  3) + 1;
 adi->outchannels  = adi->filechannels;
 adi->bits         = mpxplay_bitstream_getbits_be24(flaci->bs,  5) + 1;

 flaci->pcmdatalen = mpxplay_bitstream_getbits_be64(flaci->bs, 36);

 //mpxplay_bitstream_skipbits(flaci->bs,64); // md5 sum
 //mpxplay_bitstream_skipbits(flaci->bs,64); //
}

static int flac_parse_header(struct flac_demuxer_data_s *flaci,struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,mpxp_uint32_t openmode)
{
 mpxplay_audio_decoder_info_s *adi=miis->audio_decoder_infos;
 struct flac_seek_point_s *fspi;
 unsigned long metadata_last;

 if(fbfs->fseek(fbds,flaci->flacheaderbegin,SEEK_SET)<0)
  return 0;
 if(fbfs->get_le32(fbds)==PDS_GET4C_LE32('f','L','a','C')){
  do{
   unsigned long metadata_type,metadata_size;
   mpxp_uint32_t i=fbfs->get_be32(fbds);
   if(!i)
    break;
   metadata_last = FLAC_METADATA_GET_LASTBIT(i);
   metadata_type = FLAC_METADATA_GET_TYPE(i);
   metadata_size = FLAC_METADATA_GET_SIZE(i);
   if(metadata_size){
    mpxp_filesize_t filepos;
    filepos=fbfs->ftell(fbds);
    switch(metadata_type){
     case FLAC_METADATA_TYPE_STREAMINFO:
      mpxplay_bitstream_reset(flaci->bs);
      mpxplay_bitstream_fill(flaci->bs,fbfs,fbds,metadata_size);
      flac_metadata_streaminfo(flaci,miis);
      //if(!(openmode&(MPXPLAY_INFILE_OPENMODE_INFO_ID3|MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB)))
      if(!(openmode&MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB))
       goto err_out_parse;
      break;
     case FLAC_METADATA_TYPE_SEEKTABLE:
      if(!(openmode&MPXPLAY_INFILE_OPENMODE_LOAD_SEEKTAB))
       break;
      i=metadata_size/18;
      if(i<2)
       break;
      flaci->fspi=fspi=malloc((i+2)*sizeof(struct flac_seek_point_s));
      if(!fspi)
       break;
      flaci->seekpoint_num=i;
      do{
       fspi->sample_offset=fbfs->get_be64(fbds);
       fspi->byte_offset=fbfs->get_be64(fbds);
       fspi->sample_num=fbfs->get_be16(fbds);
       if(fspi->sample_offset!=0xffffffffffffffff)
        fspi++;
      }while(--i);
      fspi->sample_offset=flaci->pcmdatalen;
      fspi->byte_offset=fbfs->filelength(fbds);
      break;
    }
    if(fbfs->fseek(fbds,filepos+metadata_size,SEEK_SET)<0)
     break;
   }
  }while(!metadata_last);
 }
err_out_parse:
 if(adi->freq && flaci->pcmdatalen && (adi->bits>=8) && adi->filechannels){
  if(flaci->seekpoint_num){
   unsigned int i=flaci->seekpoint_num;
   mpxp_filesize_t flacdatabegin=fbfs->ftell(fbds);
   fspi=flaci->fspi;
   do{
    fspi->byte_offset+=flacdatabegin;
    fspi++;
   }while(--i);
  }
  return 1;
 }
 return 0;
}

static int flac_read_vorbiscomment(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,struct vorbiscomment_info_s *vci)
{
 struct flac_demuxer_data_s *flaci=(struct flac_demuxer_data_s *)miis->private_data;
 struct flac_metadata_block_s *mb;
 unsigned long metadata_last,blockread_status=0;
 int retcode=MPXPLAY_ERROR_INFILE_EOF;

 if(!flaci)
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
 mb=flaci->metadata_blocks;

 fbfs->fseek(fbds,flaci->flacheaderbegin,SEEK_SET);

 if(fbfs->get_le32(fbds)!=PDS_GET4C_LE32('f','L','a','C'))
  return MPXPLAY_ERROR_INFILE_CANTOPEN;
 do{
  unsigned long metadata_type,metadata_size;
  unsigned char *readbuf;
  mpxp_uint32_t val=fbfs->get_be32(fbds);
  metadata_last = FLAC_METADATA_GET_LASTBIT(val);
  metadata_type = FLAC_METADATA_GET_TYPE(val);
  metadata_size = FLAC_METADATA_GET_SIZE(val);
  if(mb){
   if(flaci->metadata_block_count>=FLAC_METADATA_BLOCKS_MAX)
    return MPXPLAY_ERROR_INFILE_CANTOPEN;
   mb->block_header=val;
   mb->block_size_old=metadata_size+4;
   mb->block_offset=fbfs->ftell(fbds)-4;
   //fprintf(stdout,"l:%d t:%d s:%d brs:%d\n",metadata_last,metadata_type,metadata_size,blockread_status);
   switch(metadata_type){
    case FLAC_METADATA_TYPE_VORBISCOMMENT:
    case FLAC_METADATA_TYPE_PADDING:
     blockread_status++;
     break;
    default:
     if(blockread_status!=1) // we load the blocks between vorbiscomment and padding only
      break;
     if(metadata_size){
      mb->block_data=malloc(metadata_size+4);
      if(!mb->block_data)
       return MPXPLAY_ERROR_INFILE_MEMORY;
      PDS_PUTB_BEU32(mb->block_data,val);
      if(fbfs->fread(fbds,mb->block_data+4,metadata_size)!=metadata_size)
       goto err_out_ftg;
     }

   }
   mb++;
   flaci->metadata_block_count++;
  }
  if(metadata_size){
   switch(metadata_type){
    case FLAC_METADATA_TYPE_VORBISCOMMENT:
     readbuf=malloc(metadata_size+16);
     if(!readbuf)
      return MPXPLAY_ERROR_INFILE_MEMORY;
     if(fbfs->fread(fbds,readbuf,metadata_size)==metadata_size)
      if(vci)
       retcode=mpxplay_tagging_vorbiscomment_load_from_field(vci,readbuf,metadata_size);
      else
       retcode=mpxplay_tagging_vorbiscomment_read_to_mpxplay(miis,fbds,readbuf,metadata_size);
     free(readbuf);
     if(!mb)
      goto err_out_ftg;
     break;
    case FLAC_METADATA_TYPE_PADDING:
     if(fbfs->fseek(fbds,metadata_size,SEEK_CUR)<0)
      goto err_out_ftg;
     break;
    default:
     if(blockread_status!=1)
      if(fbfs->fseek(fbds,metadata_size,SEEK_CUR)<0)
       goto err_out_ftg;
     break;
   }
  }
 }while(!metadata_last);

 retcode=MPXPLAY_ERROR_OK;

err_out_ftg:

 return retcode;
}

static int INFLAC_tag_get(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis)
{
 return flac_read_vorbiscomment(fbfs,fbds,miis,NULL);
}

static struct flac_metadata_block_s *inflac_metadata_block_search(struct flac_demuxer_data_s *flaci,unsigned int metadata_type)
{
 struct flac_metadata_block_s *mb=flaci->metadata_blocks;
 unsigned int i=flaci->metadata_block_count;
 if(mb && i){
  do{
   unsigned int block_type=FLAC_METADATA_GET_TYPE(mb->block_header);
   if(block_type==metadata_type)
    return mb;
   mb++;
  }while(--i);
 }
 return NULL;
}

static struct flac_metadata_block_s *inflac_metadata_block_add(struct flac_demuxer_data_s *flaci,unsigned int metadata_type,unsigned long size)
{
 struct flac_metadata_block_s *mb=flaci->metadata_blocks;
 unsigned int i=flaci->metadata_block_count;
 mpxp_filesize_t offset=0;
 if(!mb)
  return NULL;
 if(i){ // always have to be
  do{
   unsigned int block_type=FLAC_METADATA_GET_TYPE(mb->block_header);
   if(block_type==metadata_type){
    mb->block_header=FLAC_METADATA_MAKE_HEAD(FLAC_METADATA_GET_LASTBIT(mb->block_header),metadata_type,size);
    return mb;
   }
   mb++;
  }while(--i);
  mb--;
  mb->block_header&=~(1<<31); // clear last flag
  if(mb->block_data)
   PDS_PUTB_BEU32(mb->block_data,mb->block_header); // in the datafield too
  offset=mb->block_offset+mb->block_size_old;
  mb++;
 }
 mb->block_header=FLAC_METADATA_MAKE_HEAD(1,metadata_type,size);
 mb->block_offset=offset;
 flaci->metadata_block_count++;
 return mb;
}

static int INFLAC_tag_put(struct mpxplay_filehand_buffered_func_s *fbfs,void *fbds,struct mpxplay_infile_info_s *miis,char *filename,unsigned long writetag_control)
{
 struct flac_demuxer_data_s *flaci;
 struct vorbiscomment_info_s *vci;
 struct flac_metadata_block_s *mb,*mb_vorbis,*mb_padding;
 long i,vorbiscomment_oldsize,vorbiscomment_chsize,padding_oldsize,padding_newsize;
 unsigned long blocksize;
 int retcode=MPXPLAY_ERROR_OK;
 void *tmpfile_filehand=NULL;
 char *metadata_field=NULL,readbuf[MPXPLAY_TAGGING_ID3V2_HEADSIZE];
 mpxp_int64_t len;
 //char sout[100];

 if(!fbfs->fopen(fbds,filename,O_RDWR|O_BINARY,0))
  return MPXPLAY_ERROR_INFILE_CANTOPEN;

 flaci=calloc(1,sizeof(struct flac_demuxer_data_s));
 if(!flaci){
  retcode=MPXPLAY_ERROR_INFILE_MEMORY;
  goto err_out_ftp;
 }
 miis->private_data=flaci;
 vci=&flaci->vci;
 flaci->metadata_blocks=calloc(FLAC_METADATA_BLOCKS_MAX,sizeof(struct flac_metadata_block_s));
 if(!flaci->metadata_blocks){
  retcode=MPXPLAY_ERROR_INFILE_MEMORY;
  goto err_out_ftp;
 }

 // !!! we just skip the ID3v2 header (we should update it too)
 if(fbfs->fread(fbds,readbuf,MPXPLAY_TAGGING_ID3V2_HEADSIZE)!=MPXPLAY_TAGGING_ID3V2_HEADSIZE){
  retcode=MPXPLAY_ERROR_INFILE_CANTOPEN;
  goto err_out_ftp;
 }
 i=mpxplay_tagging_id3v2_totalsize(readbuf);
 if((retcode=fbfs->fseek(fbds,i,SEEK_SET))<0)
  goto err_out_ftp;
 flaci->flacheaderbegin=i;

 if((retcode=flac_read_vorbiscomment(fbfs,fbds,miis,vci))!=MPXPLAY_ERROR_OK)
  goto err_out_ftp;
 if((retcode=mpxplay_tagging_vorbiscomment_update_from_mpxplay(miis,fbds,vci))!=MPXPLAY_ERROR_OK)
  goto err_out_ftp;
 mb_vorbis=inflac_metadata_block_search(flaci,FLAC_METADATA_TYPE_VORBISCOMMENT);
 vorbiscomment_oldsize=(mb_vorbis && (mb_vorbis->block_size_old>4))? (mb_vorbis->block_size_old-4):0;
 mb_padding=inflac_metadata_block_search(flaci,FLAC_METADATA_TYPE_PADDING);
 if((retcode=mpxplay_tagging_vorbiscomment_write_to_field(vci,&metadata_field,0xffffff,writetag_control|MPXPLAY_WRITETAG_CNTRL_NOPADD))!=MPXPLAY_ERROR_OK)
  goto err_out_ftp;

 vorbiscomment_chsize=(long)vci->metadata_size_new-vorbiscomment_oldsize;

 if(mb_vorbis && (vorbiscomment_chsize==0)){ // simply overwrite vorbiscomment block
  if(fbfs->fseek(fbds,(mb_vorbis->block_offset+4),SEEK_SET)!=(mb_vorbis->block_offset+4)){
   retcode=MPXPLAY_ERROR_MPXINBUF_SEEK_LOW;
   goto err_out_ftp;
  }
  if(fbfs->fwrite(fbds,metadata_field,vci->metadata_size_new)!=vci->metadata_size_new){
   retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
   goto err_out_ftp;
  }
 }else{
  unsigned int need_dup=1;
  padding_newsize=1024;
  padding_oldsize=(mb_padding && (mb_padding->block_size_old>4))? (mb_padding->block_size_old-4):0;
  //sprintf(sout,"mp:%8.8X po:%d pn:%d vc:%d",mb_padding,padding_oldsize,padding_newsize,vorbiscomment_chsize);
  //display_message(1,0,sout);
  //getch();
  if(padding_oldsize){
   if(padding_oldsize>=vorbiscomment_chsize){
    padding_newsize=padding_oldsize-vorbiscomment_chsize;
    need_dup=0;
   }
  }else if(vorbiscomment_chsize<=-4){
   padding_newsize=-vorbiscomment_chsize-4;
   need_dup=0;
  }

  if(need_dup){
   if(!(writetag_control&MPXPLAY_WRITETAG_CNTRL_DUPFILE)){
    retcode=MPXPLAY_ERROR_INFILE_WRITETAG_NOSPACE;
    goto err_out_ftp;
   }
  }

  if(!mb_vorbis) // couldn't test
   mb_vorbis=inflac_metadata_block_add(flaci,FLAC_METADATA_TYPE_VORBISCOMMENT,vci->metadata_size_new);
  if(!mb_padding)
   mb_padding=inflac_metadata_block_add(flaci,FLAC_METADATA_TYPE_PADDING,padding_newsize);

  mb_padding->block_data=malloc(padding_newsize+4);
  if(!mb_padding->block_data){
   retcode=MPXPLAY_ERROR_INFILE_MEMORY;
   goto err_out_ftp;
  }
  mb_padding->block_header=FLAC_METADATA_MAKE_HEAD(FLAC_METADATA_GET_LASTBIT(mb_padding->block_header),FLAC_METADATA_TYPE_PADDING,padding_newsize);
  PDS_PUTB_BEU32(mb_padding->block_data,mb_padding->block_header);
  pds_memset(mb_padding->block_data+4,0,padding_newsize);

  mb_vorbis->block_data=malloc(vci->metadata_size_new+4);
  if(!mb_vorbis->block_data){
   retcode=MPXPLAY_ERROR_INFILE_MEMORY;
   goto err_out_ftp;
  }
  mb_vorbis->block_header=FLAC_METADATA_MAKE_HEAD(FLAC_METADATA_GET_LASTBIT(mb_vorbis->block_header),FLAC_METADATA_TYPE_VORBISCOMMENT,vci->metadata_size_new);
  PDS_PUTB_BEU32(mb_vorbis->block_data,mb_vorbis->block_header);
  pds_memcpy(mb_vorbis->block_data+4,metadata_field,vci->metadata_size_new);

  mb=flaci->metadata_blocks;

  if(!need_dup){ // shrink/expand flac-padding
   if(fbfs->fseek(fbds,mb->block_offset,SEEK_SET)<0){ // !!! offset is invalid from vorbiscomment to padding after their modifications
    retcode=MPXPLAY_ERROR_INFILE_EOF;
    goto err_out_ftp;
   }
   i=flaci->metadata_block_count;
   do{
    blocksize=FLAC_METADATA_GET_SIZE(mb->block_header)+4;
    if(mb->block_data){
     if(fbfs->fwrite(fbds,mb->block_data,blocksize)!=blocksize){
      retcode=MPXPLAY_ERROR_FILEHAND_CANTWRITE;
      goto err_out_ftp;
     }
    }else if(fbfs->fseek(fbds,blocksize,SEEK_CUR)<0){
     retcode=MPXPLAY_ERROR_INFILE_EOF;
     goto err_out_ftp;
    }
    mb++;
   }while(--i);

  }else{ // duplicate file

   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPOPEN,&tmpfile_filehand,NULL))!=MPXPLAY_ERROR_OK)
    goto err_out_ftp;
   if(fbfs->fseek(fbds,0,SEEK_SET)!=0){
    retcode=MPXPLAY_ERROR_MPXINBUF_SEEK_LOW;
    goto err_out_ftp;
   }
   if(mb->block_offset){ // copy data before 1st metadata block
    len=mb->block_offset;
    if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&len))!=MPXPLAY_ERROR_OK)
     goto err_out_ftp;
   }

   i=flaci->metadata_block_count;
   do{
    blocksize=FLAC_METADATA_GET_SIZE(mb->block_header)+4;
    if(mb->block_data){
     if((retcode=miis->control_cb(tmpfile_filehand,MPXPLAY_CFGFUNCNUM_INFILE_FILE_WRITE_BYTES,mb->block_data,&blocksize))!=blocksize)
      goto err_out_ftp;
    }else if(fbfs->fseek(fbds,mb->block_offset,SEEK_SET)!=mb->block_offset){
     retcode=MPXPLAY_ERROR_INFILE_EOF;
     goto err_out_ftp;
    }else{
     if(fbfs->fseek(fbds,4,SEEK_CUR)<0){
      retcode=MPXPLAY_ERROR_INFILE_EOF;
      goto err_out_ftp;
     }
     PDS_PUTB_BEU32(readbuf,mb->block_header);
     len=4;
     if((retcode=miis->control_cb(tmpfile_filehand,MPXPLAY_CFGFUNCNUM_INFILE_FILE_WRITE_BYTES,readbuf,&len))!=len)
      goto err_out_ftp;
     len=blocksize-4;
     if(len>0)
      if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&len))!=MPXPLAY_ERROR_OK)
       goto err_out_ftp;
    }
    mb++;
   }while(--i);

   mb--;
   if(mb->block_data && mb->block_offset){
    if(fbfs->fseek(fbds,mb->block_offset+mb->block_size_old,SEEK_SET)<0){
     retcode=MPXPLAY_ERROR_INFILE_EOF;
     goto err_out_ftp;
    }
   }
   len=-1;
   if((retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_COPY,tmpfile_filehand,&len))!=MPXPLAY_ERROR_OK)
    goto err_out_ftp;
   retcode=miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_TMPXCHCLOSE,&tmpfile_filehand,NULL);
  }
 }
 if(writetag_control&MPXPLAY_WRITETAG_CNTRL_TRIMTAGS){ // !!! update to real tags
  miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAGS_CLEAR,NULL,NULL); // bullshit
  mpxplay_tagging_vorbiscomment_read_to_mpxplay(miis,fbds,metadata_field,vci->metadata_size_new);
 }

err_out_ftp:
 fbfs->fclose(fbds);
 if(flaci){
  if(metadata_field)
   free(metadata_field);
  mpxplay_tagging_vorbiscomment_free(vci);
  if(tmpfile_filehand)
   miis->control_cb(fbds,MPXPLAY_CFGFUNCNUM_INFILE_FILE_CLOSE,&tmpfile_filehand,NULL);
  free(flaci);
  miis->private_data=NULL;
 }
 return retcode;
}

//--------------------------------------------------------------------------

struct mpxplay_infile_func_s IN_FLAC_funcs={
 0,
 NULL,
 NULL,
 &INFLAC_infile_open,
 &INFLAC_infile_close,
 &INRAWAU_infile_decode,
 &INFLAC_infile_fseek,
 NULL,
 &INFLAC_tag_get,
 &INFLAC_tag_put,
 NULL,
 {"FLAC","FLA","FLC",NULL}
};

#endif // MPXPLAY_LINK_INFILE_FLAC
