'''
Created on 24 Jan 2017
@author: muth    
'''
import os
import RPi.GPIO as GPIO
import threading
import time
from Adafruit_Thermal import *
from time import sleep
from PIL import Image
from PIL import ImageOps
from PIL import ImageEnhance
from PIL import ImageDraw
from PIL import ImageFont
from smemlcd import SMemLCD
from picamera import PiCamera
from io import BytesIO
from subprocess import check_output

# Constants
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 240
SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)
PRINTER_WIDTH = 640
PRINTER_HEIGHT = 384
PRINTER_SIZE = (PRINTER_WIDTH, PRINTER_HEIGHT)
FILE_WIDTH = PRINTER_WIDTH*2
FILE_HEIGHT = PRINTER_HEIGHT*2
FILE_SIZE = (FILE_WIDTH, FILE_HEIGHT)

SHOT_PIN = 16
PRINT_PIN = 15
NEXT_PIN = 13
PREV_PIN = 11
HALT_PIN = 31
FRAME_MODE = 1
SCAN_MODE = 2
SCAN_MODE_FIX = 3

class SlitScan(object):
    def __init__(self):
        self.image_scan = Image.new('L', PRINTER_SIZE, 0)
        self.x = 0
        self.mode = FRAME_MODE
        self.scanDone = False
        self.lastTime = time.time()
        self.ditherMode = Image.ORDERED
        
    def write(self, s):
        global lcd
        image = Image.frombuffer('L', PRINTER_SIZE, s, "raw", 'L', 0, 1)
        
        if self.mode == FRAME_MODE:
            image.thumbnail(SCREEN_SIZE, Image.NEAREST)
            image = ImageOps.invert(image)
            image = image.convert('1', dither=self.ditherMode)
            
        if self.mode == SCAN_MODE:
            image = image.crop((self.x, 0, self.x+1, PRINTER_HEIGHT))
            self.image_scan.paste(image,(self.x, 0))
            
            if self.x < PRINTER_WIDTH-1:
                self.x += 1
            else:
                self.scanDone = True
                
            image = ImageOps.invert(self.image_scan)
            image.thumbnail(SCREEN_SIZE, Image.NEAREST)
            image = image.convert('1')
            
        if self.mode == SCAN_MODE_FIX:
            image = image.crop((PRINTER_WIDTH/2, 0, (PRINTER_WIDTH/2)+1, PRINTER_HEIGHT))
            image_total = Image.new('L', (self.x+1, PRINTER_HEIGHT), 0)
            image_total.paste(self.image_scan, (0, 0))
            image_total.paste(image,(self.x, 0))
            self.image_scan = image_total.copy()
              
            if self.x < 5000:
                self.x += 1
            else:
                self.scanDone = True
                  
            image = ImageOps.invert(self.image_scan)
            if image.size[0] > PRINTER_SIZE[0]:
                image = image.crop((image.size[0]-1 - PRINTER_SIZE[0], 0, image.size[0]-1, PRINTER_HEIGHT))
            
            image.thumbnail(SCREEN_SIZE, Image.NEAREST)
            image_sized = Image.new('L', SCREEN_SIZE, 0)
            image_sized.paste(image,(0, 0))
            image = image_sized.convert('1')
        
        period = time.time() - self.lastTime
        if period > 0.05:
            self.lastTime = time.time()
            lcd.write(image.tobytes())

    def flush(self):
        print('Stop SlitScan') 

# Variables
currentFileNumber = -1
print check_output(['hostname', '-I'])

# GPIO setup
GPIO.setmode(GPIO.BOARD)
GPIO.setup(SHOT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(PRINT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(NEXT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(PREV_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(HALT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# add edge detection on a channel
GPIO.add_event_detect(SHOT_PIN, GPIO.FALLING, bouncetime=1000)  
GPIO.add_event_detect(PRINT_PIN, GPIO.FALLING, bouncetime=1000)  
GPIO.add_event_detect(NEXT_PIN, GPIO.FALLING, bouncetime=400)  
GPIO.add_event_detect(PREV_PIN, GPIO.FALLING, bouncetime=400)  
GPIO.add_event_detect(HALT_PIN, GPIO.FALLING, bouncetime=1000)  

# Create Sharp mempry LCD
lcd = SMemLCD('/dev/spidev0.0')

# get IP adress
hostIP = check_output(['hostname', '-I'])

# Create Printer
printer = Adafruit_Thermal("/dev/ttyAMA0", 115200, timeout=0, rtscts=True)

# Create camera and in-memory stream
stream = BytesIO()
camera = PiCamera()
camera.rotation = 180
camera.resolution = FILE_SIZE
camera.framerate = 18
camera.contrast = 50
camera.exposure_mode = 'night'
camera.start_preview()
sleep(1)

def haltSystem():
    print 'Halt...'
    os.system("sudo halt")
    
# GPIO.add_event_detect(HALT_PIN, GPIO.FALLING, callback = haltSystem, bouncetime = 2000)

def displayImageFileOnLCD(filename):
    print 'displays ', filename
    title = 'Review Mode'
    # resize/dither to screen resolution and send to LCD
    try:
        image = Image.open(filename)
    except IOError:
        print ("cannot identify image file", filename)
        image = Image.open('unidentified.jpg')
    im_width, im_height = image.size
    if im_width < im_height:
        image = image.rotate(90, expand=1)
    image.thumbnail(SCREEN_SIZE, Image.ANTIALIAS)
    image_sized = Image.new('RGB', SCREEN_SIZE, (0, 0, 0))
    image_sized.paste(image,((SCREEN_SIZE[0] - image.size[0]) / 2, (SCREEN_SIZE[1] - image.size[1]) / 2))
    # draw texts
    draw = ImageDraw.Draw(image_sized)
    font = ImageFont.truetype('arial.ttf', 18)
    draw.rectangle([(0, 0), (115, 22)], fill=(255,255,255), outline=(0,0,0))
    draw.text((2, 2), title, fill='black', font=font)
    draw.rectangle([(279, 217), (399, 239)], fill=(255,255,255), outline=(0,0,0))
    draw.text((290, 218), filename, fill='black', font=font)
    font = ImageFont.truetype('arial.ttf', 10)
    draw.rectangle([(300, 0), (399, 14)], fill=(255,255,255), outline=(0,0,0))
    draw.text((302, 2), hostIP, fill='black', font=font)
    # display on LCD
    image_sized = ImageOps.invert(image_sized)
    image_sized = image_sized.convert('1') # convert image to black and white
    lcd.write(image_sized.tobytes())
    
def printImageFile(filename):
    print 'prints ', filename
    # resize to printer resolution and send to printer
    try:
        image = Image.open(filename)
        im_width, im_height = image.size
        if im_width > im_height:
            image = image.rotate(90, expand=1)
            im_width, im_height = image.size
        ratio = (PRINTER_HEIGHT/float(im_width))
        height = int((float(im_height)*float(ratio)))
        image = image.resize((PRINTER_HEIGHT, height), Image.ANTIALIAS)
        
        printer.printImage(image, False)
        printer.justify('C')
        printer.setSize('S')
        printer.println("PolaPi-Zero")
        printer.feed(3)
    except IOError:
        print ("cannot identify image file", filename)
    
def saveImageToFile(image, filename):
    print 'saves image ', filename
    # save full image
    image.save(filename)

#Main loop
while True:
    slitScanProcess = SlitScan()
    camera.start_recording(slitScanProcess, format='yuv', resize=PRINTER_SIZE)
    # Buttons loop
    while True:
        sleep(0.1)
        # take a picture   
        if GPIO.event_detected(SHOT_PIN):
            # Increment file number    
            i = 1
            while os.path.exists("pz%05d.jpg" % i):
                i += 1
            currentFileNumber = i
            print("capture pz%05d.jpg" % currentFileNumber)
            
            if slitScanProcess.mode == FRAME_MODE:
                camera.capture("pz%05d.jpg" % currentFileNumber, use_video_port=True)
            
            if slitScanProcess.mode == SCAN_MODE_FIX:
                slitScanProcess.image_scan.save("pz%05d.jpg" % currentFileNumber)
                
            if slitScanProcess.mode == SCAN_MODE:
                slitScanProcess.image_scan.save("pz%05d.jpg" % currentFileNumber)
            
            camera.stop_recording()
            break
        # review mode
        if GPIO.event_detected(PRINT_PIN):
            hostIP = check_output(['hostname', '-I']) #refresh IP adress
            camera.stop_recording()
            break
        # start slit-scan mode
        if GPIO.event_detected(PREV_PIN):
            slitScanProcess.mode = SCAN_MODE
        # start slit-scan mode
        if GPIO.event_detected(NEXT_PIN):
            slitScanProcess.mode = SCAN_MODE_FIX
		# Change dithering mode
        if GPIO.event_detected(HALT_PIN):
            if slitScanProcess.ditherMode == Image.ORDERED:
                slitScanProcess.ditherMode = Image.FLOYDSTEINBERG
            elif slitScanProcess.ditherMode == Image.FLOYDSTEINBERG:
                slitScanProcess.ditherMode = Image.ORDERED
        # slit-scan mode done
        if slitScanProcess.scanDone:
            # Increment file number    
            i = 1
            while os.path.exists("pz%05d.jpg" % i):
                i += 1
            currentFileNumber = i
            print("capture pz%05d.jpg" % currentFileNumber)
            slitScanProcess.image_scan.save("pz%05d.jpg" % currentFileNumber)
            camera.stop_recording()
            break
    
    # Set current file number if not set yet
    if currentFileNumber == -1 :
        i = 0
        while True:
            if os.path.exists("pz%05d.jpg" % (i+1)):
                i += 1
            else :
                break
        currentFileNumber = i
    
    # Display current image
    displayImageFileOnLCD("pz%05d.jpg" % currentFileNumber)
    
    # Review Loop
    while True:
        sleep(0.25)
        if GPIO.event_detected(NEXT_PIN):
            # Increment current file name and display it
            if os.path.exists("pz%05d.jpg" % (currentFileNumber+1)):
                currentFileNumber += 1
            displayImageFileOnLCD("pz%05d.jpg" % currentFileNumber)
        if GPIO.event_detected(PREV_PIN):
            # Decrement current file name and display it
            if os.path.exists("pz%05d.jpg" % (currentFileNumber-1)):
                currentFileNumber -= 1
            displayImageFileOnLCD("pz%05d.jpg" % currentFileNumber)
        if GPIO.event_detected(PRINT_PIN):
            # Print current file
            printImageFile("pz%05d.jpg" % currentFileNumber)
        if GPIO.event_detected(HALT_PIN):
            # halt system
            haltSystem()
        if GPIO.event_detected(SHOT_PIN):
            # Exit review
            break
            
print("Main loop has exited")