// @topic W010203 FLTK Graphics -- maze navigation
// @brief class Drawing2D extends Fl_Widget

#ifndef _DRAWING2D_H_INCLUDED_
#define _DRAWING2D_H_INCLUDED_

#include <cassert>
#include <iostream>
#include <string>
#include <sstream>
#include "FL/fl_draw.H"
#include "e002_Room.h"

const double TICK_TIMEOUT = 0.04; // 50 ms

class Drawing2D : public Fl_Widget {
    // Use 45 degree segment to calibrate segment:
    static const int UNCALIBRATED_SEGMENT = 45;
    Room* current_room;
    Room* visible_door;
    
    int ax, ay, AX, AY;
    int bx, by, BX, BY;
    int cx, cy, CX, CY;
    int dx, dy, DX, DY;
    int ex, ey, EX, EY;
    int fx, fy, FX, FY;
    int gx, gy, GX, GY;
    int hx, hy, HX, HY;
    int DLTx, DLTy; // delta
    int CTRx, CTRy; // center
    int compass;
    // door support:
    int sx, sy, SX, SY;
    int tx, ty, TX, TY;
    int ux, uy, UX, UY;
    int wx, wy, WX, WY;
    int ox, oy, OX, OY; // door knob
    int ix, iy, IX, IY; // door ID tag
    int ADJy;
    int compass_segment; // degrees in one segment
    int animation_count;
public:
    Drawing2D( int x, int y, int w, int h )
        : Fl_Widget( x, y, w, h, 0/*const char* label*/ )
    {
        // This version of constructor does not invoke
        // initialize_room() - the user must do it in
        // a separate step
    }

    Drawing2D( int x, int y, int w, int h, Room* room )
        : Fl_Widget( x, y, w, h, 0/*const char* label*/ )
    {
        initialize_room( room );
    }

    void initialize_room( Room* room )
    {
        current_room = room;
        visible_door = NULL;
        animation_count = 0;
        // Wall vertices
        ax = x(); ay = y();
        dx = x() + w(); dy = y() + h();
        bx = dx; by = ay;
        cx = ax; cy = dy;
        ex = ax + w()/4; ey = ay + h()/4;
        fx = bx - w()/4; fy = ey;
        gx = ex; gy = cy - h()/4;
        hx = fx; hy = gy;

        // Remember original vertices
        AX = ax; AY = ay;
        DX = dx; DY = dy;
        BX = bx; BY = by;
        CX = cx; CY = cy;
        EX = ex; EY = ey;
        FX = fx; FY = fy;
        GX = gx; GY = gy;
        HX = hx; HY = hy;

        // Rotation increments
        DLTx = ( fx - ex ) / 10;
        DLTy = ( gy - ey ) / 15;

        // Coordinates of the center
        CTRx = ( ax + dx ) / 2;
        CTRy = ( ay + dy ) / 2;

        // Door
        ADJy = 0; // adjustment is unknown at the beginning
        // 13*DLTy is the door height;
        //  4*DLTx is the foor width:
        sx = CTRx - 2*DLTx; sy = gy - 13*DLTy; SX = sx; SY = sy;
        tx = CTRx + 2*DLTx; ty = sy;           TX = tx; TY = ty;
        ux = sx;            uy = gy + 15;       UX = ux; UY = uy;
        wx = tx;            wy = uy + 15;       WX = wx; WY = wy;

        // Initialize compass
        compass = 0; // points to north
        compass_segment = UNCALIBRATED_SEGMENT; // value to calibrate the compass and 3D drawing

        // Calibrate 3D drawing
        // turn 90 degrees to the right
        int segment_count = 0;
        while( compass < 90 ) {
            turn_head_right();
            segment_count++;
        }
        // complete 360 turn
        while( compass > UNCALIBRATED_SEGMENT ) {
            turn_head_right();
            segment_count++;
        }
        compass_segment = 360/segment_count;
        compass = 0; // points to north

        // There is some drift of the door down on every 45-degree turn.
        // It seems like the drift size is about one single DLTy:
        ADJy = DLTy;

        // recalculate door position
        sx = CTRx - 2*DLTx; sy = gy - 13*DLTy; SX = sx; SY = sy;
        tx = CTRx + 2*DLTx; ty = sy;           TX = tx; TY = ty;
        ux = sx;            uy = gy;           UX = ux; UY = uy;
        wx = tx;            wy = uy;           WX = wx; WY = wy;
        ox = sx;            oy = sy + ( uy - sy ) / 2;
                                               OX = ox; OY = oy; // door knob
        ix = ox;            iy = sy - 2*DLTy;  IX = ix; IY = iy; // door ID tag
        visible_door = current_room->get_room( Room::COMPASS_NORTH );

        // Reset original vertices
        //AX = ax; AY = ay;
        //DX = dx; DY = dy;
        //BX = bx; BY = by;
        //CX = cx; CY = cy;

        //EX = ex; EY = ey;
        //FX = fx; FY = fy;
        //GX = gx; GY = gy;
        //HX = hx; HY = hy;

    }

private:
    static const Fl_Color m_background_color = FL_BLACK;
    static const Fl_Color m_foreground_color = FL_WHITE;

    // Window Events
    int handle( int event ) {
        switch( event ) {
        case FL_RELEASE:
            if ( !visible_door ) {
                alarm_beep();
                return Fl_Widget::handle(event);
            } else {
                redraw();
                do_callback();
                // never do anything after a callback, as the callback
                // may delete the widget!
                return 1; // non-zero indicates we already handled the event
            }
        case FL_PUSH:
        case FL_DRAG:
        case FL_SHORTCUT:
        default:
            return Fl_Widget::handle(event);
        }
    }

    void draw()
    {
        fl_push_clip( x(), y(), w(), h() );
        fl_color( m_background_color );

        static bool back = true;
        //back = back ? false : true;
        if ( back ) fl_rectf( x(), y(), w(), h(), m_background_color ); // filled rect
        //fl_rect( x()+5, y()+5, w()-10, h()-10, m_foreground_color ); // line rect
        //fl_line( CTRx, CTRy, CTRx + 5*DLTy, CTRy + 5*DLTy  ); // DEBUG -- marks center
        fl_color( m_foreground_color );
        /*
        std::stringstream buffer;
        buffer
            << compass_reading()
            //<< compass_direction()
            //<< compass
            ;
        */
        const char* cstr = compass_reading();
        fl_font( FL_TIMES, 18 );
        fl_draw( cstr, CTRx, CY );
        // diagonal line:
        //fl_line( ax, ay, dx, dy );
        fl_line( ex, ey, fx, fy ); // EF
        fl_line( ex, ey, gx, gy ); // EG

        if ( visible_door ) {
            fl_line( hx, hy, gx, gy ); // HG
            //fl_line( ux, uy, gx, gy ); // UG
            //fl_line( ux, uy, wx, wy ); // UW
            //fl_line( hx, hy, wx, wy ); // HW
        } else {
            fl_line( hx, hy, gx, gy ); // HG
        }

        fl_line( hx, hy, fx, fy ); // HF

        fl_line( ex, ey, ax, ay ); // EA
        fl_line( bx, by, fx, fy ); // BF
        fl_line( hx, hy, dx, dy ); // HD
        fl_line( cx, cy, gx, gy ); // CG

        // draw door
        if ( visible_door ) {
            fl_line( sx, sy, tx, ty ); // ST
            fl_line( sx, sy, ux, uy ); // SU
            //fl_line( ux, uy, wx, wy ); // UW
            fl_line( tx, ty, wx, wy ); // TW
            fl_line( sx, sy, wx, wy ); // SW
            fl_line( tx, ty, ux, uy ); // TU
            fl_line( wx, wy, ux, uy ); // WU // threshold
            fl_draw( "o", ox, oy ); // door knob
            fl_draw( visible_door->get_name(), ix, iy ); // door tag
        }

        fl_pop_clip();
    }
public:
    // Operations
    void turn_head_right()
    {
        if ( compass_segment != UNCALIBRATED_SEGMENT ) turn_compass_clockwise();
        bool negative_b4 = (fy-ey) < 0;
        //if ( fx > CTRx ) {
            //ex -= DLTx; if ( ey > ay   - 5*DLTy ) ey -= DLTy;
            //fx -= DLTx; if ( fy < CTRy - 2*DLTy ) fy += DLTy;
            //gx -= DLTx; if ( gy < cy   -   DLTy ) gy += DLTy;
            //hx -= DLTx; if ( hy > CTRy + 5*DLTy ) hy -= DLTy;

            //ex -= DLTx; if ( ey > AY   - 5*DLTy ) ey -= DLTy;
            //fx -= DLTx; if ( fy < CTRy - 5*DLTy ) fy += DLTy;
            //gx -= DLTx; if ( gy < CY   - 5*DLTy ) gy += DLTy;
            //hx -= DLTx; if ( hy > CTRy + 5*DLTy ) hy -= DLTy;

            // Front wall flows to the left:
            ex -= DLTx; ey -= DLTy;
            fx -= DLTx; fy += DLTy;
            gx -= DLTx; gy += DLTy;
            hx -= DLTx; hy -= DLTy;

            // Left wall also flows to the left:
            ax -= DLTx;
            ay -= 3*DLTy; 
            cx -= DLTx;
            cy += 3*DLTy;

            // Front door flows to the left:
            sx -= DLTx; sy -= (DLTy*2)/7;
            tx -= DLTx; ty += (DLTy*2)/7;
            ux -= DLTx; uy += (DLTy*2)/6;
            wx -= DLTx; wy -= (DLTy*2)/8;
            ox -= DLTx; //oy -= (DLTy*2)/7; // door knob
            ix -= DLTx; iy -= (DLTy*2)/6;   // door tag

            if ( fx <= CTRx ) {
                // User looks directly at the corner of the room
                // flip door points
                sx = symmetrical_x_positive( sx );
                tx = symmetrical_x_positive( tx );
                ux = symmetrical_x_positive( ux );
                wx = symmetrical_x_positive( wx );
                //ox = symmetrical_x_positive( ox );
                //ix = symmetrical_x_positive( ix );

                std::swap( sx, tx );
                std::swap( ux, wx );
                std::swap( sy, ty );
                std::swap( uy, wy );
                ox = sx;
                ix = sx; iy = sy - 2*DLTy; // door tag

                // adjust door against drift
                sy -= ADJy;
                ty -= ADJy;
                uy -= ADJy;
                wy -= ADJy;

                // flip wall points once facing NE SE SW NW
                ex = fx; ey = fy;  // E = F
                gx = hx; gy = hy;  // G = H
                //bx = ax + 2 * ( CTRx - ax ); by = ay;
                bx = symmetrical_x_positive( ax ); by = ay;
                                   // B = symmetrical(A)
                ax = AX; ay = AY;  // A = A_orig
                //dx = cx + 2 * ( CTRx - cx ); dy = cy;
                dx = symmetrical_x_positive( cx ); dy = cy;
                                   // D = symmetrical(C)
                cx = CX; cy = CY;  // C = C_orig
                //EX = ex; EY = ey;
                fx = BX; fy = BY;  // F = B_orig
                //GX = gx; GY = gy;
                hx = DX; hy = DY;  // H = D_orig
                if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_clockwise();
                else compass_round();
                configure_right_door();
                return;
            }
        //}
        bool negative_a4 = (fy-ey) < 0;
        if ( negative_a4 != negative_b4 ) {
            if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_clockwise();
            else compass_round();
            // User looks straight at the wall
            // on every pass over 90 degree, i.e. N E S W,
            // re-calibrate door position
            recalibrate_door_position();
        }
    }
    void recalibrate_door_position()
    {
        sx = SX; sy = SY;
        tx = TX; ty = TY;
        ux = UX; uy = UY;
        wx = WX; wy = WY;
        ox = OX; oy = OY; // door knob
        ix = IX; iy = IY; // door id tag

        //ex = EX; ey = EY;
        //fx = FX; fy = FY;
        //gx = GX; gy = GY;
        //hx = HX; hy = HY;
        gy = UY;
        hy = UY;
    }

    // Determines whether the left-hand side door should become visible
    void configure_left_door()
    {
        visible_door = current_room->get_room( compass_direction() - 1 );
    }

    // Determines whether the right-hand side door should become visible
    void configure_right_door()
    {
        visible_door = current_room->get_room( compass_direction() + 1 );
    }

    // Turn compass needle clockwise
    void turn_compass_clockwise()
    {
        compass += compass_segment;
        compass %= 360; // stay within 360
    }

    void turn_head_left()
    {
        if ( compass_segment != UNCALIBRATED_SEGMENT ) turn_compass_counterclockwise();
        bool negative_b4 = (ey-fy) < 0;
        //if ( ex < CTRx ) {

            // Front wall flows to the left:
            ex += DLTx; ey += DLTy;
            fx += DLTx; fy -= DLTy;
            gx += DLTx; gy -= DLTy;
            hx += DLTx; hy += DLTy;

            // Right wall also flows to the right:
            bx += DLTx;
            by -= 3*DLTy; 
            dx += DLTx;
            dy += 3*DLTy;

            // Front door flows to the right:
            tx += DLTx; ty -= (DLTy*2)/7;
            sx += DLTx; sy += (DLTy*2)/7;
            wx += DLTx; wy += (DLTy*2)/6;
            ux += DLTx; uy -= (DLTy*2)/8;
            ox += DLTx; //oy -= (DLTy*2)/7; // door knob
            ix += DLTx; iy -= (DLTy*2)/6;   // door tag

            if ( ex >= CTRx ) {
                // User looks straight at the corner of the room
                // flip door points
                sx = symmetrical_x_negative( sx );
                tx = symmetrical_x_negative( tx );
                ux = symmetrical_x_negative( ux );
                wx = symmetrical_x_negative( wx );
                //ox = symmetrical_x_negative( ox );
                //ix = symmetrical_x_negative( ix );

                std::swap( sx, tx );
                std::swap( ux, wx );
                std::swap( sy, ty );
                std::swap( uy, wy );
                ox = sx;
                ix = sx; iy = sy - 2*DLTy; // door tag

                // adjust door against drift
                sy -= ADJy;
                ty -= ADJy;
                uy -= ADJy;
                wy -= ADJy;

                // flip wall points once facing NE SE SW NW
                fx = ex; fy = ey;  // F = E
                hx = gx; hy = gy;  // H = G

                ax = symmetrical_x_negative( bx ); ay = by;
                                   // A = symmetrical(B)
                bx = BX; by = BY;  // B = B_orig

                cx = symmetrical_x_negative( dx ); cy = dy;
                                   // C = symmetrical(D)
                dx = DX; dy = DY;  // D = D_orig

                ex = AX; ey = AY;  // E = A_orig

                gx = CX; gy = CY;  // G = C_orig
                if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_counterclockwise();
                else compass_round();
                configure_left_door();
                return;
            }
        //}
        bool negative_a4 = (ey-fy) < 0;
        if ( negative_a4 != negative_b4 ) {
            if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_counterclockwise();
            else compass_round();

            // User looks straight at the wall
            // on every pass over 90 degree, i.e. N E S W,
            // re-calibrate door position
            recalibrate_door_position();
        }
    }

    // Turn compass needle counterclockwise
    void turn_compass_counterclockwise()
    {
        if ( compass <= 0 ) {
            compass = 360 + compass; // no negative degrees
        }
        compass -= compass_segment;
        compass %= 360; // stay within 360
    }

    char const* compass_reading()
    {
        static char const*const sides[] = {
            "N",
            "NE",
            "E",
            "SE",
            "S",
            "SW",
            "W",
            "NW"
        };
        return sides[ compass_direction() ];
    }

    int compass_direction()
    {
        //        N
        //  NW 338 23 NE
        //    315 0 45
        //  293  \|/  68
        //W   270-+-90    E
        //  248  /|\  113
        //   225 180 135
        // SW  203 158  SE
        //        S

        if ( compass < 23 )  return Room::COMPASS_NORTH;
        if ( compass < 68 )  return Room::COMPASS_NORTHEAST;
        if ( compass < 113 ) return Room::COMPASS_EAST;
        if ( compass < 158 ) return Room::COMPASS_SOUTHEAST;
        if ( compass < 203 ) return Room::COMPASS_SOUTH;
        if ( compass < 248 ) return Room::COMPASS_SOUTHWEST;
        if ( compass < 293 ) return Room::COMPASS_WEST;
        if ( compass < 338 ) return Room::COMPASS_NORTHWEST;
        return Room::COMPASS_NORTH;
    }

    // Occasionally, at well-known points, it's a good
    // idea to round compass direction. For example, when looking
    // straight at the corner of the room.
    void compass_round()
    {
        //        N
        //  NW 338 23 NE
        //    315 0 45
        //  293  \|/  68
        //W   270-+-90    E
        //  248  /|\  113
        //   225 180 135
        // SW  203 158  SE
        //        S

        if ( compass < 23 )  compass =   0; return;
        if ( compass < 68 )  compass =  45; return;
        if ( compass < 113 ) compass =  90; return;
        if ( compass < 158 ) compass = 135; return;
        if ( compass < 203 ) compass = 180; return;
        if ( compass < 248 ) compass = 225; return;
        if ( compass < 293 ) compass = 270; return;
        if ( compass < 338 ) compass = 315; return;
        compass = 0;
    }

    // Calculates position of the symmetrical X axis coordinate
    // relative to the center point of the view
    int symmetrical_x_positive( int xx )
    {
        // reflection as follows
        // xx  -> CTRx -> result
        return xx + 2 * ( CTRx - xx );
    }

    // Calculates position of the symmetrical X axis coordinate
    // relative to the center point of the view
    int symmetrical_x_negative( int xx )
    {
        // reflection as follows
        // result  <- CTRx <- xx
        return xx - 2 * ( xx - CTRx );
    }

    void zoom_view()
    {
        // Front wall zoom:
        ex -= DLTx*2; ey -= DLTy*2;
        fx += DLTx*2; fy -= DLTy*2;
        gx -= DLTx*2; gy += DLTy*2;
        hx += DLTx*2; hy += DLTy*2;

        // Door zoom:
        sx -= DLTx/2; sy -= DLTy;
        tx += DLTx/2; ty -= DLTy;
        ux -= DLTx/2; uy += DLTy;
        wx += DLTx/2; wy += DLTy;
        ox = sx; oy = sy + ( uy - sy ) /2; // door knob
        ix = sx; iy = sy - 2*DLTy;         // door id tag
    }

    bool animate_door_entry()
    {
        if ( animation_count ) {
            // door entry animation is in progress
            --animation_count;
            if ( animation_count == 0 ) {
                //animation_count = 0;
                int tmp_compass = compass;
                initialize_room( visible_door );
                compass = tmp_compass; 
                compass_round();
                visible_door = current_room->get_room( compass_direction() );
                return false; // stop animation
            }

            zoom_view();
            return true; // continue animation

        } else if ( visible_door ) {
            // entry animation is in progress
            // in which direction is user trying to enter the door?
            int door_direction = 45 * current_room->get_direction( visible_door );
            //turn_degree = turn_degree ? turn_degree : 360;
            int turn_degree = door_direction - compass;
            if ( turn_degree < -270 ) turn_degree = 360 - turn_degree; // when user looks NW
            //std::cout << "       door: " << door_direction << '\n';
            //std::cout << "turn_degree: " << turn_degree << '\n';
            //std::cout << "old compass: " << compass << "\n\n";
            //std::cout << "________________________________\n";
            //return false;

            if ( std::abs( turn_degree ) < 10 ) {
                recalibrate_door_position();
                //std::cout << "________________________________\n";
                animation_count = 20;
                return true;
            }
            //if ( turn_degree < -90 ) turn_head_right();
            else if ( turn_degree > 0 ) {
                turn_head_right();
            } else {
                turn_head_left();
            }
            //alarm_beep();

            //turn_degree = door_direction - compass;
            //std::cout << "  remaining: " << turn_degree << '\n';
            //std::cout << "new compass: " << compass << "\n\n";
            return true; // keep animation running
        }
        std::cout << "-------------------------------------\n";
        return false;
    }

    void alarm_beep()
    {
        std::cout << '\a';
    }
};//class Drawing2D

/*
        //        N
        //  NW 338 23 NE
        //    315 0 45
        //  293  \|/  68
        //W   270-+-90    E
        //  248  /|\  113
        //   225 180 135
        // SW  203 158  SE
        //        S

        A                                    B
         \                                  /
          E________________________________F
          |           S_______T            |
          |           |  CTR  |            |
          G___________U_______W____________H
         /                                  \
        C                                    D
*/
#endif //_DRAWING2D_H_INCLUDED_