#!/usr/bin/env perl

# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

use strict;
use warnings FATAL => 'all';
use v5.12;
use Data::Dump qw(pp);
use Path::Class;
use Perl::Tidy;
use JSON::XS;

our ( %API, %Common, %seen, %seen_combo, %Forbidden );

our %Known_Types = map { $_ => 1 } qw(boolean enum date list number int float double string time );

#===================================
sub process_files {
#===================================
    my $module = shift();
    my @files;

    while ( my $file = shift() ) {
        unless ( $file =~ /_common.json$/ ) {
            push @files, $file;
            next;
        }

        say $file;
        my $data = decode_json( $file->slurp );
        %Common = ( %Common, process_qs( $data->{params} ) );
    }

    delete @Common{ 'pretty', 'source' };

    for my $file (@files) {
        say $file;
        my $data = decode_json( $file->slurp );
        my ( $name, $defn ) = %$data;
        die "File $file doesn't match name $name"
            unless $file =~ m{/$name.json};

        eval { $API{$name} = process( $name, $defn ) }
            || die "$name: $@";

    }
    update_module($module);
}

#===================================
sub process {
#===================================
    my ( $name, $defn ) = @_;
    my %spec;

    # body
    if ( my $body = $defn->{body} ) {
        $spec{body}
            = $body->{required}
            ? { required => 1 }
            : {};
        if ( $body->{serialize} && $body->{serialize} eq 'bulk' ) {
            $spec{serialize} = 'bulk';
        }
    }
    
    # method
    my $method = $spec{method} = $defn->{methods}[0];
    delete $spec{method} if $method eq 'GET';
    
    # paths
    my $url = $defn->{url};
    $spec{paths} = process_paths( $name, $method, $url );

    # parts
    my $parts = $spec{parts} = process_parts( $url->{parts} );

    # filter path
    my %qs = ( %Common, process_qs( $url->{params} ) );
    for ( keys %$parts ) {
        delete $qs{$_};
    }

    $spec{qs} = \%qs;

    # doc
    if ( $defn->{documentation} ) {
        $spec{doc} = $defn->{documentation} =~ m{/([^/]+)\.html$} ? $1 : '';
    }
    return \%spec;
}

#===================================
sub process_paths {
#===================================
    my ( $name, $method, $url ) = @_;

    if ($url->{deprecated_paths}) {
       foreach my $u (@{$url->{deprecated_paths}}) {
           push (@{ $url->{paths} }, $u->{path});
       }
    }
    my @path_defns = map { process_path( $method, $_ ) } @{ $url->{paths} };

    my %sigs;
    for (@path_defns) {
        $_->{name} = $name;

        # check for duplicate params
        my $sig = $_->{sig};
        if ( my $exists = $sigs{$sig} ) {
            next if length( $exists->{path} ) <= length $_->{path};
        }
        $sigs{$sig} = $_;

        # check for duplicate wildcards
        warn "Duplicate paths: " . pp( [ $seen{ $_->{wildcard} }, $_ ] )
            if $seen{ $_->{wildcard} };

        $seen{ $_->{wildcard} } = $_;
    }

    # generate paths with _all
    my @paths;
    for my $path ( sort { $a->{max} <=> $b->{max} or $b->{sig} cmp $a->{sig} }
        values %sigs )
    {
    }
    continue {
        push @paths, [ $path->{params}, @{ $path->{parts} } ];
    }
    return [ reverse @paths ];
}

#===================================
sub process_parts {
#===================================
    my $parts = shift;
    my %params;
    for my $key ( keys %$parts ) {
        my %defn;
        $defn{multi}    = 1 if $parts->{$key}{type} eq 'list';
        $defn{required} = 1 if $parts->{$key}{required};
        $params{$key}   = \%defn;
    }
    return \%params;
}

#===================================
sub replace_with_all {
#===================================
    my ( $method, $path, $param ) = @_;
    substr( $path, index( $path, $param ), length($param) ) = '_all';
    return process_path( $method, $path );
}

#===================================
sub is_param { substr( $_[0], 0, 1 ) eq '{' }
sub param_name { my $n = shift; $n =~ s/[{}]//g or return undef; $n; }
#===================================

#===================================
sub process_path {
#===================================
    my ( $method, $path ) = @_;
    return if $Forbidden{$method}{$path};
    my @parts = grep {$_} split /\//, $path;

    my $defn = {
        path  => $path,
        parts => \@parts,
    };

    my $count = 0;
    for my $i ( 0 .. $#parts ) {
        my $name = param_name( $parts[$i] ) or next;
        $count++;
        $defn->{params}{$name} = $i;
    }
    $defn->{max} = $count;
    $defn->{wildcard}
        = join "/",
        "$method ",
        map { is_param($_) ? '*' : $_ } @parts;

    $defn->{sig} = join "-", sort keys %{ $defn->{params} };

    return $defn;
}

#===================================
sub process_qs {
#===================================
    my $params = shift || {};
    my %qs;

    for my $param ( keys %$params ) {
        next if $Forbidden{QS}{$param};
        my $def = $params->{$param};
        my $type = $def->{type} || die "No type specified for param [$param]";
        $type = 'time' if $type eq 'date';
        die "Unknown type [$type] for param [$param]"
            unless $Known_Types{$type};
        $qs{$param} = $type;
    }
    return %qs;
}

#===================================
sub forbid {
#===================================
    my $method = shift;
    for (@_) {
        $Forbidden{$method}{$_} = 1;
    }
}

#===================================
sub update_module {
#===================================
    my $module   = shift;
    my $file     = file($module);
    my $contents = $file->slurp;
    my $out;
    if ( $contents =~ /^(.+\n#=== AUTOGEN - START ===\n\n)/s ) {
        $out = $1;
    }
    else {
        die "Couldn't find AUTOGEN - START marker";
    }

    my @keys = grep { !/\./ } sort keys %API;
    push @keys, grep {/\./} sort keys %API;

    for my $name (@keys) {
        $out .= "\n'$name' => " . pp( $API{$name} ) . ",\n";
    }

    if ( $contents =~ /(\n\n#=== AUTOGEN - END ===.+$)/s ) {
        $out .= $1;
    }
    else {
        die "Couldn't find AUTOGEN - END marker";
    }

    Perl::Tidy::perltidy(
        source      => \$out,
        destination => $module,
        argv        => '-q --indent-columns=4 --maximum-line-length=80 '
            . '-pbp -nst -sot -vt=2 -nsob -sbcp=#='
    );

}

1;
