"""
simpleglfw.py

Prosty program OpenGL Pythona, który wykorzystuje PyOpenGL + GLFW do 
uzyskania kontekstu OpenGL 3.2.

Autor: Mahesh Venkitachalam
"""

import OpenGL
from OpenGL.GL import *

import numpy, math, sys, os
import glutils

import glfw

strVS = """
#version 330 core

layout(location = 0) in vec3 aVert;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform float uTheta;

out vec2 vTexCoord;

void main() {
  // transformacja rotacyjna
  mat4 rot =  mat4(
		vec4( cos(uTheta),  sin(uTheta), 0.0, 0.0),
		vec4(-sin(uTheta),  cos(uTheta), 0.0, 0.0),
	    vec4(0.0,         0.0,         1.0, 0.0),
	    vec4(0.0,         0.0,         0.0, 1.0)
	    );
  // transformowanie wierzchołlka
  gl_Position = uPMatrix * uMVMatrix * rot * vec4(aVert, 1.0); 
  // ustawienie współrzędnych tekstury
  vTexCoord = aVert.xy + vec2(0.5, 0.5);
}
"""
strFS = """
#version 330 core

in vec2 vTexCoord;

uniform sampler2D tex2D;
uniform bool showCircle;

out vec4 fragColor;

void main() {
  if (showCircle) {
#if 1
    // odrzucanie fragmentu spoza okręgu
    if (distance(vTexCoord, vec2(0.5, 0.5)) > 0.5) {
      discard;
    }
    else {
      fragColor = texture(tex2D, vTexCoord);
    }
#else

   // Answer to home work!
    
   #define M_PI 3.1415926535897932384626433832795
   
   float r = distance(vTexCoord, vec2(0.5, 0.5));
   if (sin(16*M_PI*r) < 0.0) {
      discard;
   }
   else {
      fragColor = texture(tex2D, vTexCoord);
   }
#endif
  }
  else {
     fragColor = texture(tex2D, vTexCoord);
  }
}
"""

class Scene:    
    """Klasa sceny 3D OpenGL"""
    # inicjowanie
    def __init__(self):
        # tworzenie shadera
        self.program = glutils.loadShaders(strVS, strFS)

        glUseProgram(self.program)

        self.pMatrixUniform = glGetUniformLocation(self.program, 
                                                   b'uPMatrix')
        self.mvMatrixUniform = glGetUniformLocation(self.program, 
                                                  b'uMVMatrix')
        # tekstura 
        self.tex2D = glGetUniformLocation(self.program, b'tex2D')

        # definiowanie wierzchołków pasa trójkątów 
        vertexData = numpy.array(
            [-0.5, -0.5, 0.0, 
              0.5, -0.5, 0.0, 
              -0.5, 0.5, 0.0,
              0.5, 0.5, 0.0], numpy.float32)

        # konfigurowanie obiektu VAO
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)
        # wierzchołki
        self.vertexBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        # ustawianie danych bufora 
        glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, 
                     GL_STATIC_DRAW)
        # włączenie tablicy wierzchołków
        glEnableVertexAttribArray(0)
        # ustawienie wskaźnika danych bufora
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
        # usunięcie dowiązania do obiektu VAO
        glBindVertexArray(0)

        # czas
        self.t = 0 

        # tekstura
        self.texId = glutils.loadTexture('star.png')

        # pokazać okrąg?
        self.showCircle = False
        
    # krok
    def step(self):
        # zwiększanie kąta
        self.t = (self.t + 1) % 360
        # ustawienie kąta shadera w radianach
        glUniform1f(glGetUniformLocation(self.program, 'uTheta'), 
                    math.radians(self.t))

    # renderowanie 
    def render(self, pMatrix, mvMatrix):        
        # użycie shadera
        glUseProgram(self.program)
        
        # ustawienie macierzy rzutowania
        glUniformMatrix4fv(self.pMatrixUniform, 1, GL_FALSE, pMatrix)

        # ustawienie macierzy widoku modelu
        glUniformMatrix4fv(self.mvMatrixUniform, 1, GL_FALSE, mvMatrix)

        # pokazać okrąg?
        glUniform1i(glGetUniformLocation(self.program, b'showCircle'), 
                    self.showCircle)

        # włączenie tekstury 
        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, self.texId)
        glUniform1i(self.tex2D, 0)

        # wiązanie VAO
        glBindVertexArray(self.vao)
        # rysowanie
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
        # usunięcie dowiązania do VAO
        glBindVertexArray(0)


class RenderWindow:
    """Klasa okna renderowania GLFW"""
    def __init__(self):

        # zapisanie bieżącego katalogu roboczego
        cwd = os.getcwd()

        # inicjowanie glfw — to zmienia cwd
        glfw.glfwInit()
        
        # przywrócenie cwd
        os.chdir(cwd)

        # wskazówki wersji
        glfw.glfwWindowHint(glfw.GLFW_CONTEXT_VERSION_MAJOR, 3)
        glfw.glfwWindowHint(glfw.GLFW_CONTEXT_VERSION_MINOR, 3)
        glfw.glfwWindowHint(glfw.GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE)
        glfw.glfwWindowHint(glfw.GLFW_OPENGL_PROFILE, 
                            glfw.GLFW_OPENGL_CORE_PROFILE)
    
        # utworzenie okna
        self.width, self.height = 640, 480
        self.aspect = self.width/float(self.height)
        self.win = glfw.glfwCreateWindow(self.width, self.height, 
                                         b'simpleglfw')
        # ustawienie bieżącego kontekstu
        glfw.glfwMakeContextCurrent(self.win)
        
        # inicjowanie GL
        glViewport(0, 0, self.width, self.height)
        glEnable(GL_DEPTH_TEST)
        glClearColor(0.5, 0.5, 0.5,1.0)

        # ustawienie wywołań zwrotnych okna
        glfw.glfwSetMouseButtonCallback(self.win, self.onMouseButton)
        glfw.glfwSetKeyCallback(self.win, self.onKeyboard)
        glfw.glfwSetWindowSizeCallback(self.win, self.onSize)        

        # tworzenie 3D
        self.scene = Scene()

        # flaga wyjścia
        self.exitNow = False

        
    def onMouseButton(self, win, button, action, mods):
        #print 'przycisk myszy: ', win, button, action, mods
        pass

    def onKeyboard(self, win, key, scancode, action, mods):
        #print 'klawiatura: ', win, key, scancode, action, mods
        if action == glfw.GLFW_PRESS:
            # ESC, aby zakończyć
            if key == glfw.GLFW_KEY_ESCAPE: 
                self.exitNow = True
            else:
                # przełączanie przycinania
                self.scene.showCircle = not self.scene.showCircle 
        
    def onSize(self, win, width, height):
        #print 'zmiana rozmiaru okna: ', win, width, height
        self.width = width
        self.height = height
        self.aspect = width/float(height)
        glViewport(0, 0, self.width, self.height)

    def run(self):
        # timer inicjatora
        glfw.glfwSetTime(0)
        t = 0.0
        while not glfw.glfwWindowShouldClose(self.win) and not self.exitNow:
            # aktualizowanie co x sekund
            currT = glfw.glfwGetTime()
            if currT - t > 0.1:
                # aktualizowanie czasu
                t = currT
                # czyszczenie
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
                
                # budowanie macierzy rzutowania
                pMatrix = glutils.perspective(45.0, self.aspect, 0.1, 100.0)
                
                mvMatrix = glutils.lookAt([0.0, 0.0, -2.0], [0.0, 0.0, 0.0],
                                          [0.0, 1.0, 0.0])
                # renderowanie
                self.scene.render(pMatrix, mvMatrix)
                # krok 
                self.scene.step()

                glfw.glfwSwapBuffers(self.win)
                # sprawdzanie zdarzeń i przetwarzanie ich
                glfw.glfwPollEvents()
        # koniec
        glfw.glfwTerminate()

    def step(self):
        # czyszczenie
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        
        # budowanie macierzy rzutowania
        pMatrix = glutils.perspective(45.0, self.aspect, 0.1, 100.0)
                
        mvMatrix = glutils.lookAt([0.0, 0.0, -2.0], [0.0, 0.0, 0.0],
                                          [0.0, 1.0, 0.0])
        # renderowanie
        self.scene.render(pMatrix, mvMatrix)
        # krok 
        self.scene.step()

        glfw.SwapBuffers(self.win)
        # spradzanie i przetwarzanie zdarzeń
        glfw.PollEvents()

# funkcja main()
def main():
    print("Uruchamianie programu simpleglfw. "
          "Wciśnij dowolny klawisz, aby przełączyć przycinanie. Wciśnij ESC, aby zakończyć.")
    rw = RenderWindow()
    rw.run()

# wywołanie main
if __name__ == '__main__':
    main()
