// g++  -ggdb -DDESKTOP -march=native -Wall -std=c++11 ./prog.cpp  -lm -lncurses -o prog_batman
// or
// make -j -k && cp ./XXX.uf2  /media/rm/RPI-RP2/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <cmath>
#include <cstring>
#include <ctime>
#include <algorithm>
#include <initializer_list>
#include <array>
#include "/home/rm/docs/shape/stls/display/font.h"

#ifdef  DESKTOP
#include <iostream>
#else
#include "pico/stdlib.h"
#include "pico/rand.h"
#include "pico/multicore.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "hardware/pwm.h"
#include "apa102.pio.h"
#endif

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define MOTOR_PIN 29
#define OPTICAL_GPIO 7
#define PIN_CLK_1 0
#define PIN_DIN_1 2
#define PIN_CLK_2 14
#define PIN_DIN_2 13

#define N_LEDS 64
#define SERIAL_FREQ (5 * 1000 * 1000)  // 5 mhz; 32 bpp so 5x2^20 / (32*64) = 2^11 so 5x2^9 updates per second ~ 2500. at 10rpm that's 250 per cycle so need to delay about 0.5ms between updates

struct Col {

    uint8_t r,g,b;
    bool is_zero() const { return 0==(r|b|g); }
    void down1() {
        if (r) --r;
        if (g) --g;
        if (b) --b;
    }
    static Col rainbow(uint8_t step) {
        static const uint8_t MAGNITUDE = 25;

        if (step < 43)
            return Col{MAGNITUDE, uint8_t(int(MAGNITUDE) * step / 43), 0};
        step -= 43;
        if (step < 43)
            return Col{uint8_t(MAGNITUDE - int(MAGNITUDE) * step / 43), MAGNITUDE, 0};
        step -= 43;
        if (step < 43)
            return Col{0, MAGNITUDE, uint8_t(int(MAGNITUDE) * step / 43)};
        step -= 43;
        if (step < 43)
            return Col{0, uint8_t(MAGNITUDE - int(MAGNITUDE) * step / 43), MAGNITUDE};
        step -= 43;
        if (step < 43)
            return Col{uint8_t(int(MAGNITUDE) * step / 43), 0, MAGNITUDE};
        step -= (43 - 3);
        return Col{MAGNITUDE, 0, uint8_t(MAGNITUDE - (int(MAGNITUDE) * step / 43))};
    }

};

static Col frame1[64];  // optical sensor side
static Col frame2[64];  // rpi side

static uint8_t remap1[64];
static uint8_t remap2[64];

#ifndef DESKTOP
void put_start_frame(PIO pio, uint sm) {
    pio_sm_put_blocking(pio, sm, 0u);
}

void put_end_frame(PIO pio, uint sm) {
    pio_sm_put_blocking(pio, sm, ~0u);
}

void put_rgb888(PIO pio, uint sm, uint8_t r, uint8_t g, uint8_t b) {
    static const int BRIGHTNESS = 1;
    pio_sm_put_blocking(pio, sm,
                        0x7 << 29 |                   // magic
                        (BRIGHTNESS & 0x1f) << 24 |   // global brightness parameter
                        (uint32_t) b << 16 |
                        (uint32_t) g << 8 |
                        (uint32_t) r << 0
        );
}
PIO pio_0 = pio0;

void emit_frame() {
    put_start_frame(pio_0, 0);
    put_start_frame(pio_0, 1);
    for (uint i = 0; i < N_LEDS; ++i) {
        auto v1 = frame1[remap1[i]];
        auto v2 = frame2[remap2[i]];
        put_rgb888(pio_0, 0, v1.r, v1.g, v1.b);
        put_rgb888(pio_0, 1, v2.r, v2.g, v2.b);
//        put_rgb888(pio_0, 1, 0,0,0);
    }
    put_end_frame(pio_0, 0);
    put_end_frame(pio_0, 1);
}
#else
int gpio_get(int) { throw "nyi"; }
void emit_frame() {
    for (int i = 0; i < 8; ++i) {
        for (int j = 0; j < 8; ++j){
            std::cout << (frame1[i*8+j].is_zero() ? '.' : '0') << ' ';
        }
        std::cout << '\n';
    }
}
void sleep_ms(int i) {usleep(1000*i);}
void sleep_us(int i) {usleep(i);}
uint32_t get_rand_32() { return rand(); }
uint32_t time_us_32() {
    struct timespec ts;
    if(clock_gettime(CLOCK_REALTIME, &ts)) throw "asdf";
    return uint32_t(ts.tv_sec*1000000) + uint32_t(ts.tv_nsec / 1000);
}
#endif

void blankframe() {
    for (uint i = 0; i < N_LEDS; ++i) {
        frame1[i] = {0,0,0};
        frame2[i] = {0,0,0};
    }
}

void emit_blankframe() {
    blankframe();
    emit_frame();
}

void setremap() {
    for (int i = 0; i < 64; ++i) {
        auto alti = ((i % 8) * 8 ) + (i/8);
        remap1[i] = alti;
        auto alti2 = ((i % 8) * 8 ) + ((7 - (i/8)));
        remap2[i] = alti2;
    }
}

static const float led_d = 2.8;
static const float led_pitch_mm = 22./8;

void write_frame(float theta_r) {
    // frame1 faces optical sensor
    const float sinth = sinf(theta_r);
    const float costh = cosf(theta_r);

    for (int i = 0; i < 8; ++i) {
        const float r = (float(i - 3) * led_pitch_mm) - (led_pitch_mm/2) - 0.5;
        const float x2 = r * costh - led_d * sinth;
        const float y2 = r * sinth + led_d * costh;

        const float x1 = -x2;
        const float y1 = -y2;

        Col c1 = {0,0,0};
        if (x1 > 0) {
            if (y1 > 0) {
                c1 = {30,0,0};
            }
            else {
                c1 = {0,0,0};
            }
        } else {
            if (y1 > 0) {
                c1 = {0,0,0};
            }
            else {
                c1 = {0,30,0};
            }
        }
        Col c2 = {0,0,0};
        if (x2 > 0) {
            if (y2 > 0) {
                c2 = {30,0,0};
            }
            else {
                c2 = {0,0,0};
            }
        } else {
            if (y2 > 0) {
                c2 = {0,0,0};
            }
            else {
                c2 = {0,30,0};
            }
        }
//#define XMASTREE 0
#ifdef XMASTREE
        const float h = hypotf(x2, y2);
#endif
        for (int z = 0; z < 8; ++z) {
#ifdef XMASTREE
            const float f = (float(z) *(-10./8)) + 10.;

            frame1[z*8 + (i)] = frame2[z*8 + (7-i)] = Col{0,uint8_t(h < f ? 30 : 0),0};
            // close to edge, sometimes flash red!
            if (fabsf(h-f) < 0.1) {
                auto r = rand();
                if (0 == (r & 31))
                    frame1[z*8 + (i)] = Col{30,0,0};
                if (0 == ((r << 5) & 31))
                    frame2[z*8 + (7-i)] = Col{30,0,0};
            }
#else
            if (z < 4) continue;
            frame1[z*8 + i] = c1;
            frame2[z*8 + (7-i)] = c2;
#endif
        }
    }
    emit_frame();
    //sleep_us(100);
    emit_blankframe(); // save envergy..?
}
void test_r_pattern() {
    while (1) {
        for (float hyp = 0.; hyp < 15; hyp += 0.07) {
            for (float theta_r : {0,1,2,3,4}) {

                const float sinth = sinf(theta_r);
                const float costh = cosf(theta_r);

                for (int i = 0; i < 8; ++i) {
                    const float r = (float(i - 3) * led_pitch_mm) - (led_pitch_mm/2) - 0.5;
                    const float x = r * costh - led_d * sinth;
                    const float y = r * sinth + led_d * costh;

                    const float h = hypotf(x, y);
                    if (h < hyp)
                        for (int z = 0; z < 8; ++z) {
                            if (z < 4)
                                frame1[z*8 + i] = {30,0,0};
                            if (z >=5)
                                frame2[z*8 + (7-i)] = {0,30,0};
                        }
                }
                emit_frame();
                sleep_ms(33);
                emit_blankframe();
            }
        }
    }
}

static const uint MAX_PWM = 512;
volatile uint pwm_frac = MAX_PWM / 2;

static volatile uint32_t lastzero_us;
static volatile uint32_t per_us;

bool core1started = false;



class FrameUpdater {
    virtual void upd(float theta_r, uint32_t tnow) = 0;
};

typedef std::array<float, 9> MatT;
typedef std::array<float, 3> VecT;
VecT rand_norm() {
    VecT ret;
    ret[0] = float(get_rand_32() % 100) - 50.;
    ret[1] = float(get_rand_32() % 100) - 50.;
    ret[2] = float(get_rand_32() % 100) - 50.;
    float n = sqrtf(ret[0] * ret[0] +
                    ret[1] * ret[1] +
                    ret[2] * ret[2]);
    ret[0] /= n;
    ret[1] /= n;
    ret[2] /= n;
    return ret;
}

struct TextUpdater:FrameUpdater {

    void write_text(const char * thetext, const int thetextlen,
                    const uint32_t step, const bool f1, const Col &c) {
        if (!thetextlen) return;

        static const Col zc({0,0,0});
        const char c1 = thetext[(step / 8) % thetextlen];
        const char c2 = thetext[((step+8) / 8) % thetextlen];
        int idx1 = c1 - ' ';
        int idx2 = c2 - ' ';
        idx1 = std::min(126-33, std::max(0, idx1));
        idx2 = std::min(126-33, std::max(0, idx2));
        emit_blankframe();
        for (int rowi = 0; rowi < 8; ++rowi) {
            char ci = 0;
            for (size_t c1i = step % 8; c1i < 8; ++c1i, ++ci) {
                //put_pixel(urgb_u32(((characters[idx1][rowi] &(1 << (8-c1i))) ? 30:0), 0, 0));
                if (f1) {
                    if (characters[idx1][rowi] &(1 << (8-c1i))) frame1[(7-rowi)*8 + (7-ci)] = c;
                }
                else {
                    if (characters[idx1][rowi] &(1 << (8-c1i))) frame2[(7-rowi)*8 + (ci)] = c;
                }
            }
            for (size_t c2i = 0; ci < 8 ; ++c2i, ++ci) {
                if (f1) {
                    if (characters[idx2][rowi] &(1 << (8-c2i))) frame1[(7-rowi)*8 + (7-ci)] = c;
                }
                else {
                    if (characters[idx2][rowi] &(1 << (8-c2i))) frame2[(7-rowi)*8 + (ci)] = c;
                }
            }
        }
        emit_frame();
    }

    char buf1[32];
    char buf2[32];

    TextUpdater() {
        memset(buf1, '\0', 32);
        memset(buf2, '\0', 32);
        sprintf(buf1, "%s\0", "hi  ");
        sprintf(buf2, "%s\0", "blaggg ");
    }

    void upd(float theta_r, uint32_t tnow) {
        const char t1[] = "Hello Moore Family  ";
        const int l1 = strlen(t1);
        const char t2[] = "love from Richard  ";
        const int l2 = strlen(t2);
        if (((tnow/1000000) % 8) == 0) {
            memset(buf1, '\0', 32);
            memset(buf2, '\0', 32);
            sprintf(buf2, "%i   \0", tnow);
            sprintf(buf1, "%i   \0", get_rand_32());
        }
        float f = 2.*M_PI/32.;
        if (fabs(theta_r-(M_PI/2.)) < f) {
            write_text(buf1, strlen(buf1), tnow / 50000, true, {0,0,30});
            write_text(buf2, strlen(buf2), tnow / 50000, false, {30,0,0});
        }
        else if (fabs(theta_r - M_PI) < f) {
            write_text(t1, l1, tnow / 50000, true, {0,30,0});
            write_text(t2, l2, tnow / 50000, false, {10,10,10});
        }
        else if (fabs(theta_r-(3*M_PI/2.)) < f) {
            write_text(buf1, strlen(buf1), tnow / 50000, false, {0,0,30});
            write_text(buf2, strlen(buf2), tnow / 50000, true, {30,0,0});
        }
        else if (fabs(theta_r-(2.*M_PI)) < f) {
            write_text(t1, l1, tnow / 50000, false, {0,30,0});
            write_text(t2, l2, tnow / 50000, true, {10,10,10});
        }
        else
            emit_blankframe();
    }
};

struct CubeUpdater: FrameUpdater {

    static MatT rot_mat(float x, float y, float z, // un-normalised axis
                        float angle_in_rad
        ) {
        const float h = sqrtf(x*x + y*y + z*z);
        x /= h;
        y /= h;
        z /= h;
        const float c = cosf(angle_in_rad), s = sinf(angle_in_rad);
        return {
            c + x*x*(1-c),            x*y*(1-c) - z*s,      x*z*(1-c) + y*s,
		    y*x*(1-c) + z*s,  c + y*y*(1-c),            y*z*(1-c) - x*s,
		    z*x*(1-c) - y*s,      z*y*(1-c) + x*s,  c + z*z*(1-c),
        };
    }

    static MatT rot_mat_abg(float a, float b, float g) {
        const float sa = sinf(a),
            sb = sinf(b),
            sg = sinf(g),
            ca = cosf(a),
            cb = cosf(b),
            cg = cosf(g);

        return { ca*cg, sa*sb*cg - ca*sg, ca*sb*cg + sa*sg,
            cb*sg, sa*sb*sg + ca*cg, ca*sb*sg - sa*cg,
            -sb, sa*cb, ca*cb};
    }

    static VecT rot(float p_x, float p_y, float p_z, const MatT &fs) {

        VecT ret;
        ret[0] = fs[0]*p_x + fs[1] * p_y + fs[2]*p_z;
        ret[1] = fs[3]*p_x + fs[4] * p_y + fs[5]*p_z;
        ret[2] = fs[6]*p_x + fs[7] * p_y + fs[8]*p_z;
        // ret[0] = fs[0]*p_x + fs[3] * p_y + fs[6]*p_z;
        // ret[1] = fs[1]*p_x + fs[4] * p_y + fs[7]*p_z;
        // ret[2] = fs[2]*p_x + fs[5] * p_y + fs[8]*p_z;
        return ret;
    }
    float shape_theta;// = 1.;
    VecT v;// = {1,1,1};
    MatT m;// =
    uint32_t tm, tv;

    CubeUpdater():shape_theta(0),//float(get_rand_32()%1024)/1024),
                  v({0,1,0}),//rand_norm()),
                  m(rot_mat(v[0], v[1], v[2], shape_theta)),
                  tm(time_us_32()),
                  tv(time_us_32())
        {}

    void upd(float theta_r, uint32_t tnow) {
        // frame1 faces optical sensor
        const float sinth = sinf(theta_r);
        const float costh = cosf(theta_r);
        const Col c = Col::rainbow(tnow/30000);
        emit_blankframe();
        for (int i = 0; i < 8; ++i) {
            const float r = (float(i - 3) * led_pitch_mm) - (led_pitch_mm/2) - 0.5;
            const float x = r * costh - led_d * sinth;
            const float y = r * sinth + led_d * costh;

            for (int z_i = 0; z_i < 8; ++z_i) {
                const float z = float(z_i - 3) * led_pitch_mm - (led_pitch_mm/2);
                const VecT v = rot(x, y, z, m);
                //const VecT v = {x2, y2, z};
                const auto cubesz = 6.0;
                // if ((fabs(v[0]) < cubesz) &&
                //     (fabs(v[1]) < cubesz) &&
                //     (fabs(v[2]) < cubesz)) {

                    // if ((fabs(v[0]) > (1.5)) &&
                    //     (fabs(v[1]) > (1.5)) &&
                    //     (fabs(v[2]) > (1.5))) {

                if ((fabs(v[0] < 1.6) && (hypotf(v[1], v[2]) < 5)))
                    frame1[z_i*8 + (i)] = frame2[z_i*8 + (7-i)] = c;
//                    }
//                }
            }
        }
        emit_frame();

        if ((tnow - tv) > 15e6) {
            v[0] = (rand() % 100) - 50;
            v[1] = (rand() % 100) - 50;
            v[2] = (rand() % 100) - 50;
            tv = tnow;
        }
        if ((tnow - tm) > 50000) {  // slow? recalc
            //std::cout << "recalc" << std::endl;
            shape_theta += 0.015;
            m = rot_mat(v[0], v[1], v[2], shape_theta);
            tm = tnow;
        }
    }

};
struct BallBounceUpdater: FrameUpdater {
    VecT s, v; // mm?
    uint32_t lastupd;

    BallBounceUpdater():
        s({-2,-3,-3}),
        lastupd(0) {
        init_rand_v();
    }

    void init_rand_v() {
        v = rand_norm();
        // make 2d for testing:
        //v[1] = 0;
    }

    void upd(float theta_r, uint32_t tnow) override {
        if (!lastupd) { lastupd = tnow; return; }
        const Col c = Col::rainbow(tnow/30000);
        static const float B_RAD = 3.25;
        const float sinth = sinf(theta_r);
        const float costh = cosf(theta_r);

        emit_blankframe();
        for (int i = 0; i < 8; ++i) {
            const float r = (float(i - 3) * led_pitch_mm) - (led_pitch_mm/2) - 0.5;
            const float x = r * costh - led_d * sinth;
            const float y = r * sinth + led_d * costh;

            const float h1 = hypotf(s[0]-x, s[1]-y);
            const float h2 = hypotf(-s[0]-x, -s[1]-y);
            for (int z_i = 0; z_i < 8; ++z_i) {
                const float z = float(z_i - 3) * led_pitch_mm - (led_pitch_mm/2);
                const float r1 = hypotf(h1, s[2]-z);
                const float r2 = hypotf(h2, s[2]-z);
                if (r2 < B_RAD)
                    frame2[z_i*8 + (7-i)] = c;
                if (r1 < B_RAD)
                    frame1[z_i*8 + (i)] = c;
            }
        }
        emit_frame();

        // std::cout << "tnow " << tnow
        //           << "tnow2 " << (tnow / 1000000)
        //           << "tnow3 " << ((tnow / 1000000)%10)
        //           << "tnow4 " << (0 == ((tnow / 1000000)%10))
        //           << std::endl;
        if (((tnow / 1000000) % 16) == 0) // reset every n s
        {
            init_rand_v();
            //   std::cout << "randomising v" << std::endl;
//            throw "asf";
        }
        const auto usec_elaps = tnow - lastupd;
        lastupd = tnow;
        const float vscale = float(usec_elaps) / 50000.;  // 20mm/s?
        s[0] += v[0] * vscale;
        s[1] += v[1] * vscale;
        s[2] += v[2] * vscale;
        static const int LIM = 10;
        if (s[0] < -LIM) { s[0] = -LIM; v[0] = -v[0]; }
        if (s[0] >  LIM) { s[0] =  LIM; v[0] = -v[0]; }
        if (s[1] < -LIM) { s[1] = -LIM; v[1] = -v[1]; }
        if (s[1] >  LIM) { s[1] =  LIM; v[1] = -v[1]; }
        if (s[2] < -LIM) { s[2] = -LIM; v[2] = -v[2]; }
        if (s[2] >  LIM) { s[2] =  LIM; v[2] = -v[2]; }
    }
};

void core1() {

    //CubeUpdater u;
    //BallBounceUpdater u;
    TextUpdater u;

    while (true) {  // display stuff
        uint32_t tnow = time_us_32();
        auto theta_r = per_us ? (float(tnow - lastzero_us) * (2.*M_PI) / per_us) : 0.;
        u.upd(theta_r, tnow);

        int32_t dur = tnow + 1400 + (get_rand_32() % 200) - time_us_32();
        sleep_us(std::max(dur, int32_t(0)));
        //sleep_ms(100);
//        write_frame(theta_r);
//        sleep_us(std::max(100000, rand()%(100000/50)));
    }

}
void startup() {
    for (int z = 0; z < 64; ++z) {
        for (int i = 0; i < 8; ++i) {
            for (int j = 0; j < 8; ++j) {
                frame1[j*8 + i] = ((j*8+i) < z ? Col({30,0,0}) : Col({0,0,0}));
                frame2[j*8 + i] = ((j*8+i) < z ? Col({0,30,0}) : Col({0,0,0}));
            }
        }
        emit_frame();
        sleep_ms(25);
    }
    for (int i = 0; i < 256; ++i) {
        for (uint j = 0; j < 64; ++j)
            frame1[j] = frame2[j] = Col::rainbow(i+j);
        emit_frame();
        sleep_ms(8);
    }

    // fade out
    for (uint8_t i = 30; i; --i)
    {
        for (auto &f : frame1) f.down1();
        for (auto &f : frame2) f.down1();
        emit_frame();
        sleep_ms(30);
    }
    emit_blankframe();

    if (0) {  // simulate stepping
        float theta = 0.;
        //CubeUpdater u;
        //BallBounceUpdater u;
        TextUpdater u;
        while (1)
        {
            u.upd(theta, time_us_32());
            sleep_ms(100);
            //theta += 0.1;
        }
    }
    if (0) { // test optical sensor
        while (1) {
            const bool on = gpio_get(OPTICAL_GPIO);
            for (int i = 0; i < 8; ++i) {
                for (int j = 0; j < 8; ++j) {
                    frame1[j*8 + i] = Col{uint8_t(on?30u:0u),0,0};
                    frame2[j*8 + i] = Col{0,uint8_t(on?30u:0u),0};
                }
            }
            emit_frame();
            sleep_ms(25);
        }
    }
    if (0) test_r_pattern();
    if (0) {  // DONTSPIN
#ifndef DESKTOP
        multicore_launch_core1(core1);
#endif
        while (1) sleep_ms(100);
    }
}

int main()
#ifdef DESKTOP
try {
    setremap();
    startup();
#else
{
    setremap();
    uint offset0 = pio_add_program(pio_0, &apa102_mini_program);
    apa102_mini_program_init(pio_0, 0, offset0, SERIAL_FREQ, PIN_CLK_1, PIN_DIN_1);
    apa102_mini_program_init(pio_0, 1/*sm*/, offset0, SERIAL_FREQ, PIN_CLK_2, PIN_DIN_2);

    gpio_init(MOTOR_PIN);
    gpio_set_function(MOTOR_PIN, GPIO_FUNC_PWM);
    auto slice_num = pwm_gpio_to_slice_num(MOTOR_PIN);
    pwm_config config = pwm_get_default_config();
    // Set divider, reduces counter clock to sysclock/this value
    pwm_config_set_clkdiv(&config, 4.f);
    // Load the configuration into our PWM slice, and set it running.
    pwm_init(slice_num, &config, true);

    pwm_set_gpio_level(MOTOR_PIN, 0);
    startup();

//    uint32_t t = time_us_32();
    uint32_t tstart = time_us_32();
    uint32_t zeropoints[4] = {0,0,0,0};  // record the last N time points at which we pass zero
    uint8_t zeropoint_idx = 0;  // as a ringbuffer

    static const uint RPS = 10;

    while (true) {
        const uint32_t t = time_us_32();
        if (((t - tstart) > 7000000) && ! core1started) {
            multicore_launch_core1(core1);
            core1started = true;
        }
        zeropoints[(zeropoint_idx++) & 3] = t;
        lastzero_us = t;
        per_us = (zeropoints[(zeropoint_idx + 3) & 3] - zeropoints[(zeropoint_idx) & 3]) / 3;

        if (per_us > (1000000/RPS))  // increment/decrement
            pwm_frac = std::min(MAX_PWM, pwm_frac + 1);
        else
            pwm_frac = std::max(1u, pwm_frac - 1);
        pwm_set_gpio_level(MOTOR_PIN, (65535u * pwm_frac) / MAX_PWM);

/*        while (true) {  // display stuff
          auto tnow = time_us_32();
          if (tnow - tstart < 7000000) break;  // wait until up to speed
          auto theta_r = float(tnow - lastzero_us) * (2.*M_PI) / per_us;
          if (theta_r > 2*M_PI*0.8) break;
          write_frame(theta_r);
          tnow = time_us_32();  // re-get
          int trem = std::max(0., (lastzero_us + per_us * 0.8) - tnow);
          if (trem == 0) break;
          sleep_us(std::m(trem, rand()%(100000/50)));
          }
*/
        while (!gpio_get(OPTICAL_GPIO))
            sleep_us(10);  // or busy?

        if (0) { // colour during blanking, indicate pwm level
            for (uint i = 0; i < (64 * pwm_frac) / MAX_PWM; ++i) {
                frame1[i] = {30,0,0};
                frame2[i] = {0,30,0};
            }
            emit_frame();
            emit_blankframe();
        }

        while (gpio_get(OPTICAL_GPIO))
            ;   // busy wait
    }
#endif
}
#ifdef DESKTOP
catch (const char *c) { std::cout << c << std::endl; }
#endif

