/* === TeaScript Host Application ===
 * SPDX-FileCopyrightText:  Copyright (C) 2024 Florian Thake <contact |at| tea-age.solutions>.
 * SPDX-License-Identifier: AGPL-3.0-only
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, version 3.
 * 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>
 */
#if defined _MSC_VER  && !defined _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
# define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
#endif
#if defined _MSC_VER  && !defined _SILENCE_CXX20_U8PATH_DEPRECATION_WARNING
# define _SILENCE_CXX20_U8PATH_DEPRECATION_WARNING
#endif
#if defined _MSC_VER  && !defined _CRT_SECURE_NO_WARNINGS
# define _CRT_SECURE_NO_WARNINGS
#endif

// C:\Program Files( x86 )\Windows Kits\10\Include\10.0.19041.0\um\winbase.h( 9531, 5 ) : 
// warning C5105 : macro expansion producing 'defined' has undefined behavior( compiling source file source\TestTeaScriptCore_Main.cpp )
// from WinBase.h: #define MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS (_WIN32_WINNT >= 0x0502 || !defined(_WINBASE_))
#if defined( _MSC_VER )
# include <sdkddkver.h> // WDK_NTDDI_VERSION
# if WDK_NTDDI_VERSION < 0x0A00000A  // NTDDI_WIN10_FE
#  pragma warning( push )
#  pragma warning( disable: 5105 )
#  pragma message( "USNING OLD WINSDK!!" )
# endif
#endif

//for VS use /Zc:__cplusplus
#if __cplusplus < 202002L
# if defined _MSVC_LANG // fallback without /Zc:__cplusplus
#  if !_HAS_CXX20
#   error must use at least C++20
#  endif
# else
#  error must use at least C++20
# endif
#endif


// Undefine or set to 0 for disable colorized output
#define TEASCRIPT_USE_FMT_COLOR     1


// Actually we are dependent of external {fmt} lib. But we use it as header only...
#if __has_include( "fmt/format.h" )
# ifndef FMT_HEADER_ONLY
#  define FMT_HEADER_ONLY
# endif
# include "fmt/format.h"
# if defined( _MSC_VER ) && FMT_VERSION == 90100
#   pragma warning( push )
#   pragma warning( disable: 4127 ) // fmt 9.1.0 is shipped with warning C4127: conditional expression is constant (with W4)
# endif
# include "fmt/chrono.h"
# if defined( _MSC_VER ) && FMT_VERSION == 90100
#   pragma warning( pop )
# endif
# if TEASCRIPT_USE_FMT_COLOR
#  include "fmt/color.h"
#endif
#else
#  error no format lib found!
#endif


#include "teascript/Parser.hpp"
#include "teascript/Context.hpp"
#include "teascript/CoreLibrary.hpp"
#include "teascript/version.h"


#include <cstdlib> // EXIT_SUCCESS
#include <cstdio>
#include <csignal>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <filesystem>
#include <chrono>

// check for readline lib (must be after <iostream> !)
#if __has_include( <readline/readline.h> )
# include <readline/history.h>
# include <readline/readline.h>
# define TEASCRIPT_HAS_READLINE    1
#endif


#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define OEMRESOURCE
#define  NOATOM
#define  NOCLIPBOARD
#define  NOCOLOR
#define  NOCTLMGR
#define  NODRAWTEXT
#define NOGDI
#define  NOKERNEL
#define NOUSER
//#define  NONLS
#define  NOMB
#define  NOMEMMGR
#define NOMETAFILE
#define NOMINMAX
#define  NOMSG 
#define NOOPENFILE
#define NOSCROLL
#define  NOSERVICE
#define NOSOUND
#define  NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOCOMM
#define  NOKANJI
#define  NOHELP
#define  NOPROFILER
#define  NODEFERWINDOWPOS
#define  NOMCX
//#include <stringapiset.h> // WIN32 MultiByteTo...
//#include <fileapi.h> //  AreFileApisANSI() ? CP_ACP : CP_OEMCP;
#include <Windows.h>
#endif

#if defined( _MSC_VER )
# if WDK_NTDDI_VERSION < 0x0A00000A  // NTDDI_WIN10_FE
#  pragma warning( pop ) // for make compiler happy (warning)
# endif
// safety guard!
# if NTDDI_VERSION != NTDDI_WIN10_VB
#  error Wrong NTDDI_VERSION
# endif
#endif


#if 0 // check on Linux again...
volatile static std::sig_atomic_t GlobalRunning = 1;

// we want to react on Ctrl+C (exit)
void signal_handler( int const sig )
{
    switch( sig ) {
    case SIGINT:
    case SIGTERM:
        GlobalRunning = 0;
        break;
    default:
        ((void)0);
    }
}

void set_signal_handler()
{
#if defined( _WIN32 ) // have a crash on Windows, looks like a race condition...
    //signal( SIGINT, SIG_IGN );
    signal( SIGINT, signal_handler );
    signal( SIGTERM, signal_handler );
#else
    signal( SIGINT, signal_handler );
    signal( SIGTERM, signal_handler );
#endif
}
#endif


std::string copyright_info()
{
    return std::string( teascript::copyright_info() ) + " See LICENSE.TXT.";
}


bool print_ast( teascript::ASTNode const *node, int depth )
{
    std::string ident = "|";

    while( depth > 1 ) {
        ident += " |";
        --depth;
    }

#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::fg( fmt::color::wheat ), "{}-{}\n", ident, fmt::styled( node->GetInfoStr(), fmt::fg( fmt::color::white_smoke ) ) );
#else
    std::cout << ident << "-" << node->GetInfoStr() << std::endl;
#endif

    return true;
}

void print_parameters()
{
    fmt::print( "\n{0}, version {1}.{2}.{3}.\n"
                "{4}\n\n"
                "{0}                           -->  without arguments interactive shell is launched.\n"
                "{0} [params] filename [args]  -->  executes 'filename' with 'args' as script arguments.\n"
                "{0} --printAST filename       -->  parses 'filename', prints the AST and measures parsing time.\n"
                "{0} --batch filename          -->  processes 'filename' as batch commands before interactive shell.\n"
                "{0} -v,--version              -->  prints version information and exit.\n"
                "{0} -?,-h,/?,--help           -->  prints this help message.\n"
                "\n[params]:\n"
                "-D,--debug        --> enables debug mode.\n"
                "-T,--time         --> enables time measurement (bootstrap/parsing/eval).\n"
                "-i,--interactive  --> launches interactive shell after script file has been executed.\n"
                "--config=c        --> uses CoreLibrary config c. c must be hexadecimal starting with 0x\n"
                "--                --> ends [params], next parameter is filename.\n"
                "--old-mutable-parameters --> activates the old parameters are mutable by default behavior.\n"
                "                             This setting is only for transition and will be removed.\n"
                "\nFurther support is available at: <{5}> \n", teascript::self_name_str(),
                teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch(), 
                copyright_info(), teascript::contact_info() );
    fmt::print( "\nNew versions of this TeaScript Host Application for execute standalone TeaScript files\n"
                "will be made available here: https://tea-age.solutions/downloads/ \n" );
}

void show_help()
{
    fmt::print( "All internal commands start with a colon :, here follows a list.\n\n"
                ":reset   -> resets the Parser state _and_ the context (e.g. clears all variables, etc.)\n"
                ":reset=c -> as reset but uses CoreLibrary config c. c must be hexadecimal starting with 0x\n"
                ":debug   -> toggles debug mode. debug mode will print the AST and pretty print eval errors.\n"
                ":silent  -> toggles silent mode. in silent mode the immediate printing of last value is disabled.\n"
                ":ls vars -> list all variables/functions of the global scope.\n"
                ":search <str>    -> list all variables/functions of the global scope containing <str>.\n\n"
                ":load=<file>     -> loads <file> for manually operating on that file (parse,exec,show,...).\n"
                ":show <n>        -> shows n lines from current position of loaded file. 0 == with marked column.\n"
                ":goto line[,col] -> goes to line and column of loaded file if position exists. col defaults to 1.\n"
                ":parse <n>       -> parses n lines from current position of loaded file.\n"
                ":exec <n>        -> executes next n statements of already parsed statements of loaded file.\n"
                ":stmt <n>        -> prints AST of an already parsed (top-level) statement of loaded file.\n"
                "                    use 0 for show the actual amount of present statements.\n\n"
                ":q|quit|exit -> exits the interactive {} shell.\n"
                ":?|help      -> shows this help message.\n\n"
                "Further support is available at: <{}> \n", teascript::self_name_str(), teascript::contact_info() );
}

// the context when manually loading and operating on a file.
struct PartialParseState
{
    std::string         filename;
    std::string         storage;
    teascript::Content  content;
    teascript::Parser   parser;
    std::list< teascript::ASTNodePtr >  parts;
};

void unload_file( PartialParseState &state )
{
    state.content = teascript::Content();
    state.filename.clear();
    state.storage.clear();
    state.parser.ClearState();
    state.parts.clear();
}

void load_file( std::string const &file, PartialParseState &state )
{
    try {
        unload_file( state );
        auto val = teascript::CoreLibrary::ReadTextFile( file );
        if( val.GetTypeInfo()->IsSame<bool>() ) {
            throw std::runtime_error( "" );
        }
        state.storage = val.GetValue<std::string>();
        state.content = teascript::Content( state.storage );
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not load file! {}\n", ex.what() );
        return;
    }
    state.filename = file;
    teascript::Content tmp = state.content;
    long long lines = 0;
    while( tmp.HasMore() ) {
        tmp.MoveToLineFeed();
        ++lines;
        auto const cur = tmp[0]; // memory for last empty line check.
        ++tmp; // next line
        // check for a possible last empty line (if the last line ends with new line)
        if( not tmp.HasMore() && cur == '\n' ) {
            ++lines; // count the last (empty) one as well
        }
    }
    fmt::print( "<<< file loaded, {} lines. ok.\n", lines );
}


void reload( PartialParseState &state )
{
    // do this always without check sth. was loaded.
    state.parser.ClearState();
    state.parts.clear();
    state.content = teascript::Content( state.storage );   
}


void show_lines( std::string const &lines, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }

        auto n = std::stoull( lines );
        if( 0 == n ) {
#if TEASCRIPT_USE_FMT_COLOR
            teascript::util::debug_print_currentline_colored( state.content, true );
#else
            teascript::util::debug_print_currentline( state.content, true );
#endif
            return;
        }
        auto c = state.content;
        auto l = c.CurrentLine();
        while( n > 0 && c.HasMore() ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( fmt::fg( fmt::color::wheat ), "{:04} : {}\n", l++, fmt::styled( teascript::util::extract_current_line( c ), fmt::fg( fmt::color::white_smoke ) ) );
#else
            fmt::print( "{:04} : {}\n", l++, teascript::util::extract_current_line( c ) );
#endif
            c.MoveToLineFeed();
            ++c;
            --n;
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show lines! {}\n", ex.what() );
    }
}

void goto_pos( std::string const &pos, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to 'goto'. Load a file first!" );
        }
        unsigned int line = 1, col = 1;
#if defined(_MSC_VER)
        auto const scanned = sscanf_s( pos.c_str(), "%u,%u", &line, &col );
#else
        auto const scanned = sscanf( pos.c_str(), "%u,%u", &line, &col );
#endif
        if( scanned < 1 ) {
            throw std::runtime_error( "Must be the form: goto line[,col]" );
        } else if( line == 0 || col == 0 ) {
            throw std::runtime_error( "Line and column must be a positive number!" );
        }

        state.content.GoTo( static_cast<long long>(line), static_cast<long long>(col) );

        show_lines( "0", state );

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not 'goto' position! {}\n", ex.what() );
    }
}

void show_stmt( std::string const &no, PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to show. Load a file first!" );
        }

        // number, not idx (starts at 1)!
        auto n = std::stoull( no );

        if( n > state.parts.size() ) {
            throw std::out_of_range( "Not enough statements available!" );
        } else if( 0 == n ) {
            fmt::print( "<<< {} remaining statements.\n", state.parts.size() );
            return;
        }

        auto it = state.parts.begin();
        if( n > 1 ) {
            std::advance( it, n - 1 );
        }
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
        fmt::print( "AST:\n" );
#endif
        (*it)->Apply( print_ast );

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not show statement! {}\n", ex.what() );
    }
}

void parse_lines( std::string const &lines, PartialParseState &state, bool debug )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to parse. Load a file first!" );
        }

        auto n = std::stoull( lines );
        if( 0 == n ) {
            throw std::out_of_range( "Must be a positive number!" );
        } else if( not state.content.HasMore() ) {
            throw std::runtime_error( "No more content to parse!" );
        }

        auto start = state.content;
        auto end = start;
        while( n > 0 && end.HasMore() ) {
            end.MoveToLineFeed();
            if( n > 1 ) {
                ++end; // next line
            }
            --n;
        }

        auto c = start.SubContent( 0, &(*end) - &(*start) );
        c.SetLineOffset( start.CurrentLine() );
        state.parser.ParsePartial( c, state.filename );

        // add new node(s) 'sub-routine'
        auto node_handler = [&state,debug]( auto node ) mutable {
            if( node->begin() != node->end() ) {
                if( debug ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
                    fmt::print( "AST:\n" );
#endif
                    node->Apply( print_ast );
                }
                state.parts.insert( state.parts.end(), node->begin(), node->end() );
            }
            return std::distance( node->begin(), node->end() );
        };

        auto count = node_handler( state.parser.GetPartialParsedASTNodes() );

        state.content = end;
        ++state.content; // next line

        // special case: end of script with an if will produce a node here:
        if( not state.content.HasMore() ) {
            count += node_handler( state.parser.GetFinalPartialParsedASTNodes() );
        }

        if( not state.content.HasMore() ) {
            fmt::print( "<<< Script end reached. {} new statements parsed.\n", count );
        } else {
            fmt::print( "<<< {} new statements parsed.\n", count );
        }

    } catch( teascript::exception::runtime_error const &ex ) {
        fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
        teascript::util::pretty_print_colored( ex );
#else
        teascript::util::pretty_print( ex );
#endif
        state.parser.ClearState();
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not parse lines! {}\n", ex.what() );
    }
}

void exec_stmts( std::string const &count, PartialParseState &state, teascript::Context & rContext, bool silent )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to execute. Load a file first!" );
        }
        auto n = std::stoull( count );
        if( state.parts.size() < n ) {
            throw std::out_of_range( "Not enough statements available!" );
        }

        teascript::ValueObject res;
        
        while( n > 0 ) {
            res = state.parts.front()->Eval( rContext );
            state.parts.pop_front();

            --n;
        }
        if( not silent && res.HasPrintableValue() ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( "<<< {}\n", fmt::styled( res.PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
            fmt::print( "<<< {}\n", res.PrintValue() );
#endif
        }
        fmt::print( "<<< Statements executed. {} remaining statements.\n", state.parts.size() );
    } catch( teascript::control::Exit_Script const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( "<<< script exited with: {}\n", fmt::styled( ex.GetResult().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
        fmt::print( "<<< script exited with: {}\n", ex.GetResult().PrintValue() );
#endif
    } catch( teascript::control::Return_From_Function const &rReturn ) {
#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( "<<< script returned from main: {}\n", fmt::styled( rReturn.GetResult().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
        fmt::print( "<<< script returned from main: {}\n", rReturn.GetResult().PrintValue() );
#endif
    } catch( teascript::exception::runtime_error const &ex ) {
        fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
        teascript::util::pretty_print_colored( ex );
#else
        teascript::util::pretty_print( ex );
#endif
    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not execute statements! {}\n", ex.what() );
    }
}


// experimental, recursivley walking the AST and flatten it....
void build_flattened_ast( teascript::ASTNodePtr const &rNode, teascript::ASTNodeContainer &rFlattened )
{
    // is it a leaf?
    if( not rNode->HasChildren() ) {
        rFlattened.push_back( rNode );
    } else {
        // first the children...
        for( auto const &node : *rNode ) {
            build_flattened_ast( node, rFlattened );
        }
        // and our self at end. (always?)
        rFlattened.push_back( rNode );
    }
}


// experimental! builds flat ASTNode list and prints it.... (for debug and future development)
void print_flattened_ast( PartialParseState &state )
{
    try {
        if( state.filename.empty() ) {
            throw std::runtime_error( "Nothing to flatten. Load a file first!" );
        }

        teascript::ASTNodeContainer  flat;

        for( auto const &p : state.parts ) {
            build_flattened_ast( p, flat );
        }

#if TEASCRIPT_USE_FMT_COLOR
        fmt::print( fmt::fg( fmt::color::wheat ), "FLAT AST:\n" );
#else
        fmt::print( "FLAT AST:\n" );
#endif

        long long c = 0;
        for( auto const &node : flat ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( fmt::fg( fmt::color::wheat ), "{:04}: {}\n", ++c, fmt::styled( node->GetInfoStr(), fmt::fg( fmt::color::white_smoke ) ) );
#else
            fmt::print( "{:04}: {}\n", ++c, node->GetInfoStr() );
#endif
        }

    } catch( std::exception const &ex ) {
        fmt::print( "<<< Could not print flattened AST! {}\n", ex.what() );
    }
}


bool is_valid_core_config( unsigned int conf ) noexcept
{
    switch( conf & teascript::config::LevelMask ) {
    case teascript::config::LevelMinimal:
    case teascript::config::LevelCoreReduced:
    case teascript::config::LevelCore:
    case teascript::config::LevelUtil:
    case teascript::config::LevelFull:
        break;
    default:
        return false;
    }
    if( conf & 0xf0 ) { // this nibble is unused so far and must be 0.
        return false;
    }
    // Feature Opt-Out bits are handled lazily. It is allowed to set unknown bits. With this it is possible to disable everything just with 0xff'ff'ff'00
    return true;
}

teascript::config::eConfig parsecoreconfig( std::string const &s )
{
    if( not s.starts_with( "0x" ) ) {
        throw std::invalid_argument( "CoreLibrary config must be passed as hex string starting with 0x" );
    }
    unsigned int conf = static_cast<unsigned int>(std::stoul( s, nullptr, 16 ));
    if( is_valid_core_config( conf ) ) {
        return static_cast<teascript::config::eConfig>(conf);
    }
    throw std::invalid_argument( "CoreLibrary config is invalid!" );
}


// possible results of internal command dispatching
enum class CmdDispatched
{
    Unknown = 0,
    Dispatched,
    Exit
};

// internal command dispatch function.
CmdDispatched dispatch_cmd( std::string const &tmp, teascript::Parser &p, teascript::Context &context, PartialParseState &partial_state, bool &debug_mode, bool &silent, bool &batch_echo )
{
    if( tmp == ":debug" ) {
        debug_mode = !debug_mode;
        p.SetDebug( debug_mode );
        context.is_debug = debug_mode;
        partial_state.parser.SetDebug( debug_mode );
        if( batch_echo ) {
            fmt::print( "<<< debug mode={}.\n", debug_mode );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp == ":silent" ) {
        silent = not silent;
        if( batch_echo ) {
            fmt::print( "<<< silent={}.\n", silent );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp == ":reset" ) {
        p.ClearState();
        reload( partial_state );
        teascript::CoreLibrary().Bootstrap( context, teascript::config::LevelFull );
        if( batch_echo ) {
            fmt::print( "<<< state reset. ok.\n" );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":reset=" ) ) {
        teascript::config::eConfig conf = teascript::config::LevelFull;
        try {
            conf = parsecoreconfig( tmp.substr( strlen( ":reset=" ) ) );
        } catch( std::exception const &ex ) {
            fmt::print( "<<< Cannot parse :reset= {}\n", ex.what() );
            return CmdDispatched::Dispatched;
        }
        p.ClearState();
        reload( partial_state );
        teascript::CoreLibrary().Bootstrap( context, conf );
        if( batch_echo ) {
            fmt::print( "<<< state reset. ok.\n" );
        }
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":load=" ) ) {
        load_file( tmp.substr( strlen( ":load=" ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":show " ) ) {
        show_lines( tmp.substr( strlen( ":show " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":goto " ) ) {
        goto_pos( tmp.substr( strlen( ":goto " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":stmt " ) ) {
        show_stmt( tmp.substr( strlen( ":stmt " ) ), partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":parse " ) ) {
        parse_lines( tmp.substr( strlen( ":parse " ) ), partial_state, debug_mode );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":exec " ) ) {
        exec_stmts( tmp.substr( strlen( ":exec " ) ), partial_state, context, silent );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":flatten" ) ) {
        print_flattened_ast( partial_state );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":ls vars" ) {
        context.Dump();
        fmt::print( "\n" );
        return CmdDispatched::Dispatched;
    } else if( tmp.starts_with( ":search ") ) {
        context.Dump( tmp.substr( strlen( ":search " ) ) );
        fmt::print( "\n" );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":?" || tmp == ":help" ) {
        show_help();
        fmt::print( "\n" );
        return CmdDispatched::Dispatched;
    } else if( tmp == ":q" || tmp == ":quit" || tmp == ":exit" ) {
        fmt::print( "<<< bye.\n" );
        return CmdDispatched::Exit;
    }
    return CmdDispatched::Unknown;
}


bool teascript_readline( char const *prompt, std::string &tmp, std::ifstream * const pBatch = nullptr, bool * const pBatchEcho = nullptr )
{
    if( pBatch ) {
        // as long as we have more lines in a possible batch file, use them.
        if( std::getline( *pBatch, tmp ) ) {
            if( pBatchEcho ) {
                if( tmp.starts_with( "#echo off" ) ) { // only recognized in batch mode.
                    *pBatchEcho = false;
                }
            }
            if( pBatchEcho == nullptr || *pBatchEcho ) {
                fmt::print( "{}{}\n", prompt, tmp );
            }
            if( pBatchEcho ) {
                if( tmp.starts_with( "#echo on" ) ) { // only recognized in batch mode.
                    *pBatchEcho = true;
                }
            }
            return true;
        }

    }
#if TEASCRIPT_HAS_READLINE
    char *input = readline( prompt );
    if( not input ) {
        return false;
    }
    add_history( input ); // TODO: filter empty lines.
    tmp = std::string( input );
    return true;
#else
    fmt::print( "{}", prompt );
    bool const res = static_cast<bool>(std::getline( std::cin, tmp ));
    if( tmp.ends_with( '\r' ) ) { // temporary fix on Windows with injected streambuf in std::cin
        tmp.erase( tmp.size() - 1 );
    }
    return res;
#endif
}

void launch_interactiveshell_internal( teascript::Context &context, teascript::Parser &p, bool is_debug, bool is_silent, std::ifstream *const pBatch = nullptr )
{
#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::fg( fmt::color::wheat ), "\nWelcome to the interactive shell of {}, version {}.{}.{}!\n",
                teascript::self_name_str(), teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch() );
    fmt::print( fmt::emphasis::italic, "{}\n\n", copyright_info() );
    fmt::print( "For multi-line inputs the line must end with a \\ (backslash).\n"
                "An ongoing multi-line input will use the prompt \\>  instead of >>> .\n"
                "Type :? <enter> or :help <enter> for show a list of internal commands.\n"
                "Press Ctrl+C or type :q <enter> for quit the program.\n\n" );
#else
    fmt::print( "\nWelcome to the interactive shell of {}, version {}.{}.{}!\n"
                "{}\n\n"
                "For multi-line inputs the line must end with a \\ (backslash).\n"
                "An ongoing multi-line input will use the prompt \\>  instead of >>> .\n"
                "Type :? <enter> or :help <enter> for show a list of internal commands.\n"
                "Press Ctrl+C or type :q <enter> for quit the program.\n\n", teascript::self_name_str(),
                teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch(),
                copyright_info() );
#endif

#if TEASCRIPT_HAS_READLINE
#if TEASCRIPT_USE_FMT_COLOR
    fmt::print( fmt::emphasis::italic, "Info: Using GNU Readline Library.\n\n" );
#else
    fmt::print( "Info: Using GNU Readline Library.\n\n" );
#endif
#endif

    PartialParseState  partial_state;

    partial_state.parser.OverwriteDialect( context.dialect ); // use eventually modified dialect.
    partial_state.parser.SetDebug( is_debug );

    bool debug_mode = is_debug;
    bool silent = is_silent;
    bool batch_echo = true;
    std::string tmp;
    std::string content;
    char const *prompt = ">>> ";
    while( teascript_readline( prompt, tmp, pBatch, &batch_echo ) ) {
        if( tmp.ends_with( '\\' ) ) { // multi line input
            tmp.back() = '\n';
            content += tmp;
            prompt = "\\>  ";
            continue;
        }
        prompt = ">>> ";
        // first check for internal commands (starting with a colon : and at least one letter (or spelcial case :?) )
        if( tmp == ":" || tmp == ":?" || (tmp.starts_with(":") && tmp.size() > 1 && std::isalpha(static_cast<unsigned char>(tmp.at(1)))) ) {
            switch( dispatch_cmd( tmp, p, context, partial_state, debug_mode, silent, batch_echo ) ) {
            case CmdDispatched::Unknown:
                fmt::print( "<<< Unknown internal command.\n    Type :? <enter> or :help <enter> for show a list of internal commands.\n" );
                [[fallthrough]];
            case CmdDispatched::Dispatched:
                continue;
            case CmdDispatched::Exit:
                return;
            }
        }

        // normal dispatching as TeaScript code...
        content += tmp + '\n';

        try {
            auto ast = p.Parse( content );
            if( ast != nullptr ) {
                if( debug_mode ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
                    fmt::print( "AST:\n" );
#endif
                    ast->Apply( print_ast );
                }
                auto const result = ast->Eval( context );
                if( not silent && batch_echo && result.HasPrintableValue() ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( "<<< {}", fmt::styled( result.PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
                    fmt::print( "<<< {}", result.PrintValue() );
#endif
                } else if( batch_echo ) {
                    fmt::print( "<<< ok." );
                }
            } else {
                fmt::print( "Internal Error! Parser returned nullptr!" );
                p.ClearState();
            }
        } catch( teascript::control::Exit_Script const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( "<<< script exited with: {}", fmt::styled( ex.GetResult().PrintValue(), fmt::fg( fmt::color::light_sky_blue ) ) );
#else
            fmt::print( "<<< script exited with: {}", ex.GetResult().PrintValue() );
#endif
        } catch( teascript::exception::runtime_error const &ex ) {
            fmt::print( "<<< " );
#if TEASCRIPT_USE_FMT_COLOR
            teascript::util::pretty_print_colored( ex );
#else
            teascript::util::pretty_print( ex );
#endif
            p.ClearState();
        } catch( std::exception const &ex ) {
            fmt::print( "<<< {}", ex.what() );
            p.ClearState();
        }
        content.clear();
        if( batch_echo ) {
            fmt::print( "\n" );
        }
    }
}


struct Config
{
    enum class Mode
    {
        Unknown,
        Shell,
        Exec,
        Parse,
        PrintVersion,
        PrintParameters,
    } mode = Mode::Unknown;
    bool  debug = false;
    bool  interactive = false;
    bool  time_measure = false; // time measurement for Exec (Parse always does this)
    bool  old_mutable_parameters = false; // activates the old function parameters are mutable by default behavior (is only a temporary setting for transition)
    bool  batch = false; // batch processing of given batch file in interactive mode.
    teascript::config::eConfig  core_config = teascript::config::LevelFull;
    std::string filename;
    std::vector<std::string> script_args;
};

void launch_interactiveshell( Config const &rConf )
{
    bool silent      = false;     // TODO: read from Config.
    teascript::Context  context;
    teascript::Parser   p;
    teascript::Context  c;
    teascript::Dialect  d;
    d.parameters_are_default_const = not rConf.old_mutable_parameters;
    c.dialect = d;
    p.OverwriteDialect( d );
    if( rConf.debug ) {
        p.SetDebug( rConf.debug );
        c.is_debug = rConf.debug;
    }
    teascript::CoreLibrary().Bootstrap( context, rConf.core_config );

    std::ifstream  batch( rConf.filename );

    launch_interactiveshell_internal( context, p, rConf.debug, silent, rConf.filename.empty() ? nullptr : &batch);
}

void launch_interactiveshell_after_exec( teascript::Context &context, teascript::Parser &p, bool is_debug, bool is_silent )
{
    fmt::print( "\n\nScript executed, starting interactive shell...\n" );

    launch_interactiveshell_internal( context, p, is_debug, is_silent );
}


Config parse_parameters( std::vector<std::string> const &args )
{
    Config config;
    bool argmode = false;
    bool parameter_end = false;
    Config::Mode tmp = Config::Mode::Unknown;
    if( args.size() < 2 ) {
        config.mode = Config::Mode::Shell;
    }
    for( size_t i = 1; i < args.size(); ++i ) {
        if( argmode ) {
            config.script_args.emplace_back( args[i] );
        } else if( (args[i][0] != '-' && 0 != args[i].compare( "/?" )) || parameter_end ) {
            config.filename = args[i];
            argmode = true;
        } else if( 0 == args[i].compare( "--" ) ) {
            parameter_end = true; // next is filename, for can have filename with starting underscore.
        } else {
            if( 0 == args[i].compare( "-D" ) || 0 == args[i].compare( "--debug" ) ) {
                config.debug = true;
            } else if( 0 == args[i].compare( "-T" ) || 0 == args[i].compare( "--time" ) ) {
                config.time_measure = true;
            } else if( 0 == args[i].compare( "--printAST" ) ) {
                tmp = Config::Mode::Parse;
            } else if( args[i].starts_with( "--config=" ) ) {
                try {
                    config.core_config = parsecoreconfig( args[i].substr( strlen( "--config=" ) ) );
                } catch( std::exception const &ex ) { // bail out
                    fmt::print( "ERROR: Cannot parse --config param:\n{}\n", ex.what() );
                    config.mode = Config::Mode::Unknown;
                    break;
                }
            } else if( 0 == args[i].compare( "-i" ) || 0 == args[i].compare( "--interactive" ) ) {
                config.interactive = true;
            } else if( 0 == args[i].compare( "--batch" ) ) {
                config.batch = true;
                tmp = Config::Mode::Shell;
            } else if( 0 == args[i].compare( "--old-mutable-parameters" ) ) {
                config.old_mutable_parameters = true;
            } else if( 0 == args[i].compare( "-v" ) || 0 == args[i].compare( "--version" ) ) {
                tmp = Config::Mode::PrintVersion;
            } else if( 0 == args[i].compare( "-?" ) || 0 == args[i].compare( "-h" ) || 0 == args[i].compare( "/?" ) || 0 == args[i].compare( "--help" ) ) {
                tmp = Config::Mode::PrintParameters;
            }
        }

        if( argmode && config.mode == Config::Mode::Unknown ) {
            config.mode = Config::Mode::Exec;
        } else if( tmp != Config::Mode::Unknown ) {
            if( config.mode == Config::Mode::Unknown ) {
                config.mode = tmp;
                tmp = Config::Mode::Unknown;
            } else { // cannot have more than one mode! bail out!
                config.mode = Config::Mode::Unknown;
                break;
            }
        }
    }
    return config;
}


void print_version( )
{
    fmt::print( "\n{}, version {}.{}.{}.\n"
                "{}\n\n", teascript::self_name_str(),
                teascript::version::get_major(), teascript::version::get_minor(), teascript::version::get_patch(),
                copyright_info() );
}

void use_help( char const *s )
{
    fmt::print( "{}:\nUse -? or --help or /? for show a help message with valid parameters.\n", s );
}


void print_time( auto const t0, auto const t1, auto const t2, auto const t3 )
{
    fmt::print( "\nElapsed time: Total {:.8}, Bootstrap {:.8}, Parsing {:.8}, Eval {:.8}\n", 
                std::chrono::duration<double>(t3 - t0),
                std::chrono::duration<double>(t1 - t0),
                std::chrono::duration<double>(t2 - t1),
                std::chrono::duration<double>(t3 - t2) );
}


int main_teascript_exe( std::vector<std::string> const &args )
{
    Config const config = parse_parameters( args );
    if( Config::Mode::Shell == config.mode ) {
#ifdef _WIN32    
        signal( SIGINT, SIG_IGN ); // to remove spurious exit code -<very big number> when stop std::getline with Ctrl+C
#else //FIXME: check on Linux!
        //set_signal_handler();
#endif
        try {
            launch_interactiveshell( config );
        } catch( teascript::exception::runtime_error const &ex ) { // on early loading stages / bootsrapping there might escape an exception to here.
#if TEASCRIPT_USE_FMT_COLOR
            fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
            teascript::util::pretty_print_colored( ex );
#else
            fmt::print( "ERROR:\n" );
            teascript::util::pretty_print( ex );
#endif
            return EXIT_FAILURE;
        } catch( std::exception const &ex ) {
            fmt::print( "ERROR: {}\n", ex.what() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::Exec == config.mode ) {
        // NOTE: strings from config are UTF-8 here already!
        // TODO: apply include paths before try absolute()
        std::filesystem::path const script( std::filesystem::absolute( teascript::util::utf8_path( config.filename ) ) );
        std::ifstream file( script, std::ios::binary | std::ios::ate ); // ate jumps to end.
        if( file ) {
            auto size = file.tellg();
            file.seekg( 0 );
            std::vector<char> buf( static_cast<size_t>(size) + 1 ); // ensure zero terminating!
            file.read( buf.data(), size );

            // build utf-8 filename again... *grrr*
#if defined(_WIN32)
            auto const tmp_u8 = script.generic_u8string();
            std::string const filename_u8 = std::string( tmp_u8.begin(), tmp_u8.end() ); // must make a copy... :-(
#else
            // NOTE: On Windows it will be converted to native code page! Could be an issue when used as TeaScript string!
            std::string const filename_u8 = script.generic_string();
#endif

            auto const  t0 = std::chrono::steady_clock::now();

            teascript::Parser   p;
            teascript::Context  c;
            teascript::Dialect  d;
            d.parameters_are_default_const = not config.old_mutable_parameters;
            c.dialect = d;
            p.OverwriteDialect( d );
            if( config.debug ) {
                p.SetDebug( config.debug );
                c.is_debug = config.debug;
            }
            // bootstrapping the core library into context.
            teascript::CoreLibrary().Bootstrap( c, config.core_config );
            // set script arguments.
            if( not config.script_args.empty() ) {
                c.SetScriptArgs( config.script_args );
            }
            auto const  t1 = std::chrono::steady_clock::now();
            auto        t2 = t1;

            teascript::Content  content{buf.data(), buf.size()};
            try {
                auto  ast = p.Parse( content, filename_u8 ); // TODO: absolute path or filename?
                t2 = std::chrono::steady_clock::now();
                if( ast != nullptr ) {
                    auto const result = ast->Eval( c );
                    auto const     t3 = std::chrono::steady_clock::now();
                    if( result.HasPrintableValue() ) {
                        fmt::print( "{}", result.PrintValue() ); //NOTE: Intentionally always uncolored for easier parsing
                    }
                    if( config.time_measure ) {
                        print_time( t0, t1, t2, t3 );
                    }
                    if( config.interactive ) {
                        launch_interactiveshell_after_exec( c, p, config.debug, false );
                    } else {
                        return EXIT_SUCCESS;
                    }
                } else {
                    throw std::runtime_error( "Parser returned nullptr!" );
                }
            } catch( teascript::control::Exit_Script const &ex ) {
                auto const  t3 = std::chrono::steady_clock::now();
                fmt::print( "SCRIPT EXITED: {}", ex.GetResult().PrintValue() ); //NOTE: Intentionally always uncolored for easier parsing
                if( config.time_measure ) {
                    print_time( t0, t1, t2, t3 );
                }
                if( config.interactive ) {
                    launch_interactiveshell_after_exec( c, p, config.debug, false );
                } else {
                    long long const *code = ex.GetResult().GetValuePtr<long long>();
                    return code ? static_cast<int>(*code) : EXIT_FAILURE;
                }
            } catch( teascript::exception::runtime_error const &ex ) {
                std::string  src_overwrite;
                // no context string but we have location? try to extract from source...
                if( ex.GetContextStr().empty() && ex.IsSourceLocSet() && ex.GetFileStr() == filename_u8 ) {
                    try {
                        content.GoTo( ex.GetLine(), ex.GetColumn() );
                        src_overwrite = teascript::util::extract_current_line( content );
                    } catch( ... ) {
                    }
                }
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
                teascript::util::pretty_print_colored( ex, std::move( src_overwrite ) );
#else
                fmt::print( "ERROR:\n" );
                teascript::util::pretty_print( ex, std::move( src_overwrite ) );
#endif
                if( config.interactive ) {
                    launch_interactiveshell_after_exec( c, p, config.debug, false );
                } else {
                    return EXIT_FAILURE;
                }
            } catch( std::exception const &ex ) {
                fmt::print( "ERROR: {}\n", ex.what() );
                if( config.interactive ) {
                    launch_interactiveshell_after_exec( c, p, config.debug, false );
                } else {
                    return EXIT_FAILURE;
                }
            }
        } else {
            fmt::print( "ERROR: cannot open file {}\n", script.string() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::Parse == config.mode ) {
        // NOTE: strings from config are UTF-8 here already!
        // TODO: apply include paths before try absolute()
        std::filesystem::path const script( std::filesystem::absolute( teascript::util::utf8_path( config.filename ) ) );
        std::ifstream file( script, std::ios::binary | std::ios::ate ); // ate jumps to end.
        if( file ) {
            auto size = file.tellg();
            file.seekg( 0 );
            std::vector<char> buf( static_cast<size_t>(size) + 1 ); // ensure zero terminating!
            file.read( buf.data(), size );

            // build utf-8 filename again... *grrr*
#if defined(_WIN32)
            auto const tmp_u8 = script.generic_u8string();
            std::string const filename_u8 = std::string( tmp_u8.begin(), tmp_u8.end() ); // must make a copy... :-(
#else
            // NOTE: On Windows it will be converted to native code page! Could be an issue when used as TeaScript string!
            std::string const filename_u8 = script.generic_string();
#endif

            // we dont need a context here, just parse...
            teascript::Parser p;
            try {
                auto const  start = std::chrono::steady_clock::now();
                auto ast = p.Parse( {buf.data(), buf.size()}, filename_u8 );
                auto const  end = std::chrono::steady_clock::now();
                if( ast != nullptr ) {
#if TEASCRIPT_USE_FMT_COLOR
                    fmt::print( fmt::fg( fmt::color::wheat ), "AST:\n" );
#else
                    fmt::print( "AST:\n" );
#endif
                    ast->Apply( print_ast );
                    fmt::print( "\nparsing time: {}\n", std::chrono::duration_cast<std::chrono::microseconds>(end - start) );
                } else {
                    fmt::print( "Internal Error! Parser returned nullptr!" );
                    return EXIT_FAILURE;
                }
            } catch( teascript::exception::runtime_error const &ex ) {
#if TEASCRIPT_USE_FMT_COLOR
                fmt::print( fmt::fg( fmt::color::tomato ), "ERROR:\n" );
                teascript::util::pretty_print_colored( ex );
#else
                fmt::print( "ERROR:\n" );
                teascript::util::pretty_print( ex );
#endif
                return EXIT_FAILURE;
            } catch( std::exception const &ex ) {
                fmt::print( "<<< {}", ex.what() );
                return EXIT_FAILURE;
            }
        } else {
            fmt::print( "ERROR: cannot open file {}\n", script.string() );
            return EXIT_FAILURE;
        }
    } else if( Config::Mode::PrintVersion == config.mode ) {
        print_version();
        return EXIT_SUCCESS;
    } else if( Config::Mode::PrintParameters == config.mode ) {
        print_parameters();
        return EXIT_SUCCESS;
    } else /*if( config.mode == Config::Mode::Unknown )*/ {
        use_help( args[0].c_str() );
        return EXIT_FAILURE;
    }

#if 0
    if( true ) {
#if 0 // Check on Linux or remove.
    } else if( argc == 3 && 0 == strcmp( argv[1], "-c" ) ) {

        // NOTE: actually on windows you can pass a multiline argument with ^ at end. but it seems to be that it is not possible to pass them as one string.
        //       So for that we must allow arbitrary args and concat them all...
        teascript::Context  c;
        teascript::CoreLibrary().Bootstrap( c );
        teascript::Parser   p;
        try {
            auto ast = p.Parse( argv[2] );
            if( ast != nullptr ) {
                auto const result = ast->Eval( c );
                if( result.HasPrintableValue() ) {
                    fmt::print( "{}", result.PrintValue() );
                }
                return EXIT_SUCCESS;
            } else {
                throw std::runtime_error( "Parser returned nullptr!" );
            }
        } catch( std::exception const &ex ) {
            fmt::print( "ERROR: {}\n", ex.what() );
            return EXIT_FAILURE;
        }
#endif
    }
#endif

    puts( "END!" );

    return EXIT_SUCCESS;
}

std::string build_abs_exe_path( std::string const &argv1 )
{
    auto path = std::filesystem::absolute( teascript::util::utf8_path( argv1 ) );
#if defined(_WIN32)
    auto const tmp_u8 = path.generic_u8string();
    return std::string( tmp_u8.begin(), tmp_u8.end() ); // must make a copy ... :-(
#else
    (void)teascript::util::debug_print_currentline; // silence unused function warning.
    (void)teascript::util::pretty_print;            // silence unused function warning.
    (void)teascript::util::pretty_print_colored;    // silence unused function warning.
    return path.generic_string();
#endif
}

// On Windows we use wmain for get full Unicode support on the command line. 
// Convert UTF-16 args to UTF-8 and then call our main.
#if defined(_WIN32) && 1

std::string utf16toutf8( wchar_t const *str )
{
    int const  len = (int)::wcslen( str );
    int res = WideCharToMultiByte( CP_UTF8, 0, str, len, 0x0, 0, 0x0, 0x0 );
    if( res <= 0 ) {
        throw std::invalid_argument( "Cannot convert parameter to UTF-8. Please check proper encoding!" );
    }
    std::string  utf8;
    utf8.resize( res );
    res = WideCharToMultiByte( CP_UTF8, 0, str, len, utf8.data(), (int)utf8.size(), 0x0, 0x0 );
    if( res <= 0 ) {
        throw std::invalid_argument( "Cannot convert parameter to UTF-8. Please check proper encoding!" );
    }
    return utf8;
}

std::wstring utf8toutf16( char const *str, size_t const size )
{
    if( !str || !*str ) {
        return L"";
    }
    int const  len = (int)size;
    int res = MultiByteToWideChar( CP_UTF8, 0, str, len, 0x0, 0 );
    if( res <= 0 ) {
        return L"utf8toutf16() error!";
    }
    std::wstring  utf16;
    utf16.resize( res );
    res = MultiByteToWideChar( CP_UTF8, 0, str, len, utf16.data(), (int)utf16.size() );
    if( res <= 0 ) {
        return L"utf8toutf16() error!";
    }
    return utf16;
}


// the stream hooking was inspired from here:
// https://stackoverflow.com/questions/1660492/utf-8-output-on-windows-console
// NOTE: currently only the input hook is used. Output is handled by libfmt.

class Win32Console_OutStream_Utf8ToUtf16Buffer : public std::stringbuf
{
    HANDLE mHandle;
    std::string mBacklog;

    static size_t count_unfinished_utf8_bytes( const std::string &s )
    {
        constexpr unsigned char utf8_Continuation_Prefix = 0x80;  // 10xx xxxx
        constexpr unsigned char utf8_Continuation_Mask   = 0xC0;  // 1100 0000

        if( s.empty() ) {
            return 0;
        }
        auto rit = s.rbegin();
        unsigned char const last = *rit;
        if( last <= 0x7F ) { // good ol' ASCII ...
            return 0;
        }
        // assume we only have valid UTF-8, then everything greater the max continuation char is a leading char.
        if( last > 0xBF ) {
            return 1;
        }

        // normally this is covered by the above checks already...
        if( (last & utf8_Continuation_Mask) != utf8_Continuation_Prefix ) {
            return 0;
        }

        while( rit != s.rend() && (*rit & utf8_Continuation_Mask) != utf8_Continuation_Prefix ) {
            ++rit;
        }

        if( rit == s.rend() ) {
            return s.size();
        }

        unsigned char const  c = *rit;
        size_t        const  expected = c >= 0xF0 ? 3 : c >= 0xE0 ? 2 : c >= 0xC0 ? 1 : 0;
        size_t        const  actual   = std::distance( s.rbegin(), rit );
        if( actual == expected ) {
            return 0; // is complete!
        }

        return actual;
    }
    
public:
    Win32Console_OutStream_Utf8ToUtf16Buffer() 
        : mHandle( ::GetStdHandle( STD_OUTPUT_HANDLE ) )
    {}

    int sync() override
    {
        // use backlog with new data...
        std::string buffer = mBacklog + str();
        // count unfinished utf8 bytes
        size_t const  unfinished_utf8_bytes = count_unfinished_utf8_bytes( buffer );
        // copy them to backlog for next call.
        mBacklog = std::string( buffer.end() - unfinished_utf8_bytes, buffer.end() );

        // now we can clear the input.
        str( "" );

        // cut off the unfinisehed data.
        buffer.erase( buffer.size() - unfinished_utf8_bytes );
        if( buffer.empty() ) {
            return 0;
        }

        std::wstring const utf16 = utf8toutf16( buffer.c_str(), buffer.size() );
        DWORD written = 0;
        ::WriteConsoleW( mHandle, utf16.c_str(), static_cast<DWORD>( utf16.size() ), &written, 0x0 );

        return 0;
    }
 
#if 0
    Win32Console_OutStream_Utf8ToUtf16Buffer::int_type overflow( int_type c ) override
    {
        mBacklog += traits_type::to_char_type( c );
        return traits_type::not_eof( c );
    }
#endif
};

template< typename T > requires std::derived_from< T, std::streambuf >
class Win32Console_OutStream_Overwrite
{
public:
    Win32Console_OutStream_Overwrite()
        : mOld( nullptr )
    {
        if( ::GetFileType( ::GetStdHandle( STD_OUTPUT_HANDLE ) ) == FILE_TYPE_CHAR ) {
            std::cout.flush();
            mStreamBuf = std::make_unique<T>();
            mOld = std::cout.rdbuf();
            std::cout.rdbuf( mStreamBuf.get() );
        }
    }
    ~Win32Console_OutStream_Overwrite()
    {
        if( mOld ) {
            std::cout.flush();
            std::cout.rdbuf( mOld );
        }
    }
private:
    std::streambuf     *mOld;
    std::unique_ptr<T>  mStreamBuf;;
};

class Win32Console_InStream_Utf16ToUtf8Buffer : public std::streambuf
{
    std::string mBuffer;
    HANDLE      mHandle;
public:
    Win32Console_InStream_Utf16ToUtf8Buffer() 
        : mHandle( ::GetStdHandle( STD_INPUT_HANDLE ) )
    {
        setg( 0, 0, 0 );
    }
private:
    int_type underflow() override
    {
        if( gptr() >= egptr() ) {
            wchar_t  wbuf[128] = L"\0";
            DWORD read = 0;
            if( !::ReadConsoleW( mHandle, wbuf, ARRAYSIZE( wbuf ) - 2, &read, 0x0 ) ) {
                return traits_type::eof();
            }
            if( read > 0 && IS_HIGH_SURROGATE( wbuf[read - 1] ) ) {
                // try to read one more character
                DWORD extra = 0;
                if( ::ReadConsoleW( mHandle, wbuf + read, 1, &extra, NULL ) && extra == 1 ) {
                    ++read;
                }
            }

            wbuf[read] = L'\0'; // ensure zero terminated.
            // convert to UTF-8
            mBuffer = utf16toutf8( wbuf );
            // set the UTF-8 buffer as new content for read.
            setg( mBuffer.data(), mBuffer.data(), mBuffer.data() + mBuffer.size());

            if( gptr() >= egptr() ) {
                return traits_type::eof();
            }
        }

        return sgetc();
    }
};

template< typename T > requires std::derived_from< T, std::streambuf >
class Win32Console_InStream_Overwrite
{
public:
    Win32Console_InStream_Overwrite()
        : mOld( nullptr )
    {
        // only do if stdin is connected to a console/terminal.
        if( ::GetFileType( ::GetStdHandle( STD_INPUT_HANDLE ) ) == FILE_TYPE_CHAR ) {
            mStreamBuf = std::make_unique<T>();
            mOld = std::cin.rdbuf( mStreamBuf.get() );
            
        }
    }
    ~Win32Console_InStream_Overwrite()
    {
        if( mOld ) {
            std::cin.rdbuf( mOld );
        }
    }
private:
    std::unique_ptr<T>  mStreamBuf;
    std::streambuf     *mOld;
};


int wmain( int argc, wchar_t **argv )
{
    std::vector<std::string> args;
    try {
        args.reserve( argc );
        args.emplace_back( build_abs_exe_path( utf16toutf8( argv[0] ) ) );
        for( int i = 1; i < argc; ++i ) {
            args.emplace_back( utf16toutf8( argv[i] ) );
        }
    } catch( std::exception const &ex ) {
        std::cerr << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    Win32Console_InStream_Overwrite< Win32Console_InStream_Utf16ToUtf8Buffer >   make_std_cin_read_utf8;
    // NOTE: Unfortunately, this does not cover the echo from std::getline( std::cin,...) !!! :-(
    //       I opened a bug report for the broken echo, but it seems like MS is not gonna fix it ...
    //       https://developercommunity.visualstudio.com/t/Bug-in-WIN32-API-ReadConsoleW-for-UTF-16/10252965
    //Win32Console_OutStream_Overwrite< Win32Console_OutStream_Utf8ToUtf16Buffer > make_std_cout_print_utf8;

    // check for correct console mode for be able to do colorized output (old conhost.exe needs this)
#if TEASCRIPT_USE_FMT_COLOR
    DWORD mode = 0;
    if( not ::GetConsoleMode( ::GetStdHandle( STD_OUTPUT_HANDLE ), &mode ) ) {
        std::cerr << "Warning: Console mode could not be detected. Colorized output might produce garbage on screen." << std::endl;
    } else if( not (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ) {
        if( not ::SetConsoleMode( ::GetStdHandle( STD_OUTPUT_HANDLE ), mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT ) ) {
            std::cerr << "\nERROR! Colorized output could not be activated for your console!\n" 
                      << "Colorized output may produce garbage on the screen.\n" 
                      << "Please, use a modern Console Host (e.g., Windows Terminal) or\n" 
                      << "compile with TEASCRIPT_USE_FMT_COLOR set to 0.\n" << std::endl;
        }
    }
#endif

    return main_teascript_exe( args );
}
#else // On Linux/Unix-like we just build the args vector. strings are UTF-8 already.
int main( int argc, char **argv )
{
    std::vector<std::string> args;
    try {
        args.reserve( argc );
        args.emplace_back( build_abs_exe_path( argv[0] ) );
        for( int i = 1; i < argc; ++i ) {
            args.emplace_back( argv[i] );
        }
    } catch( std::exception const &ex ) {
        std::cerr << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    return main_teascript_exe( args );
}
#endif

