/*****************************************************************************
 * autocomplete: x264cli shell autocomplete
 *****************************************************************************
 * Copyright (C) 2018-2020 x264 project
 *
 * Authors: Henrik Gramner <henrik@gramner.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
 *
 * This program is also available under a commercial proprietary license.
 * For more information, contact us at licensing@x264.com.
 *****************************************************************************/

#include "x264cli.h"
#include "input/input.h"

#if HAVE_LAVF
#undef DECLARE_ALIGNED
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#endif

static const char * const level_names[] =
{
    "1", "1.1", "1.2", "1.3", "1b",
    "2", "2.1", "2.2",
    "3", "3.1", "3.2",
    "4", "4.1", "4.2",
    "5", "5.1", "5.2",
    "6", "6.1", "6.2",
    NULL
};

/* Options requiring a value for which we provide suggestions. */
static const char * const opts_suggest[] =
{
    "--alternative-transfer",
    "--aq-mode",
    "--asm",
    "--avcintra-class",
    "--avcintra-flavor",
    "--b-adapt",
    "--b-pyramid",
    "--colormatrix",
    "--colorprim",
    "--cqm",
    "--demuxer",
    "--direct",
    "--frame-packing",
    "--input-csp",
    "--input-fmt",
    "--input-range",
    "--level",
    "--log-level",
    "--me",
    "--muxer",
    "--nal-hrd",
    "--output-csp",
    "--overscan",
    "--pass", "-p",
    "--preset",
    "--profile",
    "--pulldown",
    "--range",
    "--subme", "-m",
    "--transfer",
    "--trellis", "-t",
    "--tune",
    "--videoformat",
    "--weightp",
    NULL
};

/* Options requiring a value for which we don't provide suggestions. */
static const char * const opts_nosuggest[] =
{
    "--b-bias",
    "--bframes", "-b",
    "--deblock", "-f",
    "--bitrate", "-B",
    "--chroma-qp-offset",
    "--chromaloc",
    "--cplxblur",
    "--cqm4",
    "--cqm4i",
    "--cqm4ic",
    "--cqm4iy",
    "--cqm4p",
    "--cqm4pc",
    "--cqm4py",
    "--cqm8",
    "--cqm8i",
    "--cqm8p",
    "--crf",
    "--crf-max",
    "--crop-rect",
    "--deadzone-inter",
    "--deadzone-intra",
    "--fps",
    "--frames",
    "--input-depth",
    "--input-res",
    "--ipratio",
    "--keyint", "-I",
    "--lookahead-threads",
    "--merange",
    "--min-keyint", "-i",
    "--mvrange",
    "--mvrange-thread",
    "--nr",
    "--opencl-device",
    "--output-depth",
    "--partitions", "-A",
    "--pbratio",
    "--psy-rd",
    "--qblur",
    "--qcomp",
    "--qp", "-q",
    "--qpmax",
    "--qpmin",
    "--qpstep",
    "--ratetol",
    "--ref", "-r",
    "--rc-lookahead",
    "--sar",
    "--scenecut",
    "--seek",
    "--slices",
    "--slices-max",
    "--slice-max-size",
    "--slice-max-mbs",
    "--slice-min-mbs",
    "--sps-id",
    "--sync-lookahead",
    "--threads",
    "--timebase",
    "--vbv-bufsize",
    "--vbv-init",
    "--vbv-maxrate",
    "--video-filter", "--vf",
    "--zones",
    NULL
};

/* Options requiring a filename. */
static const char * const opts_filename[] =
{
    "--cqmfile",
    "--dump-yuv",
    "--index",
    "--opencl-clbin",
    "--output", "-o",
    "--qpfile",
    "--stats",
    "--tcfile-in",
    "--tcfile-out",
    NULL
};

/* Options without an associated value. */
static const char * const opts_standalone[] =
{
    "--8x8dct",
    "--aud",
    "--bff",
    "--bluray-compat",
    "--cabac",
    "--constrained-intra",
    "--cpu-independent",
    "--dts-compress",
    "--fake-interlaced",
    "--fast-pskip",
    "--filler",
    "--force-cfr",
    "--mbtree",
    "--mixed-refs",
    "--no-8x8dct",
    "--no-asm",
    "--no-cabac",
    "--no-chroma-me",
    "--no-dct-decimate",
    "--no-deblock",
    "--no-fast-pskip",
    "--no-mbtree",
    "--no-mixed-refs",
    "--no-progress",
    "--no-psy",
    "--no-scenecut",
    "--no-weightb",
    "--non-deterministic",
    "--open-gop",
    "--opencl",
    "--pic-struct",
    "--psnr",
    "--quiet",
    "--sliced-threads",
    "--slow-firstpass",
    "--ssim",
    "--stitchable",
    "--tff",
    "--thread-input",
    "--verbose", "-v",
    "--weightb",
    NULL
};

/* Options which shouldn't be suggested in combination with other options. */
static const char * const opts_special[] =
{
    "--fullhelp",
    "--help", "-h",
    "--longhelp",
    "--version",
    NULL
};

static int list_contains( const char * const *list, const char *s )
{
    if( *s )
        for( ; *list; list++ )
            if( !strcmp( *list, s ) )
                return 1;
    return 0;
}

static void suggest( const char *s, const char *cur, int cur_len )
{
    if( s && *s && !strncmp( s, cur, cur_len ) )
        printf( "%s\n", s );
}

static void suggest_lower( const char *s, const char *cur, int cur_len )
{
    if( s && *s && !strncasecmp( s, cur, cur_len ) )
    {
        for( ; *s; s++ )
            putchar( *s < 'A' || *s > 'Z' ? *s : *s | 0x20 );
        putchar( '\n' );
    }
}

static void suggest_num_range( int start, int end, const char *cur, int cur_len )
{
    char buf[16];
    for( int i = start; i <= end; i++ )
    {
        snprintf( buf, sizeof( buf ), "%d", i );
        suggest( buf, cur, cur_len );
    }
}

#if HAVE_LAVF
/* Suggest each token in a string separated by delimiters. */
static void suggest_token( const char *s, int delim, const char *cur, int cur_len )
{
    if( s && *s )
    {
        for( const char *tok_end; (tok_end = strchr( s, delim )); s = tok_end + 1 )
        {
            int tok_len = tok_end - s;
            if( tok_len && tok_len >= cur_len && !strncmp( s, cur, cur_len ) )
                printf( "%.*s\n", tok_len, s );
        }
        suggest( s, cur, cur_len );
    }
}
#endif

#define OPT( opt ) else if( !strcmp( prev, opt ) )
#define OPT2( opt1, opt2 ) else if( !strcmp( prev, opt1 ) || !strcmp( prev, opt2 ) )
#define OPT_TYPE( type ) list_contains( opts_##type, prev )

#define suggest( s ) suggest( s, cur, cur_len )
#define suggest_lower( s ) suggest_lower( s, cur, cur_len )
#define suggest_list( list ) for( const char * const *s = list; *s; s++ ) suggest( *s )
#define suggest_num_range( start, end ) suggest_num_range( start, end, cur, cur_len )
#define suggest_token( s, delim ) suggest_token( s, delim, cur, cur_len )

int x264_cli_autocomplete( const char *prev, const char *cur )
{
    int cur_len = strlen( cur );
    if( 0 );
    OPT( "--alternative-transfer" )
        suggest_list( x264_transfer_names );
    OPT( "--aq-mode" )
        suggest_num_range( 0, 3 );
    OPT( "--asm" )
        for( const x264_cpu_name_t *cpu = x264_cpu_names; cpu->flags; cpu++ )
            suggest_lower( cpu->name );
    OPT( "--avcintra-class" )
        suggest_list( x264_avcintra_class_names );
    OPT( "--avcintra-flavor" )
        suggest_list( x264_avcintra_flavor_names );
    OPT( "--b-adapt" )
        suggest_num_range( 0, 2 );
    OPT( "--b-pyramid" )
        suggest_list( x264_b_pyramid_names );
    OPT( "--colormatrix" )
        suggest_list( x264_colmatrix_names );
    OPT( "--colorprim" )
        suggest_list( x264_colorprim_names );
    OPT( "--cqm" )
        suggest_list( x264_cqm_names );
    OPT( "--demuxer" )
        suggest_list( x264_demuxer_names );
    OPT( "--direct" )
        suggest_list( x264_direct_pred_names );
    OPT( "--frame-packing" )
        suggest_num_range( 0, 7 );
    OPT( "--input-csp" )
    {
        for( int i = X264_CSP_NONE+1; i < X264_CSP_CLI_MAX; i++ )
            suggest( x264_cli_csps[i].name );
#if HAVE_LAVF
        for( const AVPixFmtDescriptor *d = NULL; (d = av_pix_fmt_desc_next( d )); )
            suggest( d->name );
#endif
    }
    OPT( "--input-fmt" )
    {
#if HAVE_LAVF
        av_register_all();
        for( const AVInputFormat *f = NULL; (f = av_iformat_next( f )); )
            suggest_token( f->name, ',' );
#endif
    }
    OPT( "--input-range" )
        suggest_list( x264_range_names );
    OPT( "--level" )
        suggest_list( level_names );
    OPT( "--log-level" )
        suggest_list( x264_log_level_names );
    OPT( "--me" )
        suggest_list( x264_motion_est_names );
    OPT( "--muxer" )
        suggest_list( x264_muxer_names );
    OPT( "--nal-hrd" )
        suggest_list( x264_nal_hrd_names );
    OPT( "--output-csp" )
        suggest_list( x264_output_csp_names );
    OPT( "--output-depth" )
    {
#if HAVE_BITDEPTH8
        suggest( "8" );
#endif
#if HAVE_BITDEPTH10
        suggest( "10" );
#endif
    }
    OPT( "--overscan" )
        suggest_list( x264_overscan_names );
    OPT2( "--partitions", "-A" )
        suggest_list( x264_partition_names );
    OPT2( "--pass", "-p" )
        suggest_num_range( 1, 3 );
    OPT( "--preset" )
        suggest_list( x264_preset_names );
    OPT( "--profile" )
        suggest_list( x264_valid_profile_names );
    OPT( "--pulldown" )
        suggest_list( x264_pulldown_names );
    OPT( "--range" )
        suggest_list( x264_range_names );
    OPT2( "--subme", "-m" )
        suggest_num_range( 0, 11 );
    OPT( "--transfer" )
        suggest_list( x264_transfer_names );
    OPT2( "--trellis", "-t" )
        suggest_num_range( 0, 2 );
    OPT( "--tune" )
        suggest_list( x264_tune_names );
    OPT( "--videoformat" )
        suggest_list( x264_vidformat_names );
    OPT( "--weightp" )
        suggest_num_range( 0, 2 );
    else if( !OPT_TYPE( nosuggest ) && !OPT_TYPE( special ) )
    {
        if( OPT_TYPE( filename ) || strncmp( cur, "--", 2 ) )
            return 1; /* Fall back to default shell filename autocomplete. */

        /* Suggest options. */
        suggest_list( opts_suggest );
        suggest_list( opts_nosuggest );
        suggest_list( opts_filename );
        suggest_list( opts_standalone );

        /* Only suggest special options if no other options have been specified. */
        if( !*prev )
            suggest_list( opts_special );
    }

    return 0;
}
