
import libc;
import platform;
import printer.platform.(
    formatForInt64, formatForUInt64,
    formatForPartialInt128,
);


//
// printTo
//

define printTo;

inline overload printTo(stream) {
}

inline overload printTo(stream, a, b, ..rest) {
    ..for (x in a, b, ..rest)
        printTo(stream, x);
}



//
// printTo - Bool
//

overload printTo(stream, x:Bool) {
    if (x)
        printTo(stream, "true");
    else
        printTo(stream, "false");
}



//
// printTo - integers, floats, pointers
//

[I | SignedInteger?(I)]
overload printTo(stream, x:I) {
    printWithSprintf(stream, "%d", Int(x));
}

[I | UnsignedInteger?(I)]
overload printTo(stream, x:I) {
    printWithSprintf(stream, "%u", UInt(x));
}

overload printTo(stream, x:Int64) {
    printWithSprintf(stream, formatForInt64, x);
}

overload printTo(stream, x:UInt64) {
    printWithSprintf(stream, formatForUInt64, x);
}

overload printTo(stream, x:Int128) {
    if (x >= Least(Int64) and x <= Greatest(Int64))
        printTo(stream, Int64(x));
    else {
        var lo = Int64(x % 1_000_000_000_000_000_000ll);
        var loabs = UInt64(if (lo < 0) -lo else lo);
        var hi = x / 1_000_000_000_000_000_000ll;

        printTo(stream, hi);
        printWithSprintf(stream, formatForPartialInt128, loabs);
    }
}

overload printTo(stream, x:UInt128) {
    if (x <= Greatest(UInt64))
        printTo(stream, UInt64(x));
    else {
        var lo = UInt64(x % 1_000_000_000_000_000_000ull);
        var hi = x / 1_000_000_000_000_000_000ull;

        printTo(stream, hi);
        printWithSprintf(stream, formatForPartialInt128, lo);
    }
}


overload printTo(stream, x:Complex80) {
    printWithSprintf(stream, "%.19Lg%+.19Lgj", ..rectValues(x));
}

overload printTo(stream, x:Complex64) {
    printWithSprintf(stream, "%.16g%+.16gj", ..rectValues(x));
}

overload printTo(stream, x:Complex32) {
    printWithSprintf(stream, "%.16g%+.16gj", ..mapValues(Double, ..rectValues(x)));
}

overload printTo(stream, x:Imag80) {
    printWithSprintf(stream, "%.19Lgj", x);
}

overload printTo(stream, x:Imag64) {
    printWithSprintf(stream, "%.16gj", x);
}

overload printTo(stream, x:Imag32) {
    printWithSprintf(stream, "%.8gj", Imag64(x));
}

overload printTo(stream, x:LongDouble) {
    printWithSprintf(stream, "%.19Lg", x);
}

overload printTo(stream, x:Double) {
    printWithSprintf(stream, "%.16g", x);
}
overload printTo(stream, x:Float) {
    printWithSprintf(stream, "%.8g", Double(x));
}

[T]
overload printTo(stream, x:Pointer[T]) {
    printWithSprintf(stream, "%p", x);
}
[I, O]
overload printTo(stream, x:CodePointer[I, O]) {
    printWithSprintf(stream, "%p", x);
}
[CP | CCodePointer?(CP)]
overload printTo(stream, x:CP) {
    printWithSprintf(stream, "%p", x);
}

printWithSprintf(stream, formatString, ..values) {
    var buf = Array[CChar, 100]();
    libc.sprintf(begin(buf), cstring(formatString), ..values);
    var n = libc.strlen(begin(buf));
    var ptr = Pointer[Byte](begin(buf));
    write(stream, ptr, SizeT(n));
}



//
// printTo - statics
//

[X]
overload printTo(stream, x:Static[X]) {
    printTo(stream, StaticName(x));
}

[X | Identifier?(X)]
overload printTo(stream, x:Static[X]) {
    printTo(stream, StaticName(x));
}


//
// printTo - enums
//

[E | Enum?(E)]
overload printTo(stream, x:E) {
    printTo(stream, enumName(x));
}



//
// printTo - tuples, records, arrays, vectors
//

[..T]
overload printTo(stream, x:Tuple[..T]) {
    printTo(stream, "[", ..weaveValues(", ", ..unpackTuple(x)), "]");
}

[T | Record?(T)]
overload printTo(stream, x:T) {
    printTo(stream, T, "(", ..weaveValues(", ", ..recordFields(x)), ")");
}

printSequenceTo(stream, xs) {
    printTo(stream, "{");
    interleave(xs,
        () -> { printTo(stream, ", "); },
        x  -> { printTo(stream, x);    },
    );
    printTo(stream, "}");
}

[T,n]
overload printTo(stream, xs:Array[T,n]) {
    printSequenceTo(stream, xs);
}

[T]
overload printTo(stream, xs:Vector[T]) {
    printSequenceTo(stream, xs);
}



//
// printTo - variants
//

[T | Variant?(T)]
overload printTo(stream, x:T) {
    printTo(stream, *x);
}



//
// printTo - Char, String?, ContiguousString?
//

overload printTo(stream, x:Char) {
    write(stream, Pointer[Byte](&x), SizeT(1));
}

[S | String?(S)]
overload printTo(stream, x:S) {
    for (c in x)
        write(stream, c);
}

[S | ContiguousString?(S)]
overload printTo(stream, x:S) {
    write(stream, x);
}



//
// printTo - SharedPointer
//

[T]
overload printTo(stream, x:SharedPointer[T]) {
    printTo(stream, SharedPointer[T], "(");
    if (not null?(x))
        printTo(stream, x^);
    printTo(stream, ")");
}



//
// printTo - reference types
//

[T | ReferenceType?(T)]
overload printTo(stream, x:T) {
    if (null?(x)) {
        printTo(stream, "null");
        return;
    }
    printTo(stream, T, "(", ..weaveValues(", ", ..referenceTypeFields(x)), ")");
}



//
// printTo - HashMap
//

[K,V]
overload printTo(stream, x:HashMap[K,V]) {
    printReprTo(stream, x);
}



//
// printReprTo
//

define printReprTo;



//
// printReprTo - Bool, Numeric?, Pointer[T], Enum

overload printReprTo(stream, x:Bool) {
    printTo(stream, x);
}

overload printReprTo(stream, x:Int8) {
    printTo(stream, x, "ss");
}

overload printReprTo(stream, x:Int16) {
    printTo(stream, x, "s");
}

overload printReprTo(stream, x:Int32) {
    printTo(stream, x, "i");
}

overload printReprTo(stream, x:Int64) {
    printTo(stream, x, "l");
}

overload printReprTo(stream, x:Int128) {
    printTo(stream, x, "ll");
}

overload printReprTo(stream, x:UInt8) {
    printTo(stream, x, "uss");
}

overload printReprTo(stream, x:UInt16) {
    printTo(stream, x, "us");
}

overload printReprTo(stream, x:UInt32) {
    printTo(stream, x, "u");
}

overload printReprTo(stream, x:UInt64) {
    printTo(stream, x, "ul");
}

overload printReprTo(stream, x:UInt128) {
    printTo(stream, x, "ull");
}

overload printReprTo(stream, x:Float32) {
    printWithSprintf(stream, "%.6af", Float64(x));
}

overload printReprTo(stream, x:Float64) {
    printWithSprintf(stream, "%.13a", x);
}

// Windows, Linux, and MacOS X printf format %La as `0x8.0000_0000_0000_000`
// FreeBSD printf formats %La as `0x1.0000_0000_0000_0000`
overload printReprTo(stream, x:Float80) {
    printWithSprintf(stream, "%.15Lafl", x);
}

[| platform.OS == platform.FreeBSD]
overload printReprTo(stream, x:Float80) {
    printWithSprintf(stream, "%.16Lafl", x);
}

overload printReprTo(stream, x:Imag32) {
    printWithSprintf(stream, "%.6afj", Imag64(x));
}

overload printReprTo(stream, x:Imag64) {
    printWithSprintf(stream, "%.13aj", x);
}

overload printReprTo(stream, x:Imag80) {
    printWithSprintf(stream, "%.15Laflj", x);
}

[| platform.OS == platform.FreeBSD]
overload printReprTo(stream, x:Imag80) {
    printWithSprintf(stream, "%.16Lafl", x);
}

overload printReprTo(stream, x:Complex80) {
    printWithSprintf(stream, "%.15Lafl%+.15Laflj", ..rectValues(x));
}

[| platform.OS == platform.FreeBSD]
overload printReprTo(stream, x:Complex80) {
    printWithSprintf(stream, "%.16Lafl%+.16Laflj", ..rectValues(x));
}

overload printReprTo(stream, x:Complex64) {
    printWithSprintf(stream, "%.13a%+.13aj", ..rectValues(x));
}

overload printReprTo(stream, x:Complex32) {
    printWithSprintf(stream, "%.6af%+.6afj", ..mapValues(Double, ..rectValues(x)));
}

[T]
overload printReprTo(stream, x:Pointer[T]) {
    printTo(stream, Pointer[T], "(");
    printTo(stream, x);
    printTo(stream, ")");
}

[E | Enum?(E)]
overload printReprTo(stream, x:E) {
    printTo(stream, x);
}


//
// printReprTo - statics
//

[X]
overload printReprTo(stream, x:Static[X]) {
    printTo(stream, StaticName(x));
}

[X | Identifier?(X)]
overload printReprTo(stream, x:Static[X]) {
    printTo(stream, '#');
    printReprTo(stream, StaticName(x));
}


//
// printReprTo - tuples
//

[..T]
overload printReprTo(stream, x:Tuple[..T]) {
    printTo(stream, "[");
    printReprValuesTo(stream, ..tupleElements(x));
    printTo(stream, "]");
}


//
// printReprTo - records
//

[T | Record?(T)]
overload printReprTo(stream, x:T) {
    printTo(stream, T);
    printReprArgumentsTo(stream, ..recordFields(x));
}



//
// printReprValuesTo, printReprArgumentsTo, printReprElementsTo
//

printReprValuesTo(stream, value, ..values) {
    printReprTo(stream, value);
    ..for (y in values) {
        printTo(stream, ", ");
        printReprTo(stream, y);
    }
}

overload printReprValuesTo(stream) { }

printReprArgumentsTo(stream, ..values) {
    printTo(stream, "(");
    printReprValuesTo(stream, ..values);
    printTo(stream, ")");
}

printReprElementsTo(stream, seq) {
    interleave(seq,
        () -> { printTo(stream, ", "); },
        x  -> { printReprTo(stream, x); },
    );
}


//
// printReprTo - arrays, vectors
//

[T,n]
overload printReprTo(stream, a:Array[T,n]) {
    printTo(stream, Array[T,n], '(');
    printReprElementsTo(stream, a);
    printTo(stream, ')');
}

[T]
overload printReprTo(stream, a:Vector[T]) {
    printTo(stream, Vector[T], '(');
    printReprElementsTo(stream, a);
    printTo(stream, ')');
}



//
// printReprTo - variants
//

[T | Variant?(T)]
overload printReprTo(stream, x:T) {
    printTo(stream, T, "(");
    printReprTo(stream, *x);
    printTo(stream, ")");
}



//
// printReprTo - Char, String
//

printCharReprTo(stream, c:Char) {
    switch (c)
    case ('\0')
        printTo(stream, "\\0");
    case ('\n')
        printTo(stream, "\\n");
    case ('\r')
        printTo(stream, "\\r");
    case ('\t')
        printTo(stream, "\\t");
    case ('\f')
        printTo(stream, "\\f");
    case ('\\')
        printTo(stream, "\\\\");
    case ('\'')
        printTo(stream, "\\'");
    case ('\"')
        printTo(stream, "\\\"");
    case ('\$')
        printTo(stream, "\\\$");
    else if (c >= '\x20' and c <= '\x7E')
        printTo(stream, c);
    else
        printWithSprintf(stream, "\\x%02X", UInt8(c));
}

overload printReprTo(stream, c:Char) {
    printTo(stream, '\'');
    printCharReprTo(stream, c);
    printTo(stream, '\'');
}

[S | String?(S)]
overload printReprTo(stream, s:S) {
    printTo(stream, '"');
    for (c in s)
        printCharReprTo(stream, c);
    printTo(stream, '"');
}



//
// printReprTo - SharedPointer
//

[T]
overload printReprTo(stream, x:SharedPointer[T]) {
    // FIXME: handle cycles
    printTo(stream, SharedPointer[T], "(");
    if (not null?(x))
        printReprTo(stream, x^);
    printTo(stream, ")");
}



//
// printReprTo - reference types
//

[T | ReferenceType?(T)]
overload printReprTo(stream, x:T) {
    // FIXME: handle cycles
    if (null?(x)) {
        printTo(stream, "null");
        return;
    }
    printTo(stream, T);
    printReprArgumentsTo(stream, ..referenceTypeFields(x));
}



//
// printReprTo - HashMap
//

[K, V]
overload printReprTo(stream, x:HashMap[K,V]) {
    printTo(stream, HashMap[K,V], '(');
    var first = true;
    for (k, v in items(x)) {
        if (first)
            first = false;
        else
            printTo(stream, ", ");
        printReprArgumentsTo(stream, k, v);
    }
    printTo(stream, ')');
}


//
// printReprTo - UniqueValue
//

[T]
overload printReprTo(stream, v:UniqueValue[T]) {
    if (null?(v))
        printTo(stream, UniqueValue[T], "()");
    else {
        printTo(stream, UniqueValue[T], "(");
        printReprTo(stream, v^);
        printTo(stream, ")");
    }
}

[T]
overload printTo(stream, v:UniqueValue[T]) {
    if (null?(v))
        printTo(stream, UniqueValue[T], "()");
    else {
        printTo(stream, UniqueValue[T], "(");
        printTo(stream, v^);
        printTo(stream, ")");
    }
}


//
// print, println, printlnTo
//

inline print(..x) {
    printTo(stdout, ..x);
}

inline println(..x) {
    printTo(stdout, ..x, '\n');
}

inline printlnTo(stream, ..x) {
    printTo(stream, ..x, '\n');
}


//
// str, strln
//

str(forward ..x) {
    var s = String();
    printTo(s, ..x);
    return move(s);
}


//
// printBuffer
//

[S | ByteBuffer?(S)]
printBuffer(buf:S, ..args) {
    var bv = BufferVector(begin(buf), end(buf));
    printTo(bv, ..args);
    return size(bv);
}



//
// observe, observeTo, observeCall -- log intermediate values and forward them
//

observeTo(stream, forward ..x) {
    var captured = captureValues(..x);
    printlnTo(stream, "[observed: ", ..weaveValues(", ", ..capturedRefs(captured)), "]");
    flush(stream);
    return forward ..forwardValues(captured);
}
[tag | Identifier?(tag)]
overload observeTo(stream, static tag, forward ..x) {
    var captured = captureValues(..x);
    printlnTo(stream, "[observed ", tag, ": ", ..weaveValues(", ", ..capturedRefs(captured)), "]");
    flush(stream);
    return forward ..forwardValues(captured);
}
observe(forward ..x) = forward ..observeTo(stderr, ..x);

observeCallTo(stream, forward fn, forward ..args) {
    var capturedFn = captureValue(fn);
    var capturedArgs = captureValues(..args);
    printlnTo(stream, "[observed call ", capturedRef(capturedFn), "(", ..weaveValues(", ", ..capturedRefs(capturedArgs)), ")..");
    flush(stream);
    try {
        var capturedReturn = captureValues(..forwardValue(capturedFn)(..forwardValues(capturedArgs)));
        printlnTo(stream, "  ..returned ", ..capturedRefs(capturedReturn), "]");
        flush(stream);
        return forward ..forwardValues(capturedReturn);
    } catch (ex) {
        printlnTo(stream, "  ..threw ", ex, "]");
        flush(stream);
        throw ex;
    }
}

observeCall(forward fn, forward ..args)
    = forward ..observeCallTo(stderr, fn, ..args);
