#!/usr/bin/env perl

use strict;

use FindBin;
use IPC::Open3;
use Symbol 'gensym';
use Data::Dumper;

use YAML;
use Template;


sub usage {
    print STDERR "usage: $0 <schema-file> <output-dir>\n";
    exit 1;
}

my $schemaFile = shift || usage();
my $outputDir = shift || usage();


die "unable to find schema file $schemaFile" if !-e $schemaFile;

my $schema = YAML::LoadFile($schemaFile);


## Sanity checking

{
    $schema->{db} ||= 'defaultDb';

    die "db name can't include '__': $schema->{db}" if $schema->{db} =~ /__/;

    foreach my $tableName (keys %{ $schema->{tables} }) {
        die "table names can't include '__': $tableName" if $tableName =~ /__/;
    }
}

## Pre-process indices

foreach my $tableName (keys %{ $schema->{tables} }) {
    my $table = $schema->{tables}->{$tableName};

    die "opaque tables cannot have 'fields'" if $table->{opaque} && @{ $table->{fields} || [] };

    foreach my $field (@{ $table->{fields} }) {
        if ($field->{nestedFlat}) {
            $field->{nestedFlatCppNamespace} = $field->{nestedFlat};
            $field->{nestedFlatCppNamespace} =~ s/[.]/::/g;
            $field->{attributes} = qq{ (nested_flatbuffer: "$field->{nestedFlat}")};
        }
    }

    foreach my $field (@{ $table->{fields} }) {
        die "field name cannot contain __" if $field->{name} =~ /__/;

        next if !$field->{index};
        $field->{index} = {} unless ref $field->{index} eq 'HASH';

        die "duplicate index definition for $tableName / $field->{name}" if $table->{indices}->{$field->{name}};
        die "index from field cannot be multi: $field->{name}" if $field->{index}->{multi};

        my $newIndex = {
            %{ $field->{index} },
            accessor => $field->{name},
            from_field => 1,
            integer => integerType($field->{type}),
            multi => !!($field->{type} =~ /\[\]$/),
        };


        $table->{indices}->{$field->{name}} = $newIndex;
    }

    foreach my $indexName (keys %{ $table->{indices} }) {
        $table->{indices}->{$indexName} = {} unless ref $table->{indices}->{$indexName} eq 'HASH';
        my $index = $table->{indices}->{$indexName};
        die "indices with multi and unique are not supported ($tableName.$indexName)" if $index->{multi} && $index->{unique};
        $index->{accessor} = $indexName if !$index->{accessor};
    }
}



if ($ENV{DUMP_SCHEMA}) {
    print Dumper($schema);
}



system("mkdir", "-p", $outputDir) && die $!;

my $tt = Template->new({
    ABSOLUTE => 1,
    INCLUDE_PATH => $FindBin::Bin,
}) || die "$Template::ERROR\n";

$schema->{utils} = {
    type_to_flatbuffers => sub {
        my $type = shift;
        return "uint64" if !defined $type || $type eq '';
        return "[ubyte]" if $type eq 'ubytes';
        return "[string]" if $type eq 'ubytes[]' || $type eq 'string[]';
        return "[uint64]" if $type eq 'uint64[]';
        return $type;
    },

    type_to_cpp => sub {
        my $type = shift;
        my $insertion = shift;
        return "uint64_t" if !defined $type || $type eq '';
        return "${type}_t" if $type =~ /^u?int\d+$/;
        return "std::vector<std::string_view>" if $type eq 'ubytes[]' || $type eq 'string[]';
        return "std::vector<uint64_t>" if $type eq 'uint64[]';
        return "std::string_view";
    },

    index_name_to_type => sub {
        my $indexDef = shift;
        return "uint64_t" if $indexDef->{integer};
        return "std::string_view";
    },

    index_conversion => sub {
        my $indexDef = shift;
        return "lmdb::to_sv<uint64_t>" if $indexDef->{integer};
        return "";
    },

    index_conversion_rev => sub {
        my $indexDef = shift;
        return "lmdb::from_sv<uint64_t>" if $indexDef->{integer};
        return "";
    },
};

my $fbsFile = "$outputDir/$schema->{db}.schema.fbs";
$tt->process("$FindBin::Bin/schema.fbs.tt", $schema, $fbsFile) || die $tt->error(), "\n";

{
    open3(undef, my $fh = gensym, my $fh2 = gensym, qw{flatc --cpp --reflect-names -o}, $outputDir, $fbsFile) || die "unable to run flatc: $!";

    my $printer = sub {
        next if /warning: field names should be lowercase snake_case/;
        next if /^warning:$/;
        next if /^\s*$/;
        print "FLATC: $_";
    };

    while (<$fh>) { $printer->(); }
    while (<$fh2>) { $printer->(); }

    wait;
    die "flatc failure" if $?;
}

my $hFile = "$outputDir/$schema->{db}.h";
$tt->process("$FindBin::Bin/main.h.tt", $schema, $hFile) || die $tt->error(), "\n";








#######

sub integerType {
    my $type = shift;

    return 1 if !defined $type;
    return 1 if $type =~ /^u?int\d+(\[\]|)$/;

    return undef;
}
