#pragma once

#include "../buffer/BufferDecoder.h"
#include "../buffer/BufferEncoder.h"

//(metaprogramming helper equivalent to std::conditional)
template<bool Condition, class First, class Second> struct TernaryOperator {};
template<class First, class Second> struct TernaryOperator<true, First, Second> { typedef First Type; };
template<class First, class Second> struct TernaryOperator<false, First, Second> { typedef Second Type; };

//possible formats of input/output data
enum DataFormat {
    dfUtf8,         // UTF-8
    dfUtf16,        // UTF-16LE
    dfUtf32,        // UTF-32LE
    dfUtfCount      //(helper)
};


//modes of conversion (synchronized with EncoderMode and DecoderMode)
//see full explanations in ProcessorSelector description
enum ConversionMode {
    cmFast = 0,     // only fast path: converts limited set of code points
    cmFull = 1,     // fast + slow path: converts any correct input
    cmValidate = 2  // any input allowed (includes validation)
};

// This selector can be used to get type of processor by options, e.g.:
//   typedef ProcessorSelector<dfUtf8, dfUtf16>::WithOptions<cmValidate, 2>::Processor MyProcessor;
//   BaseBufferProcessor *processor = new MyProcessor();
//   ... now you can pass this processor e.g. to functions from MessageConverter.h
//
// For each choice of template arguments, a separately optimized code is generated by the compiler.
// In order to avoid template bloat, you can choose the set of all configurations you might need,
// and write a factory function for them (similar to GenerateProcessor function in AllProcessors.cpp).
//
// The conversion itself is defined by two arguments:
//   SrcFormat: defines how the input data is encoded
//   DstFormat: defines desired encoding of the output data
// Both of these arguments are chosen from DataFormat enum (must be dfUtf8, dfUtf16, or dfUtf32).
//
// The other arguments specify supported inputs and performance tradeoffs.
// The processor usually contains two code paths: the FAST path and the SLOW path.
// The fast path works only on a limited set of code points, while the slow path can process any data.
// To maintain high overall performance, fast path should be able to process more than 99% of input code points.
//
// Fast path supports a code point if its representation in UTF-8 consists of at most MaxBytes bytes.
// Therefore, fast path supports the following set of characters (depending on MaxBytes argument):
//   MaxBytes = 1:  U+0000 .. U+007F            all ASCII characters
//   MaxBytes = 2:  U+0000 .. U+07FF            ASCII + latin extensions, cyrillic, and much more
//   MaxBytes = 3:  U+0000 .. U+FFFF            the whole Basic Multilingual Plane
//     (except surrogate code points U+D800 .. U+DFFF)
// 
// The Mode arguments controls what happens when code points unsupported by fast path are met:
//   Mode == cmFast:
//     Only fast path is included, no slow path at all.
//     The input data must be correctly encoded, and all code points must be within supported range of fast path.
//     Beware: incorrect encoding or unsupported code point in input results in Undefined Behavior.
//   Mode == cmFull:
//     Slow path is included to support the whole set of Unicode code points, but NO validation is done in fast path.
//     The input data must be correctly encoded, and may contain all valid code points.
//     Beware: incorrect encoding of the input results in Undefined Behavior.
//   Mode == cmValidate:
//     Slow path is included to support all code points, fast path includes full validation of the input.
//     The input data may be an arbitrary byte array, the processor should always behave properly.
//     In case of encoding error, the processor stops by default (error correction is discussed below).
// NOTE: If the input comes from untrusted source, then using cmFast and cmFull modes is a serious security issue!
// Prefer cmValidate mode in general.
// 
// The SpeedMult argument is a small tweak which might improve performance on modern machines.
//   SpeedMult = 1:
//     Process input sequentally, reading a 16-byte chunk on each iteration.
//   SpeedMult = 4:
//     from UTF-8: Use 4 interleaved streams of processing, thus emulating Hyper-threading in a single thread.
//      to  UTF-8: Use 4x loop unrolling in the innermost loop.
// Note that using multi-stream processor is a bit more complicated, e.g. because it has four output buffers instead of one.
// Also, it does not support error correction.
//
// If Mode == cmValidate, then it is possible to enable error correction callback (using SetErrorCallback method).
// This callback is called when it becomes impossible to process the next code point.
// It may adjust input/output data and pointers and ask to continue processing, or confirm that conversion must be stopped.
// Two ready-to-use callbacks are included: skipping code units or replacing them with 0xFFFD replacement code point.
// Note that error correction works even slower than the slow path, and it won't work in cmFast and cmFull modes.
//
// Here is an example of enabling the 'skip-code-unit' error correction:
//   typedef ProcessorSelector<dfUtf8, dfUtf16> ConversionDirection;
//   typedef ConversionDirection::WithOptions<cmValidate, 2>::Processor MyProcessor;
//   BaseBufferProcessor *processor = new MyProcessor();
//   int errorsCount = 0;       //number of errors maintained here
//   processor->SetErrorCallback(&ConversionDirection::OnErrorMissCodeUnits, &errorsCount);
// Alternatively, you can create processor with 'replace-with-0xfffd' error correction like this:
//   typedef ProcessorSelector<dfUtf8, dfUtf16>::WithOptions<cmValidate, 2>::Processor MyProcessor;
//   int errorsCount = 0;
//   BaseBufferProcessor *processor = MyProcessor::Create(&errorsCount);
//
template<int SrcFormat, int DstFormat>
struct ProcessorSelector {
    static_assert(SrcFormat >= 0 && SrcFormat < dfUtfCount, "Unsupported format");
    static_assert(DstFormat >= 0 && DstFormat < dfUtfCount, "Unsupported format");
    static_assert((SrcFormat == dfUtf8) != (DstFormat == dfUtf8), "Supported only conversions: from UTF-8 or to UTF-8");

    template<int Mode = cmValidate, int MaxBytes = 3, int SpeedMult = 1>
    struct WithOptions {
        // Type of corresponding processor, can be created as "new ...::Processor();"
        typedef typename TernaryOperator<SrcFormat == dfUtf8,
            BufferDecoder<MaxBytes, (DstFormat == dfUtf32 ? 4 : 2), Mode, SpeedMult>,
            BufferEncoder<MaxBytes, (SrcFormat == dfUtf32 ? 4 : 2), Mode, SpeedMult>
        >::Type Processor;

        // A helper for creating processor with error correction or without it.
        // In any case: a new processor is created and returned.
        // If errorCounter is not null, then error correction is additionally enabled:
        // OnErrorSetReplacementChars callback is installed, and number of fixed errors is maintained in *errorCounter.
        static Processor* Create(int *errorCounter = 0);
    };

    // Some ready-to-use callbacks which can be installed for error correction (use BaseBufferProcessor::SetErrorCallback).
    //   code units are simply skipped until conversion can be continued
    static bool OnErrorMissCodeUnits(void *context, const char *&srcBuffer, int srcBytes, char *&dstBuffer, int dstBytes);
    //   code units are replaced with 0xFFFD code points until conversion can be continued
    static bool OnErrorSetReplacementChars(void *context, const char *&srcBuffer, int srcBytes, char *&dstBuffer, int dstBytes);
};



//==============================
// implementation of some stuff
//==============================

FORCEINLINE int GetUnitSizeOfFormat(int format) { return format == dfUtf8 ? 1 : format == dfUtf16 ? 2 : 4; }

template<int SrcFormat, int DstFormat>
bool ProcessorSelector<SrcFormat, DstFormat>::OnErrorMissCodeUnits(
    void *context, const char *&srcBuffer, int srcBytes, char *&dstBuffer, int dstBytes
) {
    (void)dstBytes; // otherwise it is unused
    (void)dstBuffer; // otherwise it is unused
    (void)srcBytes; // otherwise it is unused 
    srcBuffer += GetUnitSizeOfFormat(SrcFormat);
    if (context) {
        int &counter = *(int*)context;
        counter++;
    }
    return true;
}

template<int SrcFormat, int DstFormat>
bool ProcessorSelector<SrcFormat, DstFormat>::OnErrorSetReplacementChars(
    void *context, const char *&srcBuffer, int srcBytes, char *&dstBuffer, int dstBytes
) {
    (void)srcBytes;
    if (DstFormat == dfUtf8) {
        if (dstBytes < 3) return false;
        *dstBuffer++ = (char)0xEF;
        *dstBuffer++ = (char)0xBF;
        *dstBuffer++ = (char)0xBD;
    }
    else if (DstFormat == dfUtf16) {
        if (dstBytes < 2) return false;
        *dstBuffer++ = (char)0xFD;
        *dstBuffer++ = (char)0xFF;
    }
    else if (DstFormat == dfUtf32) {
        if (dstBytes < 4) return false;
        *dstBuffer++ = (char)0xFD;
        *dstBuffer++ = (char)0xFF;
        *dstBuffer++ = (char)0x00;
        *dstBuffer++ = (char)0x00;
    }
    srcBuffer += GetUnitSizeOfFormat(SrcFormat);
    if (context) {
        int &counter = *(int*)context;
        counter++;
    }
    return true;
}

template<int SrcFormat, int DstFormat>
template<int Mode, int MaxBytes, int SpeedMult>
typename ProcessorSelector<SrcFormat, DstFormat>::template WithOptions<Mode, MaxBytes, SpeedMult>::Processor *
ProcessorSelector<SrcFormat, DstFormat>::WithOptions<Mode, MaxBytes, SpeedMult>::Create(int *errorCounter) {
    Processor *result = new Processor();
    if (errorCounter)
        result->SetErrorCallback(OnErrorSetReplacementChars, errorCounter);
    return result;
}
