/*//
_                    _             _
| |    __ _ _ __ ___ | |__       _-(")-
| |   / _` | '_ ` _ \| '_ \    `%%%%%
| |__| (_| | | | |_| | |_) | _  // \\
|_____\__,_|_| |_| |_|_.__/_| |__  ___
                 | |   / _` | '_ \/ __|
                 | |__| (_| | |_) \__ \
 2018-05-24      |_____\__,_|_.__/|___/
//*/

#include <windows.h>
#include <gl/glew.h>
//#include <gl/gl.h>
//#include <gl/glu.h>
#include <stdio.h>
#include <math.h>
#include "res.h"

#define IDT_REDRAW_FRAME 2018

GLuint uiTextureEyeID;
GLuint uiTextureBodyID;

unsigned char* ReadBmpFromFile(const char* szFileName, int &riWidth, int &riHeight)
{
  BITMAPFILEHEADER     bfh;
  BITMAPINFOHEADER     bih;

  int                i, j, h, v, lev, l, ls;
  unsigned char*     buff = NULL;

  unsigned char* p_palette = NULL;
  unsigned short n_colors = 0;

  unsigned char* pRGBBuffer=NULL;

  FILE* hfile;
  fopen_s(&hfile,szFileName, "rb");

  if (hfile != NULL)
  {
    fread(&bfh, sizeof(bfh), 1, hfile);
    if (!(bfh.bfType != 0x4d42 || (bfh.bfOffBits < (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)))))
    {
      fread(&bih, sizeof(bih), 1, hfile);
      v = bih.biWidth;
      h = bih.biHeight;
      lev = bih.biBitCount;

      riWidth = v;
      riHeight = h;
      pRGBBuffer = new unsigned char[riWidth*riHeight * 3]; //Zaalokowanie odpowiedniego buffora obrazu

                                                            //Załaduj Palete barw jesli jest
      if ((lev == 1) || (lev == 4) || (lev == 8))
      {
        n_colors = 1 << lev;
        p_palette = new unsigned char[4 * n_colors];
        fread(p_palette, 4 * n_colors, 1, hfile);
      }

      fseek(hfile, bfh.bfOffBits, SEEK_SET);

      buff = new unsigned char[v * 4];

      switch (lev)
      {
      case 1:
        //Nie obsługiwane
        break;
      case 4:
        //nie Obsługiwane
        break;
      case 8: //Skala szarości
        ls = (v + 3) & 0xFFFFFFFC;
        for (j = (h - 1); j >= 0; j--)
        {
          fread(buff, ls, 1, hfile);
          for (i = 0, l = 0; i<v; i++)
          {
            pRGBBuffer[((j*riWidth) + i) * 3 + 2] = p_palette[(buff[i] << 2) + 2];//R
            pRGBBuffer[((j*riWidth) + i) * 3 + 1] = p_palette[(buff[i] << 2) + 1];//G
            pRGBBuffer[((j*riWidth) + i) * 3 + 0] = p_palette[(buff[i] << 2) + 0];//B
          }
        };
        break;
      case 24:
        //bitmapa RGB
        ls = (v * 3 + 3) & 0xFFFFFFFC;
        for (j = (h - 1); j >= 0; j--)
        {
          //x_fread(hfile,buff,ls);
          fread(buff, ls, 1, hfile);
          for (i = 0, l = 0; i<v; i++, l += 3)
          {
            pRGBBuffer[((j*riWidth) + i) * 3 + 2] = buff[l + 0];
            pRGBBuffer[((j*riWidth) + i) * 3 + 1] = buff[l + 1];
            pRGBBuffer[((j*riWidth) + i) * 3 + 0] = buff[l + 2];
          };
        };
        break;
      case 32:
        // RGBA bitmap 
        for (j = (h - 1); j >= 0; j--)
        {
          fread(buff, v * 4, 1, hfile);
          for (i = 0, l = 0; i<v; i++, l += 4)
          {
            pRGBBuffer[((j*riWidth) + i) * 3 + 0] = buff[l + 0];
            pRGBBuffer[((j*riWidth) + i) * 3 + 1] = buff[l + 1];
            pRGBBuffer[((j*riWidth) + i) * 3 + 2] = buff[l + 2];
          }
        };
        break;
      };
      delete buff;
      if (p_palette) delete p_palette;

    }
  }
  return pRGBBuffer;
}

void DrawAxes(float a) {
  glPushMatrix();
  glScalef(a, a, a);
  glBegin(GL_LINES);
  glColor3f(0.0, 0.0, 1.0);
  glVertex3f(-10, 0, 0);
  glVertex3f(+10, 0, 0);
  glColor3f(0.0, 1.0, 0.0);
  glVertex3f(0, -10, 0);
  glVertex3f(0, +10, 0);
  glColor3f(1.0, 0.0, 0.0);
  glVertex3f(0, 0, -10);
  glVertex3f(0, 0, +10);
  glEnd();
  glPopMatrix();
}

void DrawBox(float a, float b, float c) {
  glPushMatrix();
  glScalef(a/2, b/2, c/2);
  glBegin(GL_QUADS);
    glNormal3i(0, -1, 0);
    glTexCoord2f(0.0, 0.0);
    glVertex3i(-1, -1, -1);
    glTexCoord2f(0.0, 1.0);
    glVertex3i(-1, -1, +1);
    glTexCoord2f(1.0, 1.0);
    glVertex3i(+1, -1, +1);
    glTexCoord2f(1.0, 0.0);
    glVertex3i(+1, -1, -1);

    glNormal3i(0, +1, 0);
    glTexCoord2f(0.0, 0.0);
    glVertex3i(-1, +1, -1);
    glTexCoord2f(0.0, 1.0);
    glVertex3i(-1, +1, +1);
    glTexCoord2f(1.0, 1.0);
    glVertex3i(+1, +1, +1);
    glTexCoord2f(1.0, 0.0);
    glVertex3i(+1, +1, -1);

    glNormal3i(-1, 0, 0);
    glTexCoord2f(0.0, 0.0);
    glVertex3i(-1, -1, -1);
    glTexCoord2f(0.0, 1.0);
    glVertex3i(-1, -1, +1);
    glTexCoord2f(1.0, 1.0);
    glVertex3i(-1, +1, +1);
    glTexCoord2f(1.0, 0.0);
    glVertex3i(-1, +1, -1);

    glNormal3i(+1, 0, 0);
    glTexCoord2f(0.0, 0.0);
    glVertex3i(+1, -1, -1);
    glTexCoord2f(0.0, 1.0);
    glVertex3i(+1, -1, +1);
    glTexCoord2f(1.0, 1.0);
    glVertex3i(+1, +1, +1);
    glTexCoord2f(1.0, 0.0);
    glVertex3i(+1, +1, -1);

    glNormal3i(0, 0, -1);
    glTexCoord2f(0.0, 0.0);
    glVertex3i(-1, -1, -1);
    glTexCoord2f(0.0, 1.0);
    glVertex3i(-1, +1, -1);
    glTexCoord2f(1.0, 1.0);
    glVertex3i(+1, +1, -1); 
    glTexCoord2f(1.0, 0.0);
    glVertex3i(+1, -1, -1);

    glNormal3i(0, 0, +1);
    glTexCoord2f(0.0, 0.0);
    glVertex3i(-1, -1, +1);
    glTexCoord2f(0.0, 1.0);
    glVertex3i(-1, +1, +1);
    glTexCoord2f(1.0, 1.0);
    glVertex3i(+1, +1, +1);
    glTexCoord2f(1.0, 0.0);
    glVertex3i(+1, -1, +1);
  glEnd();
  glPopMatrix();
}

void DrawFly(float fScale) {
  glPushMatrix();
    glScalef(fScale, fScale, fScale);
    //Body of the fly
    glColor3f(0.2, 0.2, 0.2);
    DrawBox(2.0, 1.0, 1.5);

    //Legs
    for(int i=0;i<3;i++)
      for (int j = 0; j < 2; j++) {
        glPushMatrix();
        glTranslatef(-1.0+(2.0/5.0/2.0)+i*(2.0- (2.0 / 5.0))/2, -0.5-0.5/2, -0.75+(1.5/3/2)+j*(1.5-1.5/3)/1);
        DrawBox(2.0 / 5.0, 0.5, 1.5 / 3.0);
        glPopMatrix();
      }
    
    //Eyes
    glColor3f(1.0, 1.0, 1.0);
    glBindTexture(GL_TEXTURE_2D, uiTextureEyeID);
    for (int i = 0; i < 2; i++) {
      glPushMatrix();
      glTranslatef(-1.0,0.5+0.6,-0.75+i*1.5);
      DrawBox(1.4, 1.4, 1.4);
      glPopMatrix();
    }
    glBindTexture(GL_TEXTURE_2D, 0);

    //Nose
    glColor3f(0.2, 0.2, 0.2);
    glPushMatrix();
    glTranslatef(-1.0-0.75, 0.2, 0.0);
    DrawBox(1.5, 0.2, 0.2);
    glPopMatrix();

    //Wings
    glColor3f(0.8, 0.8, 1.0);
    for (int i = -1; i < 2; i+=2) {
      glPushMatrix();
      glTranslatef(0.9, 0.5, i*0.70);
      glRotatef(- i * 30, 0, 1, 0);
      glTranslatef(-1.0, 0, 0);
      glRotatef(7.5*sin(GetTickCount()/100.0)+7.5, 0, 0, 1);
      glTranslatef(1.0, 0, 0);
      DrawBox(2.0, 0.2, 1.4);
      glPopMatrix();
    }

  glPopMatrix();
}

void DrawFrame() {
  glClearColor(0.1, 0.1, 0.1, 0.5);
  glClearDepth(1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glLoadIdentity();
  glTranslatef(0.0, -1.0, -10.0);
  
  static float fAngle = 0.0;
  fAngle += 1;
  glRotatef(fAngle, 0, 1, 0);

  DrawAxes(1.0);

  glColor3f(1.0, 1.0, 1.0);

  glEnable(GL_TEXTURE_2D);
  glActiveTexture(GL_TEXTURE0);
  
  //DrawBox(2.0, 1.0, 1.0);
  DrawFly(1.0);
}

void InitOpenGL() {

  glewExperimental = GL_TRUE;
  GLenum err = glewInit();
  if (err != GLEW_OK)
  {
    char strMsg[1000];
    printf_s(strMsg, "%s", glewGetErrorString(err));
    MessageBox(0, (LPCWSTR)strMsg, TEXT("Error Glew"), MB_OK);
  }

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_TEXTURE_2D);
  glShadeModel(GL_SMOOTH);
}

void InitTexture() {
  int iWidth;
  int iHeight;
  unsigned char* pImageData = ReadBmpFromFile("..\\res\\eye.bmp", iWidth, iHeight);

  
  glActiveTexture(GL_TEXTURE0);
  glGenTextures(1, &uiTextureEyeID);
  glBindTexture(GL_TEXTURE_2D, uiTextureEyeID);
  //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, iWidth, iHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, pImageData);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  //glGenerateMipmap(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, 0);
  //glGenerateMipmap(GL_TEXTURE_2D);
  delete[] pImageData;

  glShadeModel(GL_FLAT);
}

INT_PTR OnWmClose(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
) {
  DestroyWindow(hwndDlg);
  PostQuitMessage(0);
  return TRUE;
}

INT_PTR OnWmDestroy(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
) {
  HGLRC hglrc = wglGetCurrentContext();
  HDC hdc = wglGetCurrentDC();
  wglMakeCurrent(NULL, NULL);
  wglDeleteContext(hglrc);
  ReleaseDC(hwndDlg,hdc);

  KillTimer(hwndDlg, IDT_REDRAW_FRAME);

  PostQuitMessage(0);
  return TRUE;
}

INT_PTR OnWmInitDialog(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
) {
  HDC      hdc;
  HGLRC    hglrc;
  hdc = GetDC(hwndDlg);

  PIXELFORMATDESCRIPTOR pfd = {
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL,
    PFD_TYPE_RGBA,
    24,
    0,0,0,0,0,0,
    0,0,0,
    0,0,0,0,
    32,
    0,0,
    PFD_MAIN_PLANE,
    0,0,0,0
  };
  int iPixelFormat = ChoosePixelFormat(hdc, &pfd);
  SetPixelFormat(hdc, iPixelFormat, &pfd);

  if (hglrc = wglCreateContext(hdc)) {
    bool bHaveCurrentRC = wglMakeCurrent(hdc, hglrc);
  }

  InitOpenGL();

  InitTexture();

  return TRUE;
}

INT_PTR OnWmSize(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
) {
  int iWidth = LOWORD(lParam);
  int iHeight = HIWORD(lParam);

  if (iHeight == 0) iHeight++;

  glViewport(0, 0, iWidth, iHeight);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0, ((float)iWidth) / iHeight, 0.1, 100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  return TRUE;
}

INT_PTR OnWmTimer(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
) {
  switch (wParam) {
    case IDT_REDRAW_FRAME:
      DrawFrame();
      HDC hdc = wglGetCurrentDC();
      glFlush();
      SwapBuffers(hdc);
      break;

  }
  return TRUE;
}

INT_PTR CALLBACK DialogProc(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
)
{
  switch (uMsg) {
    case WM_INITDIALOG:
      return OnWmInitDialog(hwndDlg, uMsg, wParam, lParam);

    case WM_CLOSE:
      return OnWmClose(hwndDlg, uMsg, wParam, lParam);
    
    case WM_DESTROY:
      return OnWmDestroy(hwndDlg, uMsg, wParam, lParam);

    case WM_SIZE:
      return OnWmSize(hwndDlg, uMsg, wParam, lParam);

    case WM_TIMER:
      return OnWmTimer(hwndDlg, uMsg, wParam, lParam);
  }
  return FALSE;
}

int CALLBACK WinMain(
  _In_ HINSTANCE hInstance,
  _In_ HINSTANCE hPrevInstance,
  _In_ LPSTR     lpCmdLine,
  _In_ int       nCmdShow
) {
  HWND hwndMainDialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);

  ShowWindow(hwndMainDialog,SW_SHOW);

  SetTimer(hwndMainDialog, IDT_REDRAW_FRAME, 1000 / 25, NULL);

  //Main loop
  MSG uMsg;
  BOOL bRet;
  while ((bRet=GetMessage(&uMsg, hwndMainDialog, 0, 0)) != 0) {
    TranslateMessage(&uMsg);
    DispatchMessage(&uMsg);
  }
  return uMsg.wParam;
}