/*****************************************************************************
 * preparser.c
 *****************************************************************************
 * Copyright © 2017-2017 VLC authors and VideoLAN
 * $Id: 661b2122e14a10340eefbb054dabfebd4fc7cc9b $
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * 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. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>

#include "misc/background_worker.h"
#include "input/input_interface.h"
#include "input/input_internal.h"
#include "preparser.h"
#include "fetcher.h"

struct playlist_preparser_t
{
    vlc_object_t* owner;
    playlist_fetcher_t* fetcher;
    struct background_worker* worker;
    atomic_bool deactivated;
};

static int InputEvent( vlc_object_t* obj, const char* varname,
    vlc_value_t old, vlc_value_t cur, void* worker )
{
    VLC_UNUSED( obj ); VLC_UNUSED( varname ); VLC_UNUSED( old );

    if( cur.i_int == INPUT_EVENT_DEAD )
        background_worker_RequestProbe( worker );

    return VLC_SUCCESS;
}

static int PreparserOpenInput( void* preparser_, void* item_, void** out )
{
    playlist_preparser_t* preparser = preparser_;

    input_thread_t* input = input_CreatePreparser( preparser->owner, item_ );
    if( !input )
    {
        input_item_SignalPreparseEnded( item_, ITEM_PREPARSE_FAILED );
        return VLC_EGENERIC;
    }

    var_AddCallback( input, "intf-event", InputEvent, preparser->worker );
    if( input_Start( input ) )
    {
        var_DelCallback( input, "intf-event", InputEvent, preparser->worker );
        input_Close( input );
        input_item_SignalPreparseEnded( item_, ITEM_PREPARSE_FAILED );
        return VLC_EGENERIC;
    }

    *out = input;
    return VLC_SUCCESS;
}

static int PreparserProbeInput( void* preparser_, void* input_ )
{
    int state = input_GetState( input_ );
    return state == END_S || state == ERROR_S;
    VLC_UNUSED( preparser_ );
}

static void PreparserCloseInput( void* preparser_, void* input_ )
{
    playlist_preparser_t* preparser = preparser_;
    input_thread_t* input = input_;
    input_item_t* item = input_priv(input)->p_item;

    var_DelCallback( input, "intf-event", InputEvent, preparser->worker );

    int status;
    switch( input_GetState( input ) )
    {
        case END_S:
            status = ITEM_PREPARSE_DONE;
            break;
        case ERROR_S:
            status = ITEM_PREPARSE_FAILED;
            break;
        default:
            status = ITEM_PREPARSE_TIMEOUT;
    }

    input_Stop( input );
    input_Close( input );

    if( preparser->fetcher )
    {
        if( !playlist_fetcher_Push( preparser->fetcher, item, 0, status ) )
            return;
    }

    input_item_SetPreparsed( item, true );
    input_item_SignalPreparseEnded( item, status );
}

static void InputItemRelease( void* item ) { input_item_Release( item ); }
static void InputItemHold( void* item ) { input_item_Hold( item ); }

playlist_preparser_t* playlist_preparser_New( vlc_object_t *parent )
{
    playlist_preparser_t* preparser = malloc( sizeof *preparser );

    struct background_worker_config conf = {
        .default_timeout = var_InheritInteger( parent, "preparse-timeout" ),
        .pf_start = PreparserOpenInput,
        .pf_probe = PreparserProbeInput,
        .pf_stop = PreparserCloseInput,
        .pf_release = InputItemRelease,
        .pf_hold = InputItemHold };


    if( likely( preparser ) )
        preparser->worker = background_worker_New( preparser, &conf );

    if( unlikely( !preparser || !preparser->worker ) )
    {
        free( preparser );
        return NULL;
    }

    preparser->owner = parent;
    preparser->fetcher = playlist_fetcher_New( parent );
    atomic_init( &preparser->deactivated, false );

    if( unlikely( !preparser->fetcher ) )
        msg_Warn( parent, "unable to create art fetcher" );

    return preparser;
}

void playlist_preparser_Push( playlist_preparser_t *preparser,
    input_item_t *item, input_item_meta_request_option_t i_options,
    int timeout, void *id )
{
    if( atomic_load( &preparser->deactivated ) )
        return;

    vlc_mutex_lock( &item->lock );
    int i_type = item->i_type;
    int b_net = item->b_net;
    vlc_mutex_unlock( &item->lock );

    switch( i_type )
    {
        case ITEM_TYPE_NODE:
        case ITEM_TYPE_FILE:
        case ITEM_TYPE_DIRECTORY:
        case ITEM_TYPE_PLAYLIST:
            if( !b_net || i_options & META_REQUEST_OPTION_SCOPE_NETWORK )
                break;
        default:
            input_item_SignalPreparseEnded( item, ITEM_PREPARSE_SKIPPED );
            return;
    }

    if( background_worker_Push( preparser->worker, item, id, timeout ) )
        input_item_SignalPreparseEnded( item, ITEM_PREPARSE_FAILED );
}

void playlist_preparser_fetcher_Push( playlist_preparser_t *preparser,
    input_item_t *item, input_item_meta_request_option_t options )
{
    if( preparser->fetcher )
        playlist_fetcher_Push( preparser->fetcher, item, options, -1 );
}

void playlist_preparser_Cancel( playlist_preparser_t *preparser, void *id )
{
    background_worker_Cancel( preparser->worker, id );
}

void playlist_preparser_Deactivate( playlist_preparser_t* preparser )
{
    atomic_store( &preparser->deactivated, true );
    background_worker_Cancel( preparser->worker, NULL );
}

void playlist_preparser_Delete( playlist_preparser_t *preparser )
{
    background_worker_Delete( preparser->worker );

    if( preparser->fetcher )
        playlist_fetcher_Delete( preparser->fetcher );

    free( preparser );
}
