/*
 * scanner.c
 * Copyright 2012-2016 John Lindgren
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions, and the following disclaimer in the documentation
 *    provided with the distribution.
 *
 * This software is provided "as is" and without any warranty, express or
 * implied. In no event shall the authors be liable for any damages arising from
 * the use of this software.
 */

#include "scanner.h"

#include <glib.h>  /* for GThreadPool */

#include "audstrings.h"
#include "cue-cache.h"
#include "i18n.h"
#include "internal.h"
#include "plugins.h"
#include "probe.h"
#include "tuple.h"
#include "vfs.h"

static GThreadPool * pool;

ScanRequest::ScanRequest (const String & filename, int flags, Callback callback,
 PluginHandle * decoder, Tuple && tuple) :
    filename (filename),
    flags (flags),
    callback (callback),
    decoder (decoder),
    tuple (std::move (tuple)),
    ip (nullptr)
{
    /* If this is a cuesheet entry (and it has not already been loaded), capture
     * a reference to the cache immediately.  During a playlist scan, requests
     * have overlapping lifecycles--each new ScanRequest is created by the
     * callback of the previous request--so the cached cuesheet persists as long
     * as consecutive playlist entries reference it. */
    if (! this->tuple.valid () && is_cuesheet_entry (filename))
        cue_cache.capture (new CueCacheRef (strip_subtune (filename)));
}

void ScanRequest::read_cuesheet_entry ()
{
    for (auto & item : cue_cache->load ())
    {
        if (item.filename == filename)
        {
            decoder = item.decoder;
            tuple = item.tuple.ref ();
            break;
        }
    }
}

void ScanRequest::run ()
{
    /* load cuesheet entry (possibly cached) */
    if (cue_cache)
        read_cuesheet_entry ();

    /* for a cuesheet entry, determine the source filename */
    String audio_file = tuple.get_str (Tuple::AudioFile);
    if (! audio_file)
        audio_file = filename;

    bool need_tuple = (flags & SCAN_TUPLE) && ! tuple.valid ();
    bool need_image = (flags & SCAN_IMAGE);

    if (! decoder)
        decoder = aud_file_find_decoder (audio_file, false, file, & error);
    if (! decoder)
        goto err;

    if (need_tuple || need_image)
    {
        if (! (ip = load_input_plugin (decoder, & error)))
            goto err;

        Index<char> * pimage = need_image ? & image_data : nullptr;
        if (! aud_file_read_tag (audio_file, decoder, file, tuple, pimage, & error))
            goto err;

        if ((flags & SCAN_IMAGE) && ! image_data.len ())
            image_file = art_search (audio_file);
    }

    /* rewind/reopen the input file */
    if ((flags & SCAN_FILE))
        open_input_file (audio_file, "r", ip, file, & error);
    else
    {
    err:
        /* close file if not needed or if an error occurred */
        file = VFSFile ();
    }

    callback (this);
}

static void scan_worker (void * data, void *)
{
    ((ScanRequest *) data)->run ();
    delete (ScanRequest *) data;
}

void scanner_init ()
{
    pool = g_thread_pool_new (scan_worker, nullptr, SCAN_THREADS, false, nullptr);
}

void scanner_request (ScanRequest * request)
{
    g_thread_pool_push (pool, request, nullptr);
}

void scanner_cleanup ()
{
    g_thread_pool_free (pool, false, true);
}
