#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy
from PIL import Image
from itertools import groupby
from E3ImageEditor import E3ImageEditor

class E3PostProcessor(object):

    # Versione del Post Processor
    E3ppVersion = "0.0.1"

    # Pixels per mm
    density = 3.8 # 96dpi
    # density = 2.8 # 72dpi

    # Travel speed (max 70)
    travelSpeed = 500

    # Engrave speed
    engraveSpeed = 7

    # Max engrave power
    maxEngravePower = 255

    # Min engrave power
    minEngravePower = 200

    # La potenza a cui settare il laser durante i travel. Se diversa da 0 fa
    # in modo che il laser resti acceso pur non tracciando. Evitare continue
    # accensioni e spegnimeni allunga la vita del laser. Questo valore deve
    # essere minore di minEngravePower.
    keepAlivePower = 100

    # Image in array format
    imgArray = None

    # showPreview
    showPreview = False

    # Array immagine di preview
    imgPreviewArray = None

    # Soglia del bianco; tutti i pixels con valore al di sopra di whiteValue
    # non verranno laserati.
    whiteValue = 200

    # Gradiente di raggruppamento dei pixel di intensità simile.
    gradient = 70

    # Gcode lines
    GcodeLines = []

    def __init__(self, imgPointer):
        try:
            if not imgPointer:
                raise IOError


            # Converto l'immagine in un array di ?punti/linee?
            self.imgArray = numpy.array(imgPointer)
            self.GcodeLines = []

        except IOError as e:
            print "E3PostProcess init: Image not valid"
            pass
    # End __init__

    def processImageArray(self):
        # numero di linea
        curLine = 0

        # Destination px of last movement
        destPx = 0

        # Aggiungo una linea di commento con i dati del Post Processor
        self.GcodeLines.append("; EngraveCube Post Processor V " + str(self.E3ppVersion) + "by FabLab Napoli")

        # Aggiungo i prarametri di Post Progessing
        self.GcodeLines.append("; Px desnsity:" + str(self.density))

        # Aggingo il comando per l'homing ad inizio Gcode.
        self.GcodeLines.append("G28")

        # For each image line
        for imgArrayLine in self.imgArray:

            # Aggiungo una linea di commento gcode per separare le linee
            # dell'immagine
            self.GcodeLines.append("; Image line: " + str(curLine))

            # Processing odd line in reverse order.
            # if curLine % 2 != 0:
            #     reverse = True
            #     curPx = len(imgArrayLine)
            #     imgArrayLine = list(reversed(imgArrayLine))
            # else:
            #     curPx = 0
            #     reverse = False
            curPx = 0
            reverse = False

            self.GcodeLines.append("G28");

            # Moving cursor to next line
            # curPx = self.appendTravelLine(destPx, curLine, reverse, 255, "; GoTo next line.")

            # # Moving cursor to line start px
            curPx = self.appendTravelLine(curPx, curLine, reverse, 255, "; GoTo line start.")

            # Genero liste di pixels contigui con intensità di colore uguale.
            # for i,j in groupby(imgArrayLine):
            for i,j in groupby(imgArrayLine, lambda (x): 255 if x > self.whiteValue else x // self.gradient):



                listaPxSimili = list(j)
                numPx = len(listaPxSimili)
                pxValue = listaPxSimili[0]

                if self.showPreview:
                    numpy.concatenate((self.imgPreviewArray[curLine], listaPxSimili))

                destPx = curPx - numPx if reverse else curPx + numPx
                # destPx = curPx - numPx + 1 if reverse else curPx + numPx - 1

                # Filtro le liste di pixel sulla base del valore soglia del
                # bianco whiteValue. Tutto ciò che e al di sopra non verrà inciso.
                if pxValue < self.whiteValue:
                    # Pixels da incidere.

                    comment = ""

                    curPx = self.appendEngraveLine(destPx, curLine, reverse, pxValue,  comment)

                else:
                    # Pixels da saltare - Travel
                    comment = ""

                    curPx = self.appendTravelLine(destPx, curLine, reverse, pxValue, comment)

            # Passo alla riga successiva
            curLine = curLine + 1
        # End processImageArray

    def appendEngraveLine(self, destPx, curLine, reverse, pxValue, comm =""):

        curGcodeLine = "G0 X"+str(self.pxNumToMm(destPx)) + " F" + str(self.engraveSpeed) + " E" + str(self.pxValueToLaserPwr(pxValue)) + comm

        self.GcodeLines.append(curGcodeLine)
        return destPx
        # End appendEngraveLine

    def appendTravelLine(self, destPx, curLine, reverse, pxValue, comm =""):

        curGcodeLine = "G0 X"+str(self.pxNumToMm(destPx))+" Y"+ str(self.pxNumToMm(curLine)) + " F" + str(self.travelSpeed) + " E" + str(self.keepAlivePower) + comm

        prevGcodeLine = self.getPrevLine()
        prevGcodeLineAr = prevGcodeLine.split()
        curGcodeLineAr = curGcodeLine.split()

        # self.GcodeLines.append("; Prev:" + prevGcodeLine + " Cur:" + curGcodeLine)

        if len(prevGcodeLineAr) <= 1:
            self.GcodeLines.append(curGcodeLine)
            return destPx

        if prevGcodeLineAr[1] == curGcodeLine[1] :
            self.GcodeLines.append("; Line Skipped")
            return destPx

        self.GcodeLines.append(curGcodeLine)
        return destPx
    # End appendTravelLine

    def getPrevLine(self,):
        # Calcolo il numero di line gcode già inserite.
        numGcodeLines = len(self.GcodeLines)
        prevLineNum = numGcodeLines - 1

        lineFound = False
        while not lineFound and prevLineNum >=0:
            if self.GcodeLines[prevLineNum][0] == ";":
                prevLineNum = prevLineNum -1
            elif len(self.GcodeLines[prevLineNum].split()) == 1:
                prevLineNum = prevLineNum -1
            else:
                lineFound = True
        # print "getPrevline: " + "" if numGcodeLines < 0 else self.GcodeLines[prevLineNum]
        # Sto elaborando la prima linea
        return "" if numGcodeLines < 0 else self.GcodeLines[prevLineNum]
        # End getPrevLine

    def printGcode(self):
        for line in self.GcodeLines:
            print line
        # End appendGcode

    def pxValueToLaserPwr(self, pxValue):
        return (pxValue - self.whiteValue) * (self.maxEngravePower - self.minEngravePower) / (0 - self.whiteValue) + self.minEngravePower;
        # return pxValue
        # End appendGcode

    def pxNumToMm(self, pxNum):
        return round(pxNum/self.density, 3)
        # return pxNum
        # End appendGcode

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser()

    # Defining parametes options
    parser.add_argument('file', help='Image file name')

    parser.add_argument('-sm', '--sizemm',  type=int, default=25,
        help='Max output size in mm.')

    #Check the arguments
    args = parser.parse_args()

    #Make sure at least one option is chosen
    if not (args.file):
        #Print help
        parser.print_help()
        #Exit out with no action message
        parser.error('No action requested')
    else:

        # Try to open file
        imgEdit = E3ImageEditor(args.file)

        # Resizing image
        imgEdit.ResizeKeepingAspectRatioMM(args.sizemm)

        # Converting the image to B/W 8bit
        imgEdit.ConvertTo8BitBW()

        # Creating Post Processor Object
        pp = E3PostProcessor(imgEdit.getImage())

        # Generating GCode
        pp.processImageArray()

        # Printing Gcode
        pp.printGcode()
