#ifdef CONFIG_MUSIC_ENABLE

#include <includes.h>
#include "ff.h"
#include "app_serv.h"
#include "music_tsk.h"
#include "player.h"
#include "data.h"
#include "datadesc.h"
#include "sdcard.h"

static FATFS FatFs;		/* FatFs work area needed for each volume */
static FIL Fil;			/* File object needed for each open file */

CPU_INT32U fat_status;
CPU_INT08U music_status;
CPU_INT08U music_cansel;

#define FILE_BUFFER_SIZE 512
/* How many transferred bytes between collecting data.
   A value between 1-8 KiB is typically a good value.
   If REPORT_ON_SCREEN is defined, a report is given on screen each time
   data is collected. */
#define REPORT_INTERVAL 4096
#define REPORT_INTERVAL_MIDI 512

static u_int8 playBuf[FILE_BUFFER_SIZE];

OS_STK    MusicTaskStk[MUSIC_TASK_STK_SIZE];
OS_EVENT *MusicQuery = NULL;
void     *MusicQueryTbl[MUSIC_QUERY_LEN];

///
int IsMusicPlaying(void)
{
    if (music_status == MUSIC_STATUS_PLAYING) return 1;
    return 0;
}

///
void MusicCanselPlay(void)
{
    music_cansel = 1;
}

///
int GetMusicEvent(int* event)
{
  CPU_INT08U err = 0; 
  int evt  = (int)OSQPend(MusicQuery, 1, &err);
  if (err != 0) return 0;
  *event = evt;
  return 1;  
}   

/// 
void PostMusicEvent(int event)
{
  OSQPost(MusicQuery, (void *)event);
}

enum AudioFormat {
  afUnknown,
  afRiff,
  afOggVorbis,
  afMp1,
  afMp2,
  afMp3,
  afAacMp4,
  afAacAdts,
  afAacAdif,
  afFlac,
  afWma,
  afMidi,
} audioFormat = afUnknown;

const char *afName[] = {
  "unknown",
  "RIFF",
  "Ogg",
  "MP1",
  "MP2",
  "MP3",
  "AAC MP4",
  "AAC ADTS",
  "AAC ADIF",
  "FLAC",
  "WMA",
  "MIDI",
};

enum PlayerStates {
  psPlayback = 0,
  psUserRequestedCancel,
  psCancelSentToVS10xx,
  psStopped
} playerState;


int playFile(FIL *readFp, uint16_t volume) 
{
    UINT bytesInBuffer;        // How many bytes in buffer left
    u_int32 pos=0;                // File position
    int endFillByte = 0;          // What byte value to send after file
    int endFillBytes = 2050;      // How many of those to send
    long nextReportPos=0;         // File pointer where to next collect/report
    int i;
    
    playerState = psPlayback;             // Set state to normal playback
    
    WriteSci(SCI_DECODE_TIME, 0);         // Reset DECODE_TIME

    // volume = 0..255
    WriteSci(SCI_VOL, volume*0x101);
    
    // Main playback loop
    
    while ((f_read(readFp, playBuf, FILE_BUFFER_SIZE, &bytesInBuffer) == FR_OK) && (bytesInBuffer > 0) &&
        (playerState != psStopped))
    {
        u_int8 *bufP = playBuf;
    
        while (bytesInBuffer && (playerState != psStopped))
        {
            int t = min(SDI_MAX_TRANSFER_SIZE, bytesInBuffer);
            
            // This is the heart of the algorithm: on the following line
            // actual audio data gets sent to VS10xx.
            if (WriteSdi(bufP, t) != 0) return -1;
            
            bufP += t;
            bytesInBuffer -= t;
            pos += t;
            
            // If the user has requested cancel, set VS10xx SM_CANCEL bit
            if (playerState == psUserRequestedCancel)
            {
                unsigned short oldMode;
                playerState = psCancelSentToVS10xx;
                oldMode = ReadSci(SCI_MODE);
                WriteSci(SCI_MODE, oldMode | SM_CANCEL);
            }
            
            // If VS10xx SM_CANCEL bit has been set, see if it has gone
            // through. If it is, it is time to stop playback.
            if (playerState == psCancelSentToVS10xx) 
            {
                unsigned short mode = ReadSci(SCI_MODE);
                if (!(mode & SM_CANCEL)) 
                {
                    playerState = psStopped;
                }
            }
            
            // If playback is going on as normal, see if we need to collect and
            // possibly report
            if (playerState == psPlayback && pos >= nextReportPos)
            {
                nextReportPos += (audioFormat == afMidi || audioFormat == afUnknown) ? REPORT_INTERVAL_MIDI : REPORT_INTERVAL;
                /* It is important to collect endFillByte while still in normal
                playback. If we need to later cancel playback or run into any
                trouble with e.g. a broken file, we need to be able to repeatedly
                send this byte until the decoder has been able to exit. */
                endFillByte = ReadVS10xxMem(PAR_END_FILL_BYTE);
            }
        } // if (playerState == psPlayback && pos >= nextReportPos)
    
    
        if ((music_cansel) &&(playerState == psPlayback))
        {
            playerState = psUserRequestedCancel;
        }
    
    } // while ((bytesInBuffer = fread(...)) > 0 && playerState != psStopped)
    
    // Earlier we collected endFillByte. Now, just in case the file was
    // broken, or if a cancel playback command has been given, write
    // lots of endFillBytes.
    memset(playBuf, endFillByte, sizeof(playBuf));
    for (i=0; i<endFillBytes; i+=SDI_MAX_TRANSFER_SIZE)
    {
        if (WriteSdi(playBuf, SDI_MAX_TRANSFER_SIZE) != 0) return -2;
    }
    
    // If the file actually ended, and playback cancellation was not
    // done earlier, do it now.
    if (playerState == psPlayback)
    {
        unsigned short oldMode = ReadSci(SCI_MODE);
        WriteSci(SCI_MODE, oldMode | SM_CANCEL);
        while (ReadSci(SCI_MODE) & SM_CANCEL) WriteSdi(playBuf, 2);
    }
    
    // That's it. Now we've played the file as we should, and left VS10xx
    // in a stable state. It is now safe to call this function again for
    // the next song, and again, and again...
    return 0;
}

/// поиск в директории имени файла, начинающегося с заданного индекса
FRESULT find_file(char* dirpath, UINT index, char* filename)
{
    FRESULT res;
    DIR dir;
    static FILINFO fno;

    res = f_opendir(&dir, dirpath);                           // Open the directory
    if (res == FR_OK)
    {
        char index_str[4];
        sprintf(index_str, "%03d", index);
        for (;;) 
        {
            res = f_readdir(&dir, &fno);                   // Read a directory item
            if (res != FR_OK || fno.fname[0] == 0) break;  // Break on error or end of dir
            if (!(fno.fattrib & AM_DIR))
            {
                if (strncmp(fno.fname, index_str, 3) == 0)
                {
                    strcpy(filename, fno.fname);
                    f_closedir(&dir);
                    return FR_OK;
                }
            }
        }
        f_closedir(&dir);
        res = FR_NO_FILE;
    }

    return res;
}

///
void MusicTask(void *p_arg)
{
    CPU_INT32U time_stamp;
    int event;

    if (f_mount(&FatFs, "", 1) == FR_OK)
    {
        fat_status = FAT_STATUS_OK;
    }
    else
    {
        fat_status = FAT_STATUS_EMPTY;
    }

    time_stamp = OSTimeGet();
    while (1)
    {
        if (fat_status == FAT_STATUS_OK)
        {
            CPU_INT32U sound_type;
            GetData(&SoundTypeDesc, &sound_type, 0, DATA_FLAG_SYSTEM_INDEX);
            if (sound_type != 1)
            {
                while (GetMusicEvent(&event)); // события пропускаем
                file_menu_cmd = 0;
                OSTimeDly(10000);
                continue;
            }
            
            if (GetMusicEvent(&event))
            {
                if (event >= MUSIC_EVENT_PLAY_FILE)
                {
                    music_cansel = 0;
                    if (!player_connected()) player_init();
                    if (player_connected())
                    {
                        char filepath[12 + 1];
                        if (find_file("/audio", event - MUSIC_EVENT_PLAY_FILE, filepath) == FR_OK)
                        {
                            if (f_chdir("/audio") == FR_OK)
                            {
                                music_status = MUSIC_STATUS_PLAYING;
                                if (f_open(&Fil, filepath, FA_READ | FA_OPEN_EXISTING) == FR_OK)
                                {
                                    CPU_INT32U volume100;
                                    uint16_t volume;
                                    GetData(&SoundVolumeDesc, &volume100, 0, DATA_FLAG_SYSTEM_INDEX);
                                    volume = (255 * (100 - volume100)) / 100;
                                    playFile(&Fil, volume);
                                    f_close(&Fil);
                                }
                                f_chdir("/");
                                music_status = MUSIC_STATUS_STOPPED;
                            }
                        }
                    }
                    file_menu_cmd = 0;
                }
            }
            else
            {
                if (OSTimeGet() - time_stamp > 10000)
                {
                    time_stamp = OSTimeGet();
                    if (f_open(&Fil, "fstab.tmp", FA_READ | FA_OPEN_EXISTING) == FR_OK)
                    {
                        UINT rbytes;
                        if ((f_read(&Fil, playBuf, 10, &rbytes) == FR_OK) && (rbytes == 10))
                        {
                            f_close(&Fil);
                        }
                        else
                        {
                            f_close(&Fil);
                            fat_status = FAT_STATUS_EMPTY;
                            f_unmount("");
                        }
                    }
                    else
                    {
                        fat_status = FAT_STATUS_EMPTY;
                        f_unmount("");
                    }
                }
                OSTimeDly(10);
            }
        }
        else
        {
            while (GetMusicEvent(&event)); // события пропускаем
            if (OSTimeGet() - time_stamp > 10000)
            {
                time_stamp = OSTimeGet();
                sd_init();
                if (f_mount(&FatFs, "", 1) == FR_OK)
                {
                    UINT rbytes;
                    if (f_open(&Fil, "fstab.tmp", FA_READ | FA_OPEN_EXISTING) == FR_OK)
                    {
                        if ((f_read(&Fil, playBuf, 10, &rbytes) == FR_OK) && (rbytes == 10))
                        {
                            if (f_close(&Fil) == FR_OK)
                            {
                                fat_status = FAT_STATUS_OK;
                            }
                        }
                    }
                    if (fat_status != FAT_STATUS_OK)
                    {
                        if (f_open(&Fil, "fstab.tmp", FA_WRITE | FA_CREATE_NEW) == FR_OK)
                        {
                            if ((f_read(&Fil, playBuf, 10, &rbytes) == FR_OK) && (rbytes == 10))
                            {
                                if (f_close(&Fil) == FR_OK)
                                {
                                    fat_status = FAT_STATUS_OK;
                                }
                            }
                            else
                            {
                                UINT wbytes;
                                if ((f_write(&Fil, playBuf, 10, &wbytes) == FR_OK) && (wbytes == 10))
                                {
                                    if (f_close(&Fil) == FR_OK)
                                    {
                                        fat_status = FAT_STATUS_OK;
                                    }
                                }
                                else
                                {
                                    f_close(&Fil);
                                }
                            }
                        }
                        else
                        {
                            f_unmount("");
                        }
                    }
                }
                else
                {
                    fat_status = FAT_STATUS_EMPTY;
                }
            }
            else
            {
                OSTimeDly(10);
            }
        }
    }
}

///
void InitMusicTask(void)
{
    if (MusicQuery == NULL)
    {    
        MusicQuery = OSQCreate(&MusicQueryTbl[0], MUSIC_QUERY_LEN);
    }
    fat_status = FAT_STATUS_NOCONN;
    music_status = MUSIC_STATUS_STOPPED;
    OSTaskCreate(MusicTask, (void *)0, (OS_STK *)&MusicTaskStk[MUSIC_TASK_STK_SIZE-1], MUSIC_TASK_PRIO);
}

#endif
