import sys
import os
import subprocess
import pickle
from PyQt4 import QtGui, QtCore
from stroke import Stroke
from slide import Slide


class Window(QtGui.QMainWindow):
	""" This is the main class of the application, representing the highest level window.

	Every other Qt Widget will be prompted inside of this main window.
	Thus all settings, attributes, and methods, which are central to the app itself, 
	should be implemented in this class. 

	"""

	def __init__(self):
		""" Main window constructor: all general settings should live here.. """

		super(Window, self).__init__()
		#  Main window dimensions : posX, posY, height, width
		self.setGeometry(50, 50, 600, 600)
		self.setWindowTitle('ANOT8')
		self.setWindowIcon(QtGui.QIcon('./komputar.png'))
		
		#  Timer used during recording
		self.timer = QtCore.QTimer()
		self.timer.timeout.connect(self.counter)

		#  Timer used during replay
		self.replay_timer = QtCore.QTimer()
		self.replay_timer.timeout.connect(self.replay_step)
		
		#  Tracks time elapsed on a given slide, 
		#  it is incremented by timers
		self.elapsed_on_current_slide = 0

		#  Index used during replay of a session
		self.stroke_index = 0
		
		#  Increment steps for the recording/replaying
		self.MILI_STEPS = 10
		self.REPLAY_STEPS = 33
		
		self.recording = False
		self.replaying = False
		self.reviewing = False

		#  Main deck of slides, which contain all the
		#  information about the session
		self.slides = []

		#  This is not used at the moment, but should 
		#  be used to track if the current session has been modified
		#  if so, on exit, ask to save session..
		self.modified = False

		self.create_actions()
		self.create_menus()

		self.home()

	def create_actions(self):
		""" Creates Qt actions that will be added to main menu of the application"""

		self.reviewMode = QtGui.QAction("Review Mode", self)
		self.reviewMode.setCheckable(True)
		self.reviewMode.setChecked(False)
		self.reviewMode.setShortcut("Ctrl+W")
		self.reviewMode.setStatusTip("Activate review mode")
		self.reviewMode.triggered.connect(self.reviewingView)
		
		self.exitAction = QtGui.QAction("Exit", self)
		self.exitAction.setShortcut("Ctrl+Q")
		self.exitAction.setStatusTip("Leave the App")
		self.exitAction.triggered.connect(self.close_application)

		self.openEditor = QtGui.QAction("Timeline Editor", self)
		self.openEditor.setShortcut("Ctrl+E")
		self.openEditor.setStatusTip("Open Timeline Editor")
		self.openEditor.triggered.connect(self.editorView)

		self.openSlideView = QtGui.QAction("Recording studio", self)
		self.openSlideView.setShortcut("Ctrl+R ")
		self.openSlideView.setStatusTip("Open Recording studio")
		self.openSlideView.triggered.connect(self.recordingView)

		self.openFile = QtGui.QAction("Open File", self)
		self.openFile.setShortcut("Ctrl+O")
		self.openFile.setStatusTip("Open File")
		self.openFile.triggered.connect(self.file_open)

		self.openFileEx = QtGui.QAction("Open existing session", self)
		self.openFileEx.setStatusTip("Open existing session")
		self.openFileEx.setShortcut("Ctrl+K")
		self.openFileEx.triggered.connect(self.file_open_existing)

		self.saveFile = QtGui.QAction("Save File", self)
		self.saveFile.setShortcut("Ctrl+S")
		self.saveFile.setStatusTip("Save File")
		self.saveFile.triggered.connect(self.save)

		self.aboutAction = QtGui.QAction("About", self)
		self.aboutAction.setStatusTip("About this application")
		self.aboutAction.triggered.connect(self.about)


		self.recordAction = QtGui.QAction(QtGui.QIcon("./icons/record.png"), "Start/Stop recording", self)
		self.recordAction.triggered.connect(self.record_handler)

		self.replayAction = QtGui.QAction(QtGui.QIcon("./icons/play.png"), "Replay session", self)
		self.replayAction.triggered.connect(self.replay_handler)

		self.nextAction = QtGui.QAction(QtGui.QIcon("./icons/next.png"), "Display next slide", self)
		self.nextAction.triggered.connect(self.display_next_slide)

		self.backAction = QtGui.QAction(QtGui.QIcon("./icons/previous.png"), "Display previous slide", self)
		self.backAction.triggered.connect(self.display_previous_slide)

		self.colorPickAction = QtGui.QAction(QtGui.QIcon("./icons/color_pick.png"),"Pick Color", self)
		self.colorPickAction.triggered.connect(self.penColor)

		self.widthPickAction = QtGui.QAction(QtGui.QIcon("./icons/width_pick.png"),"Pick Width of Pen", self)
		self.widthPickAction.triggered.connect(self.penWidth)

	def reviewingView(self):
		""" Sets review mode on when menu item is clicked. Conflict with recording
			and replay mode not handled yet, to be implemented
		"""
		if self.reviewing is True:
			self.reviewing = False
		else:
			self.reviewing = True


	def create_menus(self):
		""" Creates all menus for the main window """
		# Create the hint status bar
		self.statusBar()
		
		# Create main menus and toolbars
		self.mainMenu = self.menuBar()
		self.toolbar = self.addToolBar("Slide Toolbar")
		
		# Add actions to main menu tabs : File and Edit
		fileMenu = self.mainMenu.addMenu("&File")
		fileMenu.addAction(self.openFile)
		fileMenu.addAction(self.openFileEx)
		fileMenu.addAction(self.saveFile)
		fileMenu.addAction(self.exitAction)

		editorMenu = self.mainMenu.addMenu("&Edit")
		editorMenu.addAction(self.openEditor)
		editorMenu.addAction(self.openSlideView)
		editorMenu.addAction(self.reviewMode)

		helpMenu = self.mainMenu.addMenu("&Help")
		helpMenu.addAction(self.aboutAction)


		self.toolbar.addAction(self.backAction)
		self.toolbar.addAction(self.replayAction)
		self.toolbar.addAction(self.recordAction)
		self.toolbar.addAction(self.nextAction)
		self.toolbar.addAction(self.colorPickAction)
		self.toolbar.addAction(self.widthPickAction)


	def about(self):
		""" Prints out the about information in a dialog box """
		QtGui.QMessageBox.about(self, "About ANOT8",
		"<p>The <b>ANOT8</b> application is an attempt "
		"to fluidify the workflow of creating online lectures, making editing"
		" simple and intuitive. The approach seperates the annotations from the slides,"
		" allowing for efficient single point editing of course video lectures." 
		)


	def counter(self):
		""" Called at a set interval to increment the counter used during recording """
		self.elapsed_on_current_slide += self.MILI_STEPS


	def home(self):
		""" Code specific to the view used for slideshow and recording"""
	
		#  Instanciates, initialize and shows a ScribbleArea widget
		self.scribbleArea = ScribbleArea(self)
		self.scribbleArea.clearImage()
		self.scribbleArea.mainWindow = self 
		self.setCentralWidget(self.scribbleArea)

		self.show()

	def display_next_slide(self):
		""" Fetches and renders the next slide of the slideshow """
		if self.current_slide_index < len(self.slides) - 1:
			if self.recording :
				self.slides[self.current_slide_index].duration = self.elapsed_on_current_slide
			self.current_slide_index += 1
			if self.recording and self.slides[self.current_slide_index].strokes:
				self.slides[self.current_slide_index].strokes = []
			self.reset_elapsed_time()
			self.scribbleArea.openImage(self.slides[self.current_slide_index].path)

	def display_previous_slide(self):
		""" Fetches and renders the previous slide of the slideshow """
		if self.current_slide_index > 0:
			self.current_slide_index -= 1
			self.scribbleArea.openImage(self.slides[self.current_slide_index].path)

	def start_recording(self):
		""" Starts the recording of a new session """
		self.recording = True
		self.elapsed_on_current_slide = 0
		if self.slides[self.current_slide_index].strokes:
			self.slides[self.current_slide_index].strokes = []
		self.reset_elapsed_time()
		self.timer.start(self.MILI_STEPS)

	def stop_recording(self):
		""" Stops the recording of the current session """
		self.recording = False
		self.timer.stop()
		self.slides[self.current_slide_index].duration = self.elapsed_on_current_slide
		self.elapsed_on_current_slide = 0
		for index,slide in enumerate(self.slides):
			print(str(index) + ") duration:" + str(slide.duration) + " | number of strokes : " + str(len(slide.strokes)))

	def reset_elapsed_time(self):
		""" Resets the elapsed time counter """
		self.elapsed_on_current_slide = 0

	def start_replay(self):
		""" Starts the replay of the session currently in memory """
		self.replaying = True
		self.current_slide_index = 0
		self.reset_elapsed_time()
		self.scribbleArea.openImage(self.slides[self.current_slide_index].path)
		self.replay_timer.start(self.REPLAY_STEPS)
		
	def replay_step(self):
		""" Increments the time counter for the replay functionality, 
		also takes care of the drawing of lines on screen and of the transition of slides
		during replay
		"""
		self.elapsed_on_current_slide += self.REPLAY_STEPS
		if self.slides[self.current_slide_index].duration == 0 or self.current_slide_index > len(self.slides) - 1:
			self.stop_replay()
			return 

		while len(self.slides[self.current_slide_index].strokes) > self.stroke_index and self.slides[self.current_slide_index].strokes[self.stroke_index].timestamp < self.elapsed_on_current_slide:
			self.scribbleArea.drawLineReplay(self.slides[self.current_slide_index].strokes[self.stroke_index])
			self.stroke_index += 1

		if self.slides[self.current_slide_index].duration < self.elapsed_on_current_slide:
			self.stroke_index = 0
			self.display_next_slide()

	def stop_replay(self):
		""" Stops the replay """
		self.replaying = False
		self.replay_timer.stop()
		self.reset_elapsed_time()
		self.stroke_index = 0

	def replay_handler(self):
		""" Handles the calls to replay funcitonalities from connected actions """
		if self.replaying == True:
			self.stop_replay()
		else:
			self.start_replay()

	def keyPressEvent(self, keyevent):
		""" Handles and dispatches all of the keyboard events """
		key = keyevent.key()
		if key == QtCore.Qt.Key_Right:
			self.display_next_slide()
		if key == QtCore.Qt.Key_Left:
			self.display_previous_slide()
		if key == 32:
			self.replay_handler()

	def close_application(self):
		""" Called when exiting the application """
		choice = QtGui.QMessageBox.question(self, "Exit Application",
			"Do you really want to exit?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)

		if choice == QtGui.QMessageBox.Yes:
			sys.exit()
		else:
			pass

	def record_handler(self):
		""" Handles the calls to record functionalities from connected actions """
		if self.recording == True:
			self.stop_recording()
		else:
			self.start_recording()

	def file_open_existing(self):
		""" Used to open an existing anot8 file that was previously saved, 
		must contain the config.anot8 file 
		"""
		directory = str(QtGui.QFileDialog.getExistingDirectory(self, "Select Directory")).split("\\").pop()
		if "config.anot8" in set(os.listdir(directory)):
			self.slide_folder = directory
			self.current_slide_index = 0
			try:
				file_path = "./" + self.slide_folder + "/config.anot8"
				with open(file_path, "rb") as f:
					self.slides = pickle.load(f)
			except IOError as err:
				print("Error while reading config file" +  err)
			self.scribbleArea.openImage(self.slides[self.current_slide_index].path)
		else:
			print("non valid project directory")
		

	def penColor(self):
		""" Used to set a new pen color """
		newColor = QtGui.QColorDialog.getColor(self.scribbleArea.myPenColor)
		if newColor.isValid():
		    self.scribbleArea.setPenColor(newColor)

	def penWidth(self):
		""" Used to set a new pen width """
		newWidth, ok = QtGui.QInputDialog.getInteger(self, "Scribble", "Select pen width:", self.scribbleArea.myPenWidth, 1, 50, 1)
		if ok:
			self.scribbleArea.setPenWidth(newWidth)

	def editorView(self):
		""" Displays the post recording editing panel for the
		current session.
		"""
		# TODO render the editing timeline view
		pass

	def recordingView(self):
		""" Displays the recording studio panel for the 
		current slide deck
		"""
		# TODO render the slideshow/annotation view
		pass

	def file_open(self):
		""" Opens a pdf slideshow, creates the appropriate folder and converts/moves
		slides in jpg format to folder. Finally it initializes the slides in memory using all
		of the files in the newly created folder.
		"""
		self.slides = []
		
		filename = QtGui.QFileDialog.getOpenFileName(self, "Open File").split("/").pop()
		suffix = "-anot8"
		# Until functional resizing is done, this determines the size of the JPG images created
		slide_height = "650"
		file_no_ext = filename.split(".")[0]
		folder_name = file_no_ext + "-anot8-slides"
		convert_command = "convert -verbose -density 150 " + "-resize x" + slide_height + " " + filename + " -quality 100 -sharpen 0x1.0 "  + file_no_ext  + suffix + ".jpg"
		subprocess.call(convert_command, shell=True)
		subprocess.call("mkdir " + folder_name, shell=True)
		subprocess.call("move *" + suffix + "*.jpg " + folder_name, shell=True )
		self.slide_folder = folder_name
		self.current_slide_index = 0

		for file in sorted(os.listdir(self.slide_folder), key=lambda filename: int(filename.split("-anot8-")[1].split(".")[0])):
			self.slides.append(Slide("./" + self.slide_folder + "/" + file))
		
		self.scribbleArea.openImage(self.slides[self.current_slide_index].path)


	def save(self):
		""" Saves the current session in the appropriate folder in the form of a 
		serialized binary format. Can be easily reloaded to a Python array of slides
		"""
		if self.slides and self.slide_folder:
			file_path = "./" + self.slide_folder + "/config.anot8"
			try:
				with open(file_path, "wb") as f:
					pickle.dump(self.slides, f)
			except IOError:
				print("Error while saving file")
		else:
			print("nothing to save")

##################################### Draw Area Widget #######################################

class ScribbleArea(QtGui.QWidget):
    """ This widget represents the slideshow/recording interface of the application.
    Along with the editing view, they make up the two main interfaces of the application.
    """
    def __init__(self, parent=None):
        super(ScribbleArea, self).__init__(parent)
        self.setAttribute(QtCore.Qt.WA_StaticContents)
        self.scribbling = False
        self.myPenWidth = 3
        self.myPenColor = QtCore.Qt.blue
        imageSize = QtCore.QSize(600, 600)
        # This attribute is the image used to display the slides and draw on
        self.image = QtGui.QImage(imageSize, QtGui.QImage.Format_RGB32)
        self.lastPoint = QtCore.QPoint()

    def openImage(self, fileName):
        """ Fetches and displays and image, given its filename.
        Will do some resizing to fit the actual size of the image
        to the frame.
		"""
        loadedImage = QtGui.QImage()
        if not loadedImage.load(fileName):
            return False
        loadedImage = loadedImage.convertToFormat(QtGui.QImage.Format_RGB32)
        
        w = loadedImage.width()
        h = loadedImage.height()    
        self.mainWindow.resize(w, h)
        
        painter = QtGui.QPainter(self.image)
        painter.drawImage(QtCore.QPoint(0, 0), loadedImage)
        self.update()
        if self.mainWindow.recording == False and self.mainWindow.replaying == False and self.mainWindow.reviewing == True:
            for stroke in self.mainWindow.slides[self.mainWindow.current_slide_index].strokes:
                painter.setPen(QtGui.QPen(self.myPenColor, self.myPenWidth, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
                painter.drawLine(stroke.ax, stroke.ay, stroke.bx, stroke.by)
        self.update()
        return True

    def setPenColor(self, newColor):
    	""" Sets the pen color """
    	self.myPenColor = newColor

    def setPenWidth(self, newWidth):
    	""" Set the pen width """
    	self.myPenWidth = newWidth

    def clearImage(self):
    	""" Sets the image to a whole white canvas """
    	self.image.fill(QtGui.qRgb(255, 255, 255))
    	self.update()

    def mousePressEvent(self, event):
        """ Captures the mouse press event, used in the drawing process """
        if event.button() == QtCore.Qt.LeftButton:
            self.lastPoint = event.pos()
            self.scribbling = True

    def mouseMoveEvent(self, event):
        """ Captures the mouse move event, used in the drawing process """
        if (event.buttons() & QtCore.Qt.LeftButton) and self.scribbling:
            self.drawLineTo(event.pos())

    def mouseReleaseEvent(self, event):
        """ Captures the mouse release event, used in the drawing process """
        if event.button() == QtCore.Qt.LeftButton and self.scribbling:
            self.drawLineTo(event.pos())
            self.scribbling = False

    def paintEvent(self, event):
        """ Captures a paint event, used in the drawing process """
        painter = QtGui.QPainter(self)
        painter.drawImage(event.rect(), self.image)

    def resizeEvent(self, event):
        """ Captures a resize event, used in the drawing process """
        self.resizeImage(self.image, event.size())
        super(ScribbleArea, self).resizeEvent(event)

    def drawLineTo(self, endPoint):
        """ Draws a line from the current last point position, to 
        a given endpoint. Used in the drawing process """
        painter = QtGui.QPainter(self.image)
        painter.setPen(QtGui.QPen(self.myPenColor, self.myPenWidth,
            QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
        painter.drawLine(self.lastPoint, endPoint)
        if self.mainWindow.recording == True:
        	self.mainWindow.slides[self.mainWindow.current_slide_index].add_stroke(self.lastPoint.x(), self.lastPoint.y(), endPoint.x(), endPoint.y(), self.mainWindow.elapsed_on_current_slide)
        self.update()
        self.lastPoint = QtCore.QPoint(endPoint)

    def drawLineReplay(self, stroke):
    	""" Draws a line specified by a provided stroke object. Used 
    	in the replay of a session
    	"""
    	painter = QtGui.QPainter(self.image)
    	painter.setPen(QtGui.QPen(self.myPenColor, self.myPenWidth, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
    	painter.drawLine(stroke.ax, stroke.ay, stroke.bx, stroke.by)
    	self.update()

    def resizeImage(self, image, newSize):
        """ Used to resize the current image size to the approriate size
        of a provided new image
        """
        if image.size() == newSize:
            return
        newImage = QtGui.QImage(newSize, QtGui.QImage.Format_RGB32)
        newImage.fill(QtGui.qRgb(255, 255, 255))
        painter = QtGui.QPainter(newImage)
        painter.drawImage(QtCore.QPoint(0, 0), image)   
        self.image = newImage


############################### MAIN ########################################

def run():
	""" This is the main funciton of our app, here is the code
	that creates and renders the initial main window object """
	app = QtGui.QApplication(sys.argv)
	GUI = Window()
	sys.exit(app.exec_())


run()