////////////////////////////////////////////////////////////////////////////
//                           **** WAVPACK ****                            //
//                  Hybrid Lossless Wavefile Compressor                   //
//              Copyright (c) 1998 - 2013 Conifer Software.               //
//                          All Rights Reserved.                          //
//      Distributed under the BSD Software License (see license.txt)      //
////////////////////////////////////////////////////////////////////////////

// unpack_seek.c

// This module provides the high-level API for unpacking audio data from
// a specific sample index (i.e., seeking).

#ifndef NO_SEEKING

#include <stdlib.h>
#include <string.h>

#include "wavpack_local.h"

///////////////////////////// executable code ////////////////////////////////

static int64_t find_sample (WavpackContext *wpc, void *infile, int64_t header_pos, int64_t sample);

// Seek to the specified sample index, returning TRUE on success. Note that
// files generated with version 4.0 or newer will seek almost immediately.
// Older files can take quite long if required to seek through unplayed
// portions of the file, but will create a seek map so that reverse seeks
// (or forward seeks to already scanned areas) will be very fast. After a
// FALSE return the file should not be accessed again (other than to close
// it); this is a fatal error.

int WavpackSeekSample (WavpackContext *wpc, uint32_t sample)
{
    return WavpackSeekSample64 (wpc, sample);
}

int WavpackSeekSample64 (WavpackContext *wpc, int64_t sample)
{
    WavpackStream *wps = wpc->streams ? wpc->streams [0] : NULL;
    uint32_t bcount, samples_to_skip, samples_to_decode = 0;
    int stream_index = 0;
    int32_t *buffer;

    if (wpc->total_samples == -1 || sample >= wpc->total_samples ||
        !wpc->reader->can_seek (wpc->wv_in) || (wpc->open_flags & OPEN_STREAMING) ||
        (wpc->wvc_flag && !wpc->reader->can_seek (wpc->wvc_in)))
            return FALSE;

#ifdef ENABLE_LEGACY
    if (wpc->stream3)
        return seek_sample3 (wpc, (uint32_t) sample);
#endif

#ifdef ENABLE_DSD
    if (wpc->decimation_context) {      // the decimation code needs some context to be sample accurate
        if (sample < 16) {
            samples_to_decode = (uint32_t) sample;
            sample = 0;
        }
        else {
            samples_to_decode = 16;
            sample -= 16;
        }
    }
#endif

    if (!wps->wphdr.block_samples || !(wps->wphdr.flags & INITIAL_BLOCK) || sample < GET_BLOCK_INDEX (wps->wphdr) ||
        sample >= GET_BLOCK_INDEX (wps->wphdr) + wps->wphdr.block_samples) {

            free_streams (wpc);
            wpc->filepos = find_sample (wpc, wpc->wv_in, wpc->filepos, sample);

            if (wpc->filepos == -1)
                return FALSE;

            if (wpc->wvc_flag) {
                wpc->file2pos = find_sample (wpc, wpc->wvc_in, 0, sample);

                if (wpc->file2pos == -1)
                    return FALSE;
            }
    }

    if (!wps->blockbuff) {
        wpc->reader->set_pos_abs (wpc->wv_in, wpc->filepos);
        wpc->reader->read_bytes (wpc->wv_in, &wps->wphdr, sizeof (WavpackHeader));
        WavpackLittleEndianToNative (&wps->wphdr, WavpackHeaderFormat);

        if ((wps->wphdr.ckSize & 1) || wps->wphdr.ckSize < 24 || wps->wphdr.ckSize >= 1024 * 1024) {
            free_streams (wpc);
            return FALSE;
        }

        wps->blockbuff = (unsigned char *)malloc (wps->wphdr.ckSize + 8);
        memcpy (wps->blockbuff, &wps->wphdr, sizeof (WavpackHeader));

        if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + sizeof (WavpackHeader), wps->wphdr.ckSize - 24) !=
            wps->wphdr.ckSize - 24) {
                free_streams (wpc);
                return FALSE;
        }

        // render corrupt blocks harmless
        if (!WavpackVerifySingleBlock (wps->blockbuff, !(wpc->open_flags & OPEN_NO_CHECKSUM))) {
            wps->wphdr.ckSize = sizeof (WavpackHeader) - 8;
            wps->wphdr.block_samples = 0;
            memcpy (wps->blockbuff, &wps->wphdr, 32);
        }

        SET_BLOCK_INDEX (wps->wphdr, GET_BLOCK_INDEX (wps->wphdr) - wpc->initial_index);
        memcpy (wps->blockbuff, &wps->wphdr, sizeof (WavpackHeader));
        wps->init_done = FALSE;

        if (wpc->wvc_flag) {
            wpc->reader->set_pos_abs (wpc->wvc_in, wpc->file2pos);
            wpc->reader->read_bytes (wpc->wvc_in, &wps->wphdr, sizeof (WavpackHeader));
            WavpackLittleEndianToNative (&wps->wphdr, WavpackHeaderFormat);

            if ((wps->wphdr.ckSize & 1) || wps->wphdr.ckSize < 24 || wps->wphdr.ckSize >= 1024 * 1024) {
                free_streams (wpc);
                return FALSE;
            }

            wps->block2buff = (unsigned char *)malloc (wps->wphdr.ckSize + 8);
            memcpy (wps->block2buff, &wps->wphdr, sizeof (WavpackHeader));

            if (wpc->reader->read_bytes (wpc->wvc_in, wps->block2buff + sizeof (WavpackHeader), wps->wphdr.ckSize - 24) !=
                wps->wphdr.ckSize - 24) {
                    free_streams (wpc);
                    return FALSE;
            }

            // render corrupt blocks harmless
            if (!WavpackVerifySingleBlock (wps->block2buff, !(wpc->open_flags & OPEN_NO_CHECKSUM))) {
                wps->wphdr.ckSize = sizeof (WavpackHeader) - 8;
                wps->wphdr.block_samples = 0;
                memcpy (wps->block2buff, &wps->wphdr, 32);
            }

            SET_BLOCK_INDEX (wps->wphdr, GET_BLOCK_INDEX (wps->wphdr) - wpc->initial_index);
            memcpy (wps->block2buff, &wps->wphdr, sizeof (WavpackHeader));
        }

        if (!wps->init_done && !unpack_init (wpc, stream_index)) {
            free_streams (wpc);
            return FALSE;
        }

        wps->init_done = TRUE;
    }

    while (!wpc->reduced_channels && !(wps->wphdr.flags & FINAL_BLOCK)) {
        if (++stream_index == wpc->num_streams) {

            if (wpc->num_streams == wpc->max_streams) {
                free_streams (wpc);
                return FALSE;
            }

            wpc->streams = (WavpackStream **)realloc (wpc->streams, (wpc->num_streams + 1) * sizeof (wpc->streams [0]));
            wps = wpc->streams [wpc->num_streams] = (WavpackStream *)calloc (1, sizeof (WavpackStream));
            wps->stream_index = wpc->num_streams++;
            wps->wpc = wpc;
            bcount = read_next_header (wpc->reader, wpc->wv_in, &wps->wphdr);

            if (bcount == (uint32_t) -1) {
                free_streams (wpc);
                return FALSE;
            }

            wps->blockbuff = (unsigned char *)malloc (wps->wphdr.ckSize + 8);
            memcpy (wps->blockbuff, &wps->wphdr, 32);

            if (wpc->reader->read_bytes (wpc->wv_in, wps->blockbuff + 32, wps->wphdr.ckSize - 24) !=
                wps->wphdr.ckSize - 24) {
                    free_streams (wpc);
                    return FALSE;
            }

            // render corrupt blocks harmless
            if (!WavpackVerifySingleBlock (wps->blockbuff, !(wpc->open_flags & OPEN_NO_CHECKSUM))) {
                wps->wphdr.ckSize = sizeof (WavpackHeader) - 8;
                wps->wphdr.block_samples = 0;
                memcpy (wps->blockbuff, &wps->wphdr, 32);
            }

            wps->init_done = FALSE;

            if (wpc->wvc_flag && !read_wvc_block (wpc, stream_index)) {
                free_streams (wpc);
                return FALSE;
            }

            if (!wps->init_done && !unpack_init (wpc, stream_index)) {
                free_streams (wpc);
                return FALSE;
            }

            wps->init_done = TRUE;
        }
        else
            wps = wpc->streams [stream_index];
    }

    if (sample < wps->sample_index) {
        for (stream_index = 0; stream_index < wpc->num_streams; stream_index++)
            if (!unpack_init (wpc, stream_index))
                return FALSE;
            else
                wpc->streams [stream_index]->init_done = TRUE;
    }

    samples_to_skip = (uint32_t) (sample - wps->sample_index);

    if (samples_to_skip > 131072) {
        free_streams (wpc);
        return FALSE;
    }

    if (samples_to_skip) {
        buffer = (int32_t *)malloc (samples_to_skip * 8);

        for (stream_index = 0; stream_index < wpc->num_streams; stream_index++)
#ifdef ENABLE_DSD
            if (wpc->streams [stream_index]->wphdr.flags & DSD_FLAG)
                unpack_dsd_samples (wpc->streams [stream_index], buffer, samples_to_skip);
            else
#endif
                unpack_samples (wpc->streams [stream_index], buffer, samples_to_skip);

        free (buffer);
    }

#ifdef ENABLE_DSD
    if (wpc->decimation_context)
        decimate_dsd_reset (wpc->decimation_context);

    if (samples_to_decode) {
        buffer = (int32_t *)calloc (1, samples_to_decode * wpc->config.num_channels * 4);

        if (buffer) {
            WavpackUnpackSamples (wpc, buffer, samples_to_decode);
            free (buffer);
        }
    }
#endif

    return TRUE;
}

// Find a valid WavPack header, searching either from the current file position
// (or from the specified position if not -1) and store it (endian corrected)
// at the specified pointer. The return value is the exact file position of the
// header, although we may have actually read past it. Because this function
// is used for seeking to a specific audio sample, it only considers blocks
// that contain audio samples for the initial stream to be valid.

#define BUFSIZE 4096

static int64_t find_header (WavpackStreamReader64 *reader, void *id, int64_t filepos, WavpackHeader *wphdr)
{
    unsigned char *buffer = (unsigned char *)malloc (BUFSIZE), *sp = buffer, *ep = buffer;

    if (filepos != (uint32_t) -1 && reader->set_pos_abs (id, filepos)) {
        free (buffer);
        return -1;
    }

    while (1) {
        int bleft;

        if (sp < ep) {
            bleft = (int)(ep - sp);
            memmove (buffer, sp, bleft);
            ep -= (sp - buffer);
            sp = buffer;
        }
        else {
            if (sp > ep)
                if (reader->set_pos_rel (id, (int32_t)(sp - ep), SEEK_CUR)) {
                    free (buffer);
                    return -1;
                }

            sp = ep = buffer;
            bleft = 0;
        }

        ep += reader->read_bytes (id, ep, BUFSIZE - bleft);

        if (ep - sp < 32) {
            free (buffer);
            return -1;
        }

        while (sp + 32 <= ep)
            if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' &&
                !(*++sp & 1) && sp [2] < 16 && !sp [3] && (sp [2] || sp [1] || *sp >= 24) && sp [5] == 4 &&
                sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff) && sp [18] < 3 && !sp [19]) {
                    memcpy (wphdr, sp - 4, sizeof (*wphdr));
                    WavpackLittleEndianToNative (wphdr, WavpackHeaderFormat);

                    if (wphdr->block_samples && (wphdr->flags & INITIAL_BLOCK)) {
                        int64_t retpos = reader->get_pos (id) - (ep - sp + 4);
                        free (buffer);
                        return retpos;
                    }

                    if (wphdr->ckSize > 1024)
                        sp += wphdr->ckSize - 1024;
            }
    }
}

// Find the WavPack block that contains the specified sample. If "header_pos"
// is zero, then no information is assumed except the total number of samples
// in the file and its size in bytes. If "header_pos" is non-zero then we
// assume that it is the file position of the valid header image contained in
// the first stream and we can limit our search to either the portion above
// or below that point. If a .wvc file is being used, then this must be called
// for that file also.

static int64_t find_sample (WavpackContext *wpc, void *infile, int64_t header_pos, int64_t sample)
{
    WavpackStream *wps = wpc->streams [0];
    int64_t file_pos1 = 0, file_pos2 = wpc->reader->get_length (infile);
    int64_t sample_pos1 = 0, sample_pos2 = wpc->total_samples;
    double ratio = 0.96;
    int file_skip = 0;

    if (sample >= wpc->total_samples)
        return -1;

    if (header_pos && wps->wphdr.block_samples) {
        if (GET_BLOCK_INDEX (wps->wphdr) > sample) {
            sample_pos2 = GET_BLOCK_INDEX (wps->wphdr);
            file_pos2 = header_pos;
        }
        else if (GET_BLOCK_INDEX (wps->wphdr) + wps->wphdr.block_samples <= sample) {
            sample_pos1 = GET_BLOCK_INDEX (wps->wphdr);
            file_pos1 = header_pos;
        }
        else
            return header_pos;
    }

    while (1) {
        double bytes_per_sample;
        int64_t seek_pos;

        bytes_per_sample = (double) file_pos2 - file_pos1;
        bytes_per_sample /= sample_pos2 - sample_pos1;
        seek_pos = file_pos1 + (file_skip ? 32 : 0);
        seek_pos += (int64_t)(bytes_per_sample * (sample - sample_pos1) * ratio);
        seek_pos = find_header (wpc->reader, infile, seek_pos, &wps->wphdr);

        if (seek_pos != (int64_t) -1)
            SET_BLOCK_INDEX (wps->wphdr, GET_BLOCK_INDEX (wps->wphdr) - wpc->initial_index);

        if (seek_pos == (int64_t) -1 || seek_pos >= file_pos2) {
            if (ratio > 0.0) {
                if ((ratio -= 0.24) < 0.0)
                    ratio = 0.0;
            }
            else
                return -1;
        }
        else if (GET_BLOCK_INDEX (wps->wphdr) > sample) {
            sample_pos2 = GET_BLOCK_INDEX (wps->wphdr);
            file_pos2 = seek_pos;
        }
        else if (GET_BLOCK_INDEX (wps->wphdr) + wps->wphdr.block_samples <= sample) {

            if (seek_pos == file_pos1)
                file_skip = 1;
            else {
                sample_pos1 = GET_BLOCK_INDEX (wps->wphdr);
                file_pos1 = seek_pos;
            }
        }
        else
            return seek_pos;
    }
}

#endif

