#!/usr/bin/env perl6

use Cro::HTTP::Router;
use Cro::HTTP::Server;

use DSL::Shared::Utilities::MetaSpecsProcessing;
use DSL::Translators;
use Lingua::NumericWordForms;
use JSON::Fast;

use LLM::Functions;
use ML::FindTextualAnswer;
use ML::NLPTemplateEngine;


my %*SUB-MAIN-OPTS = :named-anywhere;

#============================================================
# MAIN program
#============================================================
#| Start a Cro service for translation of DSLs into executable code.
sub MAIN(
        Str :$host = 'localhost', #= Host name.
        Str :$port = '10000', #= Port.
        Str :$llm = 'ChatGPT', #= LLM service to use.
        Str :$llm-model = 'gpt-3.5-turbo', #= LLM model to use.
        Str :$llm-api-key = '', #= LLM API key; if an empty string the corresponding env variable is used.
         ) {

    # Empty result hash
    constant %emptyResult = %( CODE => '',
                               STDERR => '',
                               COMMAND => '',
                               USERID => '',
                               DSL => '',
                               DSLTARGET => '',
                               DSLFUNCTION => '');

    # Check LLM configuration
    my $conf;
    if $llm {
        $conf = llm-configuration($llm, model => $llm-model, api-key => ($llm-api-key ?? $llm-api-key !! Whatever));
    } else {
        note 'Not activating LLM.'
    }

    # Cro application
    my $application = route {
        my $user-id = '';

        #=======================================================================
        # Is ready
        get -> 'is_ready' {
            content 'application/json', to-json({ :is-ready });
        }

        #=======================================================================
        # Show setup
        get -> 'show_setup' {
            my %conf = $conf.Hash;
            if !%conf<api-key>.isa(Whatever) { %conf<api-key> = '<hidden>'; }
            content 'application/json', to-json({ :$user-id, conf => %conf.gist });
        }

        #=======================================================================
        # Setup
        get -> 'setup', Str:D :$api_key= '', Str:D :$user_id= '', Str:D :$llm = $llm, Str:D :$model = $llm-model {

            my $response = '';

            # API key setup
            if $api_key {
                given $llm {
                    when $_.lc ∈ <openai chatgpt> {
                        %*ENV<OPENAI_API_KEY> = $api_key
                    }
                    when $_.lc ∈ <palm bard gemini> {
                        %*ENV<GEMINI_API_KEY> = $api_key
                    }
                    when $_.lc ∈ <mistral mistralai> {
                        %*ENV<MISTRAL_API_KEY> = $api_key
                    }
                    default {
                        $response = 'Unknown spec for the parameter llm.'
                    }
                }
            }

            if $user_id { $user-id = $user_id }

            # Response
            if !$response {

                # Recreate the configuration
                $conf = llm-configuration($llm, api-key => $api_key, api-user-id => $user-id, :$model);

                $response = 'Setup is successful.';
            }

            content 'application/json', to-json({ 'import' => $response });
        }

        #=======================================================================
        # DSL translation by QAS
        get -> 'translate', 'qas', :$template = 'Whatever', :$lang='WL', :$command! {

            if $conf {

                my Str $res = dsl-qas-translation($conf, $command, :$template, :$lang);

                # This is temporary -- concretize should have proper :$format implementation.
                my %res = %emptyResult, { COMMAND => $command, CODE => $res, DSLFUNCTION => 'concretize'};

                content 'text/html', to-json(%res);

            } else {

                my %res = %emptyResult, { STDERR => 'LL QAS was not activated.', COMMAND => $command, DSLFUNCTION => 'concretize' };

                content 'text/html', to-json(%res);
            }
        }

        #=======================================================================
        # DSL translation by grammars
        get -> 'translate', :$command!, :to(:$lang) = 'WL', :from(:$from-lang) is copy = 'English', :$ast = 'false' {

            # I am not sure should we have these logging lines or not -- keeping them commented for now.
            # say 'translate request at ', DateTime.now.Str;
            # say (:$lang);
            # say (:$from-lang);
            # say (:$ast);
            # say (:$command);

            my $from-lang-msg = '';
            if $from-lang.isa(Whatever) || $from-lang ~~ Str && $from-lang.lc ∈ ('whatever', 'automatic') {
                my %counts = $command.subst(/\s/,'').comb.map({ $_ ∈ ('А'..'я')}).classify({$_}).map({$_.key=>$_.value.elems});
                my $total = [+] %counts.values;
                my %freqs = %counts.deepmap({ $_ / $total });
                $from-lang = ( %freqs<True>:exists && %freqs<True> ≥ 0.40 ) ?? 'Bulgarian' !! 'English';
                $from-lang-msg = "Heuristic determination of the command (from-)language to be $from-lang.";
            }

            my %res = dsl-translation($command, default-targets-spec => $lang, language => $from-lang, ast => $ast.lc eq 'true');

            if $from-lang-msg {
                %res<STDERR> = %res<STDERR> ~ "\n" ~ $from-lang-msg;
            }

            content 'text/html', to-json(%res);
        }

        #=======================================================================
        # Numeric word forms
        get -> 'translate', 'numeric', :$command!, :to(:$lang) = 'English', :from(:$from-lang) is copy = Whatever, {

            my Str $command2 = $command;
            $command2 = ($command2 ~~ / ['"' | '\''] .* ['"' | '\''] /) ?? $command2.substr(1, *- 1) !! $command2;

            my Bool $numberQ = so $command2 ~~ / ^ [';' \s* | \d]* $ /;
            # We ignore :$from-lang -- always using Whatever
            my $parserRes = $numberQ ?? to-numeric-word-form($command2, :$lang) !! from-numeric-word-form($command2, Whatever, :p);

            my %res = %( CODE => $parserRes,
                         COMMAND => $command2,
                         USERID => '',
                         DSL => 'Lingua::NumericWordForms',
                         DSLTARGET => 'Lingua::NumericWordForms',
                         DSLFUNCTION => $numberQ ?? 'to-numeric-word-form' !! 'from-numeric-word-form');

            content 'text/html', to-json(%res);
        }

        #=======================================================================
        # Find textual answer
        get -> 'find-textual-answer', :$text!, :q(:$questions)!, :$pairs = 'false' {

            my $res;
            if $conf {
                my Bool $pairs2 = $pairs.Str.lc ∈ <true yes 1> ?? True !! False;
                my @questions = $questions.split( / '?' \n* /, :skip-empty).map({ $_.trim ~ '?' });
                $res = find-textual-answer($text, @questions, pairs => $pairs2, finder => llm-evaluator($conf));
            } else {
                $res = 'LLM QAS is not activated.'
            }
            content 'text/html', $res ~~ Iterable:D ?? to-json($res) !! $res.Str;
        }

    }

    # The Cro service
    my Cro::Service $service = Cro::HTTP::Server.new(:$host, port => $port.Int, :$application);

    $service.start;

    note 'Started the Cro service.';

    react whenever signal(SIGINT) {
        $service.stop;
        exit;
    }
}


#============================================================
# QAS
#============================================================

#| Translate commands by QAS.
sub dsl-qas-translation($conf, Str:D $commands, Str:D :$template, Str:D :$lang = 'WL') {

    my $template2 = $template.lc ∈ <whatever whatevercode auto automatic> ?? Whatever !! $template;
    my $message = concretize($commands, finder => llm-evaluator($conf), template => $template2, :$lang);

    # Return result
    return $message;
}

