#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
Image module for Inkycal Project
Copyright by aceisace
"""

from inkycal.modules.template import inkycal_module
from inkycal.custom import *

from PIL import ImageOps
import requests
import numpy

filename = os.path.basename(__file__).split('.py')[0]
logger = logging.getLogger(filename)
logger.setLevel(level=logging.DEBUG)

class Inkyimage(inkycal_module):
  """Image class
  display an image from a given path or URL
  """
  _allowed_layout = ['fill', 'center', 'fit', 'auto']
  _allowed_rotation = [0, 90, 180, 270, 360, 'auto']
  _allowed_colours = ['bw', 'bwr', 'bwy']

  def __init__(self, section_size, section_config):
    """Initialize inkycal_rss module"""

    super().__init__(section_size, section_config)

    # Module specific parameters
    required = ['path']
    for param in required:
      if not param in section_config:
        raise Exception('config is missing {}'.format(param))

    # module name
    self.name = self.__class__.__name__

    # module specific parameters
    self.image_path = self.config['path']

    self.rotation = 0 #0 #90 # 180 # 270 # auto
    self.layout = 'fill' # centre # fit # auto
    self.colours = 'bw' #grab from settings file?

    # give an OK message
    print('{0} loaded'.format(self.name))

  def _validate(self):
    """Validate module-specific parameters"""

    # Validate image_path
    if not isinstance(self.image_path, str):
      print(
        'image_path has to be a string: "URL1" or "/home/pi/Desktop/im.png"')

    # Validate layout
    if not isinstance(self.layout, str) or (
      self.layout not in self._allowed_layout):
      print('layout has to be one of the following:', self._allowed_layout)

    # Validate rotation angle
    if self.rotation not in self._allowed_rotation:
      print('rotation has to be one of the following:', self._allowed_rotation)

    # Validate colours
    if not isinstance(self.colours, str) or (
      self.colours not in self._allowed_colours):
      print('colour has to be one of the following:', self._allowed_colours)

  def generate_image(self):
    """Generate image for this module"""

    # Define new image size with respect to padding
    im_width = self.width
    im_height = self.height
    im_size = im_width, im_height
    logger.info('image size: {} x {} px'.format(im_width, im_height))

    # Try to open the image if it exists and is an image file
    try:
      if self.image_path.startswith('http'):
        logger.debug('identified url')
        self.image = Image.open(requests.get(self.image_path, stream=True).raw)
      else:
        logger.info('identified local path')
        self.image = Image.open(self.image_path)
    except FileNotFoundError:
      raise ('Your file could not be found. Please check the filepath')
    except OSError:
      raise ('Please check if the path points to an image file.')

    logger.debug(('image-width:', self.image.width))
    logger.debug(('image-height:', self.image.height))

    # Create an image for black pixels and one for coloured pixels
    im_black = Image.new('RGB', size = im_size, color = 'white')
    im_colour = Image.new('RGB', size = im_size, color = 'white')

    # do the required operations
    self._remove_alpha()
    self._to_layout()
    black, colour = self._map_colours()

    # paste the imaeges on the canvas
    im_black.paste(black, (self.x, self.y))
    if colour != None:
      im_colour.paste(colour, (self.x, self.y))

    # Save image of black and colour channel in image-folder
    im_black.save(images+self.name+'.png', 'PNG')
    if colour != None:
      im_colour.save(images+self.name+'_colour.png', 'PNG')

  def _rotate(self, angle=None):
    """Rotate the image to a given angle
    angle must be one of :[0, 90, 180, 270, 360, 'auto']
    """
    im = self.image
    if angle == None:
      angle = self.rotation

    # Check if angle is supported
    if angle not in self._allowed_rotation:
      print('invalid angle provided, setting to fallback: 0 deg')
      angle = 0

    # Autoflip the image if angle == 'auto'
    if angle == 'auto':
      if (im.width > self.height) and (im.width < self.height):
        print('display vertical, image horizontal -> flipping image')
        image = im.rotate(90, expand=True)
      if (im.width < self.height) and (im.width > self.height):
        print('display horizontal, image vertical -> flipping image')
        image = im.rotate(90, expand=True)
    # if not auto, flip to specified angle
    else:
      image = im.rotate(angle, expand = True)
    self.image = image

  def _fit_width(self, width=None):
    """Resize an image to desired width"""
    im = self.image
    if width == None: width = self.width

    logger.debug(('resizing width from', im.width, 'to'))
    wpercent = (width/float(im.width))
    hsize = int((float(im.height)*float(wpercent)))
    image = im.resize((width, hsize), Image.ANTIALIAS)
    logger.debug(image.width)
    self.image = image

  def _fit_height(self, height=None):
    """Resize an image to desired height"""
    im = self.image
    if height == None: height = self.height

    logger.debug(('resizing height from', im.height, 'to'))
    hpercent = (height / float(im.height))
    wsize = int(float(im.width) * float(hpercent))
    image = im.resize((wsize, height), Image.ANTIALIAS)
    logger.debug(image.height)
    self.image = image

  def _to_layout(self, mode=None):
    """Adjust the image to suit the layout
    mode can be center, fit or fill"""

    im = self.image
    if mode == None: mode = self.layout

    if mode not in self._allowed_layout:
      print('{} is not supported. Should be one of {}'.format(
        mode, self._allowed_layout))
      print('setting layout to fallback: centre')
      mode = 'center'

    # If mode is center, just center the image
    if mode == 'center':
      pass

    # if mode is fit, adjust height of the image while keeping ascept-ratio
    if mode == 'fit':
      self._fit_height()

    # if mode is fill, enlargen or shrink the image to fit width
    if mode == 'fill':
      self._fit_width()

    # in auto mode, flip image automatically and fit both height and width
    if mode == 'auto':

      # Check if width is bigger than height and rotate by 90 deg if true
      if im.width > im.height:
        self._rotate(90)

      # fit both height and width
      self._fit_height()
      self._fit_width()

    if self.image.width > self.width:
      x = int( (self.image.width - self.width) / 2)
    else:
      x = int( (self.width - self.image.width) / 2)

    if self.image.height > self.height:
      y = int( (self.image.height - self.height) / 2)
    else:
      y = int( (self.height - self.image.height) / 2)

    self.x, self.y = x, y

  def _remove_alpha(self):
    im = self.image

    if len(im.getbands()) == 4:
      logger.debug('removing transparency')
      bg = Image.new('RGBA', (im.width, im.height), 'white')
      im = Image.alpha_composite(bg, im)
    self.image.paste(im, (0,0))

  def _map_colours(self, colours = None):
    """Map image colours to display-supported colours """
    im = self.image.convert('RGB')
    if colours == None: colours = self.colours

    if colours not in self._allowed_colours:
      print('invalid colour: "{}", has to be one of: {}'.format(
        colours, self._allowed_colours))
      print('setting to fallback: bw')
      colours = 'bw'

    if colours == 'bw':

      # For black-white images, use monochrome dithering
      im_black = im.convert('1', dither=True)
      im_colour = None

    elif colours == 'bwr':
      # For black-white-red images, create corresponding palette
      pal = [255,255,255, 0,0,0, 255,0,0, 255,255,255]

    elif colours == 'bwy':
      # For black-white-yellow images, create corresponding palette"""
      pal = [255,255,255, 0,0,0, 255,255,0, 255,255,255]

    # Map each pixel of the opened image to the Palette
    if colours == 'bwr' or colours == 'bwy':
      palette_im = Image.new('P', (3,1))
      palette_im.putpalette(pal * 64)
      quantized_im = im.quantize(palette=palette_im)
      quantized_im.convert('RGB')

      # Create buffer for coloured pixels
      buffer1 = numpy.array(quantized_im.convert('RGB'))
      r1,g1,b1 = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2]

      # Create buffer for black pixels
      buffer2 = numpy.array(quantized_im.convert('RGB'))
      r2,g2,b2 = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2]

      if colours == 'bwr':
        # Create image for only red pixels
        buffer2[numpy.logical_and(r2 ==  0, b2 == 0)] = [255,255,255] # black->white
        buffer2[numpy.logical_and(r2 ==  255, b2 == 0)] = [0,0,0] #red->black
        im_colour = Image.fromarray(buffer2)

        # Create image for only black pixels
        buffer1[numpy.logical_and(r1 ==  255, b1 == 0)] = [255,255,255]
        im_black = Image.fromarray(buffer1)

      if colours == 'bwy':
        # Create image for only yellow pixels
        buffer2[numpy.logical_and(r2 ==  0, b2 == 0)] = [255,255,255] # black->white
        buffer2[numpy.logical_and(g2 == 255, b2 == 0)] = [0,0,0] #yellow -> black
        im_colour = Image.fromarray(buffer2)

        # Create image for only black pixels
        buffer1[numpy.logical_and(g1 == 255, b1 == 0)] = [255,255,255]
        im_black = Image.fromarray(buffer1)

    return im_black, im_colour

  @staticmethod
  def save(image):
    im = self.image
    im.save('/home/pi/Desktop/test.png', 'PNG')

  @staticmethod
  def _show(image):
    """Preview the image on gpicview (only works on Rapsbian with Desktop)"""
    path = '/home/pi/Desktop/'
    image.save(path+'temp.png')
    os.system("gpicview "+path+'temp.png')
    os.system('rm '+path+'temp.png')

if __name__ == '__main__':
  print('running {0} in standalone/debug mode'.format(filename))

##  a = Inkyimage((480,800), {'path': "https://raw.githubusercontent.com/aceisace/Inky-Calendar/dev_ver2_0/Gallery/logo.png"})
##  a.generate_image()