//==============================================================================================

#pragma warning(disable: 4996)  // _CRT_SECURE_NO_WARNINGS

#include "stdafx.h"

#include <array>

#include <stdio.h>

#include <conio.h>              // _kbhit

#include <Windows.h>


//------------------------------+---------------------------------------------------------------


#define CRE                     // constructor mark


using   u8                      = unsigned  char;

using   u16                     = unsigned  short;

using   u32                     = unsigned  int;

using   uu                      = std::size_t;


//------------------------------+---------------------------------------------------------------


constexpr   auto                make_color16( u8  l, u8  r, u8  g, u8  b )

{

    return  u8( l << 3 | r << 2 | g << 1 | b );

}


enum    class                   C4 : u8

{

    //                      +  R  G  B

    black   = make_color16( 0, 0, 0, 0 ),

    navy    = make_color16( 0, 0, 0, 1 ),

    green   = make_color16( 0, 0, 1, 0 ),

    teal    = make_color16( 0, 0, 1, 1 ),

    maroon  = make_color16( 0, 1, 0, 0 ),

    purple  = make_color16( 0, 1, 0, 1 ),

    olive   = make_color16( 0, 1, 1, 0 ),

    silver  = make_color16( 0, 1, 1, 1 ),

    gray    = make_color16( 1, 0, 0, 0 ),

    blue    = make_color16( 1, 0, 0, 1 ),

    lime    = make_color16( 1, 0, 1, 0 ),

    aqua    = make_color16( 1, 0, 1, 1 ),

    red     = make_color16( 1, 1, 0, 0 ),

    fuchsia = make_color16( 1, 1, 0, 1 ),

    yellow  = make_color16( 1, 1, 1, 0 ),

    white   = make_color16( 1, 1, 1, 1 ),

};


constexpr   C4                  gray_table[]

{

    C4::black, C4::gray,  C4::gray,  C4::gray,  C4::gray,  C4::gray,  C4::gray,  C4::silver,

    C4::gray,  C4::silver,C4::silver,C4::silver,C4::silver,C4::silver,C4::silver,C4::white,

};


constexpr   auto                operator/( C4 fore, C4 back )

{

    return  u8( static_cast< u8 >( back ) << 4 | static_cast< u8 >( fore ) );

};


auto&                           operator++( C4& c )

{

    return  c == C4::black  ? c = C4::gray   :

            c == C4::gray   ? c = C4::silver :

            c == C4::silver ? c = C4::white  :

            c = static_cast< C4 > ( static_cast< u8 >( c ) | 8 );

};


auto&                           operator--( C4& c )

{

    return  c == C4::gray   ? c = C4::black  :

            c == C4::silver ? c = C4::gray   :

            c == C4::white  ? c = C4::silver :

            c = static_cast< C4 > ( static_cast< u8 >( c ) & 7 );

};


constexpr   auto                get_fore( u8 color )

{

    return  static_cast< C4 >( color & 0xF );

}


constexpr   auto                get_back( u8 color )

{

    return  static_cast< C4 >( color >> 4 );

}

//------------------------------+---------------------------------------------------------------


struct                          TEX

{

    u8                          color;

    char                        letter;

    CRE                         TEX() = default;

    CRE                         TEX( const u8 color, const char c )

    :   color( color ), letter( c ) {}

    CRE                         TEX( const int value )

    :   color( value & 0xFF ), letter( ( value & 0xFF00 ) >> 8 ) {}

};

//------------------------------+---------------------------------------------------------------


template< int x_size = 80, int y_size = 25 >

class                           PAGE

{

    static  const   int         size            = x_size * y_size;

    using                       LINE            = TEX( & )[ x_size ];

    using                       MATRIX          = TEX( & )[ y_size ][ x_size ];


public:

    CRE                         PAGE() = default;

    CRE                         PAGE( const PAGE& page )

    {

        buffer = page.buffer;

    }


    std::array< TEX, size >     buffer;

    MATRIX&                     cells           = (MATRIX&)buffer;


    u8                          text_color      = C4::silver / C4::black;

    u8                          number_color    = C4::silver / C4::black;

    u8                          number_size     = (u8)11;

    bool                        right_alignment = true;


    auto                        xsize() const { return  x_size; }

    auto                        ysize() const { return  y_size; }


    void                        fill( const TEX tex )

    {

        buffer.fill( tex );

    }


    void                        put( const int x, const int y, const TEX tex )

    {

        cells[ y ][ x ] = tex;

    }


    void                        put( const int x, const int y, const char c )

    {

        put( x, y, { text_color, c } );

    }


    auto                        puts( const int x, const int y, const char* str )

    {

        int i;

        for( i = 0; str[ i ]; ++i )

            put( x + i, y, str[ i ] );

        return  x + i;

    }


    template< class T, uu SIZE >

    auto                        puts( const int x, const int y, const char* str,

                                      const T( &colors )[ SIZE ], const int offset = 0 )

    {

        int i;

        for( i = 0; str[ i ]; ++i )

            put( x + i, y,

                { static_cast< u8 >( colors[ ( i + offset ) % SIZE ] ), str[ i ] } );

        return  x + i;

    }


    auto                        to_string( const int n ) const

    {

        static  char    numstr[ 14 ];

        int* c = (int*)numstr;

        c[ 2 ] = c[ 1 ] = c[ 0 ] = 0x20202020;

        bool    neg = false;

        u32     u = n;

        if( n < 0 ) neg = true, u = -n;

        char*   p = numstr + ( sizeof numstr - 1 );

        char*   end = p - number_size;

        do *--p = u % 10 + '0', u /= 10; while( u );

        if( neg ) *--p = '-';

        if( !right_alignment )

            return  p;

        return  end;

    }


    auto                        text_number( const int x, const int y,

                                             const char* s, const int n )

    {

        return  puts( puts( x, y, s ), y, to_string( n ) );

    }


    void                        horz_line( const int left, const int right,

                                           const int y, const TEX tex )

    {

        for( int x = left; x < right; ++x )

            put( x, y, tex );

    }


    void                        vert_line( const int x, const int top,

                                           const int bottom, const TEX tex )

    {

        for( int y = top; y < bottom; ++y )

            put( x, y, tex );

    }


    void                        rectangle( const int left, const int top,

                                           const int right, const int bottom, TEX tex )

    {

        const   int y2  = bottom - 1;

        horz_line( left, right, top, tex );

        horz_line( left, right, y2, tex );

        for( int y = top + 1; y < y2; ++y )

            horz_line( left, right, y, tex );

    }


    void                        frame( const int left, const int top,

                                       const int right, const int bottom, TEX tex )

    {

        const   int y2  = bottom - 1;

        horz_line( left, right, top, tex );

        horz_line( left, right, y2, tex );

        vert_line( left, top + 1, y2, tex );

        vert_line( right - 1, top + 1, y2, tex );

    }


    void                        frame_bold( const int left, const int top,

                                            const int right, const int bottom, TEX tex )

    {

        const   int y2  = bottom - 1;

        frame( left, top, right, bottom, tex );

        vert_line( left + 1, top + 1, y2, tex );

        vert_line( right - 2, top + 1, y2, tex );

    }


    template< int w, int h >

    void                        draw( const int left, const int top, const PAGE< w, h >& page )

    {

        const   int x_  = max( 0, -left );

        const   int y_  = max( 0, -top  );

        const   int w_  = min( w, x_size - left );

        const   int h_  = min( h, y_size - top  );

        for( int y = y_; y < h_; ++y )

        {

            const   int yt = y + top;

            for( int x = x_; x < w_; ++x )

                if( page.cells[ y ][ x ].letter )

                    put( x + left, yt, page.cells[ y ][ x ] );

        }

    }


    void                        to_gray()

    {

        for( auto& tex : buffer )

            tex.color   =   gray_table[ static_cast< u8 >( get_fore( tex.color ) ) ] /

                            gray_table[ static_cast< u8 >( get_back( tex.color ) ) ];

    }


    void                        to_lighter()

    {

        for( auto& tex : buffer )

        {

            auto    fore    = get_fore( tex.color );

            auto    back    = get_back( tex.color );

            tex.color       = ++fore / ++back;

        }

    }


    void                        to_darker()

    {

        for( auto& tex : buffer )

        {

            auto    fore    = get_fore( tex.color );

            auto    back    = get_back( tex.color );

            tex.color       = --fore / --back;

        }

    }

};

//------------------------------+---------------------------------------------------------------


template< int x_size = 80, int y_size = 25 >

class                           SCREEN

{

    char                        buffer[ 8192 ];

    HANDLE                      handle;


    void                        go( const short x, const short y ) const

    {

        fflush( stdout );

        SetConsoleCursorPosition( handle, { x, y } );

    }


    auto                        color( const u8 c ) const

    {

        fflush( stdout );

        SetConsoleTextAttribute( handle, c );

        return  c;

    }


    void                        resize( short w, short h ) const

    {

        COORD       coord { w, h };

        SetConsoleScreenBufferSize( handle, coord );

        SMALL_RECT  rect = { 0,0, w - 1, h - 1 };

        SetConsoleWindowInfo( handle, TRUE, &rect );

    }


public:

    PAGE< x_size, y_size >      top_page;


    CRE                         SCREEN( bool full_screen = false )

    :   handle( GetStdHandle( STD_OUTPUT_HANDLE ) )

    {

        setvbuf( stdout, (char*)&buffer, _IOFBF, sizeof buffer );

        if( full_screen )

            SetConsoleDisplayMode( handle, CONSOLE_FULLSCREEN_MODE, NULL );

        SetConsoleMode( handle, 0x0008 ); // DISABLE_NEWLINE_AUTO_RETURN

        cursor( false );

    }


    auto                        xsize() const { return  x_size; }

    auto                        ysize() const { return  y_size; }


    void                        cursor( const bool show ) const

    {

        CONSOLE_CURSOR_INFO info = { 1, show };

        SetConsoleCursorInfo( handle, &info );

    }


    void                        flush()

    {

        resize( x_size, y_size );

        u8  old_color = color( top_page.buffer[ 0 ].color );


        for( int y = 0; y < y_size; ++y )

        {

            go( 0, y );

            TEX* scanline = top_page.cells[ y ];

            for( int x = 0; x < x_size; ++x )

            {

                if( old_color != scanline[ x ].color )

                    old_color = color( scanline[ x ].color );

                putchar( scanline[ x ].letter );

            }

        }


        fflush( stdout );

    }


    void                        fadeout( const int delay = 1000 )

    {

        top_page.to_gray();

        flush();

        for( int i = 0; i < 4; ++i )

        {

            ::Sleep( delay );

            top_page.to_lighter();

            flush();

        }

    }


    void                        fadein( const int delay = 1000 )

    {

        top_page.to_gray();

        flush();

        for( int i = 0; i < 4; ++i )

        {

            ::Sleep( delay );

            top_page.to_darker();

            flush();

        }

    }


    template< class F = void(*)() >

    void                        waitfor_press( F f = []{} )

    {

        while( !_kbhit() ) f();

        getchar();

    }

};

//------------------------------+---------------------------------------------------------------

//  여기서부터 데모 예제


SCREEN<> stage;


int main()

{

    stage.top_page.fill( { C4::silver / C4::black, ' ' } );

    stage.top_page.frame_bold( 0, 0, stage.top_page.xsize(), stage.top_page.ysize(),

        { C4::black / C4::blue, ' ' } );


    const   u8  color_set1[] = { 15, 7, 8 };

    stage.top_page.puts( 35, 2, "Hello teX", color_set1 );


    const   C4  color_set2[] = { C4::red, C4::white, C4::white, C4::white };

    stage.top_page.puts( 4, 4, "Codesafer's C++ programming guide", color_set2 );


    PAGE<>  back( stage.top_page );


    stage.waitfor_press( [ &color_set2 ]

    {

        static  int i = 0;

        stage.top_page.puts( 4, 6, "Codesafer's C++ programming guide", color_set2, i++ );

        stage.flush();

    } );

    stage.fadeout();


    PAGE< 4, 4 >    stamp;

    stamp.rectangle( 0, 0, 4, 4, { C4::white / C4::blue, ' ' } );


    for( int y = 0; y < stage.ysize(); y += stamp.ysize() )

        for( int x = 0; x < stage.xsize(); x += stamp.xsize() )

        {

            stage.top_page.draw( x, y, stamp );

            stage.flush();

            ::Sleep( 3 );

        }


    int frames = 0;

    stage.waitfor_press( [ &frames ]

    {

        for( int i = (int)C4::blue; i < 256; i += 16 )

        {

            stage.top_page.text_color = (u8)i;

            stage.top_page.fill( i );

            stage.top_page.text_number( 30, 12, "Hello teX", ++frames );

            stage.flush();

            ::Sleep( 30 );

        }

    } );

    stage.fadein();


    for( int y = 0; y < back.ysize(); ++y )

    {

        stage.top_page.draw( 0, y, back );

        stage.flush();

        ::Sleep( 16 );

    }


    for( int y = back.ysize(); y >= -back.ysize(); --y )

    {

        stage.top_page.draw( 0, y, back );

        stage.flush();

        ::Sleep( 16 );

    }


    for( int y = -back.ysize(); y < 0; ++y )

    {

        stage.top_page.draw( 0, y, back );

        stage.flush();

        ::Sleep( 16 );

    }


    for( int x = 0; x < back.xsize(); ++x )

    {

        stage.top_page.draw( x, 0, back );

        stage.flush();

        ::Sleep( 16 );

    }


    for( int x = back.xsize(); x >= -back.xsize(); --x )

    {

        stage.top_page.draw( x, 0, back );

        stage.flush();

        ::Sleep( 16 );

    }


    stage.waitfor_press();


    return  0;

}

//==============================================================================================


1.  windows 전용 ( VS2015 이상 권장 )

2.  SCREEN 과 PAGE 로 분리해서 이미지는 PAGE 에서 작업하고 복수의 PAGE 를 사용가능하며,

    SCREEN 의 top_page 에 그려주면 되는 식.

3.  fade-in / fade-out 적용. ( grayscale 일때 제대로임 )

4.  따라서 demo 늘어남

5.  우측 최하단에 문자 찍으면 스크롤 밀리던 문제 해결

6.  함수 다 테스트 안해봄~ ( 널문자는 투명으로 처리하게 해 뒀는데 테스트 안해봄 )


갖고 노실분 갖고 노십쇼~