#include <time.h>
#include <iostream>
#include <random>
#include <Windows.h>
#include <conio.h>

using namespace std;

// Map Width (constexpr)
constexpr int MAP_WIDTH = 12;
int up, down, lft, rht, R, space, score;
// Map Height (constexpr)
constexpr int MAP_HEIGHT = 22;



// Rectangle 구조체
struct stRect
{
   int nWidth;
   int nHeight;
};

// Key Code
enum eKeyCode
{
    KEY_UP = 72,     // 방향키 ↑
    KEY_DOWN = 80,   // 방향키 ↓
    KEY_LEFT = 75,   // 방향키 ←
    KEY_RIGHT = 77,  // 방향키 →
    KEY_SPACE = 32,  // 스페이스바
    KEY_R = 114,     // R키
};


// 콘솔 관련 설정 값을 가지고 있을 구조체
struct stConsole
{
   // Console Handler
   HANDLE hConsole;
   // Console Rect Data
   stRect rtConsole;
   // Console Buffer Handler
   HANDLE hBuffer[2];
   // Current Console Buffer Index
   int nCurBuffer;

   stConsole()
      : hConsole(nullptr), hBuffer{ nullptr, }, nCurBuffer(0)
   {}
};

// Origin Map
int ORIGIN_MAP[MAP_HEIGHT][MAP_WIDTH] =
{
   {1,1,1,1,1,1,1,1,1,1,1,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,0,0,0,0,0,0,0,0,0,0,1,},
   {1,1,1,1,1,1,1,1,1,1,1,1,},
};


// Block Type
const char BLOCK_TYPES[][4] =
{
   "  ",  // 빈 공간
   "▣",  // 프레임
   "□",  // 블록
   "★"  
};

// Console Data
//stConsole g_console;
stConsole console;

/**
@brief      Rendering function
@param      nXOffset   X Offset (그림을 그릴 때 왼쪽에서부터)
@param      nYOffset   Y Offset (그림을 그릴 때 위쪽에서부터)
@return
*/
void Render(int nXOffset = 0, int nYOffset = 0)
{
    COORD coord{ 0, };
    int nXAdd = 0;
    DWORD dw = 0;

    // Map 그리기
    {
        for (int nY = 0; nY < MAP_HEIGHT; ++nY)
        {
            nXAdd = 0;

            for (int nX = 0; nX < MAP_WIDTH; nX++)
            {
                coord.X = nXAdd + nXOffset;
                coord.Y = nY + nYOffset;

                // 커서의 위치를 이동
                SetConsoleCursorPosition(console.hBuffer[console.nCurBuffer], coord);
                // 출력 버퍼의 해당 커서 위치에 출력
                WriteFile(console.hBuffer[console.nCurBuffer], BLOCK_TYPES[ORIGIN_MAP[nY][nX]], sizeof(BLOCK_TYPES[ORIGIN_MAP[nY][nX]]), &dw, NULL);

                // X 위치 이동
                nXAdd += 2;

                // 굴림체 폰트에서는 특수문자의 경우 특수문자 하나가 띄어쓰기 2개와 크기가 같습니다.
                // (보이는 것만 그렇고 실제로는 특수문자도 공간을 하나만 차지합니다.)
                // 그렇기 때문에 띄어쓰기가 나올 경우 2칸을 움직이게 합니다.
               // if (ORIGIN_MAP[nY][nX] == 0)
                 //   nXAdd += 1;
            }
        }
        char buf[110] = {0};
        sprintf(buf, "%d%d%d%d%d%d", up, down, lft, rht, space, R);
        SetConsoleCursorPosition(console.hBuffer[console.nCurBuffer], { 4, 23 });
        WriteFile(console.hBuffer[console.nCurBuffer], buf, 10, &dw, NULL);

        sprintf(buf, "SCORE : %04d", score);
        SetConsoleCursorPosition(console.hBuffer[console.nCurBuffer], { 4, 24 });
        WriteFile(console.hBuffer[console.nCurBuffer], buf, 10, &dw, NULL);
    }
}

void InitGame(bool bInitConsole = true)
{
    srand(time(NULL));
   // Initialize Console Data
   if (bInitConsole)
   {
       // 현재 콘솔의 핸들을 받아옵니다.
      console.hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
      // 현재 콘솔 버퍼의 인덱스를 0으로 초기화
      console.nCurBuffer = 0;

      // 콘솔 관련 설정
      CONSOLE_CURSOR_INFO consoleCursor{ 1, FALSE };  // 콘솔의 커서 깜빡임을 제거합니다.
      CONSOLE_SCREEN_BUFFER_INFO consoleInfo{ 0, };
      GetConsoleScreenBufferInfo(console.hConsole, &consoleInfo);
      consoleInfo.dwSize.X = 20;    // 콘솔의 Width
      consoleInfo.dwSize.Y = 30;    // 콘솔의 Height

      // 콘솔의 크기를 다시 계산 (나중에 그림 그릴때 사용)
      console.rtConsole.nWidth = consoleInfo.srWindow.Right - consoleInfo.srWindow.Left;
      console.rtConsole.nHeight = consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top;

      // 콘솔의 첫번째 화면 버퍼 생성
      console.hBuffer[0] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
      SetConsoleScreenBufferSize(console.hBuffer[0], consoleInfo.dwSize);    // 화면 버퍼 크기 설정
      SetConsoleWindowInfo(console.hBuffer[0], TRUE, &consoleInfo.srWindow); // 콘솔 설정
      SetConsoleCursorInfo(console.hBuffer[0], &consoleCursor);              // 콘솔의 커서 설정

      // 콘솔의 두번째 화면 버퍼 생성
      console.hBuffer[1] = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
      SetConsoleScreenBufferSize(console.hBuffer[1], consoleInfo.dwSize);
      SetConsoleWindowInfo(console.hBuffer[1], TRUE, &consoleInfo.srWindow);
      SetConsoleCursorInfo(console.hBuffer[1], &consoleCursor);
   }
}

void DestroyGame()
{
   if (console.hBuffer[0] != nullptr)
   {
      CloseHandle(console.hBuffer[0]);
   }

   if (console.hBuffer[1] != nullptr)
   {
      CloseHandle(console.hBuffer[1]);
   }
}

// 화면 클리어
void ClearScreen()
{
   COORD pos{ 0, };
   DWORD dwWritten = 0;
   unsigned size = console.rtConsole.nWidth * console.rtConsole.nHeight;

   // 콘솔 화면 전체를 띄어쓰기를 넣어 빈 화면처럼 만듭니다.
   FillConsoleOutputCharacter(console.hConsole, ' ', size, pos, &dwWritten);
   SetConsoleCursorPosition(console.hConsole, pos);
}

// 버퍼 스왑
void BufferFlip()
{
   // 화면 버퍼 설정
   SetConsoleActiveScreenBuffer(console.hBuffer[console.nCurBuffer]);
    // 화면 버퍼 인덱스를 교체
   console.nCurBuffer = console.nCurBuffer ? 0 : 1;
}

void InputKey()
{
    bool keyState[256] = {false}; // 모든 키의 상태를 저장할 배열
    DWORD dw = 0;
    int nKey = 0;

    if (_kbhit() > 0)
    {
        nKey = _getch();
        switch (nKey)
        {
            case eKeyCode::KEY_UP:    // 방향키 위를 눌렀을 때
            {
                if( up ) break;
                up = 1;
                break;
            }
            case eKeyCode::KEY_DOWN:  // 방향키 아래를 눌렀을 때
            {
                if( down ) break;
                down = 1;
                break;
            }
            case eKeyCode::KEY_LEFT:  // 방향키 왼쪽을 눌렀을 때
            {
                if( lft ) break;
                lft = 1;
                break;
            }
            case eKeyCode::KEY_RIGHT: // 방향키 오른쪽을 눌렀을 때
            {
                if( rht ) break;
                rht = 1;
                break;
            }
            case eKeyCode::KEY_SPACE: // 스페이스바를 눌렀을 때
            {
                if( space ) break;
                space = 1;
                while( ORIGIN_MAP[rand()%20+1][rand()%10+1] );
                int temp = rand()%2+2;
                ORIGIN_MAP[rand()%20+1][rand()%10+1] = temp;
                break;
            }
            case eKeyCode::KEY_R:     // R키를 눌렀을 때
            {
                if( R ) break;
                R = 1;
                break;
            }

        }

    }
    // 키 뗌 감지
    for (int key = 0; key < 256; key++) {
        if (GetAsyncKeyState(key) & 0x8000) { // 키가 눌린 상태
            if (!keyState[key]) { // 키가 이전에 눌리지 않았다면
                keyState[key] = true; // 눌린 상태로 변경
            }
        } else {
            if (keyState[key]) { // 키가 눌렸던 상태
                keyState[key] = false; // 눌리지 않은 상태로 변경
            }
        }
    }

    if( !keyState[38] ) up = 0;
    if( !keyState[40] ) down = 0;
    if( !keyState[37] ) lft = 0;
    if( !keyState[39] ) rht = 0;
    if( !keyState[32] ) space = 0;
    if( !keyState[82] ) R = 0;

}
void Process()
{
    for(int i = 1 ; i < 22 ; i++) for(int j = 1 ; j < 12 ; j++ )
        if( ORIGIN_MAP[i][j] == 2 ) score--;
        else if( ORIGIN_MAP[i][j] == 3 ) score++;
}
int main(void)
{
   InitGame();

   char chBuf[256] = { 0, };
   COORD coord{ 0,0 };
   DWORD dw = 0;

   while (true)
   {

      InputKey();

      Render(3, 1);
      Process();
      ClearScreen();
      BufferFlip();
      Sleep(1);
   }

   DestroyGame();

   return 0;
}