# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2012 David Planella <david.planella@ubuntu.com>
# This program is free software: you can redistribute it and/or modify it 
# under the terms of the GNU General Public License version 3, as published 
# by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranties of 
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along 
# with this program.  If not, see <http://www.gnu.org/licenses/>.
### END LICENSE

import sys
try:
    import qrencode
except ImportError:
    print("You need to install the python-qrencode package")
    sys.exit(1)

from PIL import Image
from PIL import ImageOps
import cairo
import array


class QRCodeSize(object):
    SMALL = 120
    MEDIUM = 230
    LARGE = 350


class QRCodeOutput(object):
    PIL_IMAGE = 0
    CAIRO_SURFACE = 1


class QRCode(object):

    def __init__(self, output_size=QRCodeSize.MEDIUM):
        self.output_size = output_size
        self.qrcode_size = 0
        self.pixel_size = 1
        self.image = None
        self.cr_surface = None

    def encode(self, text, current_color_fg=None, previous_color_fg=None,
               current_color_bg=None, previous_color_bg=None, border=True,
               output_type=QRCodeOutput.PIL_IMAGE):
        '''Encodes the given text and returns a QR code in the given format'''

        if output_type == QRCodeOutput.PIL_IMAGE:
            qr_code = self._encode_to_pil(text, current_color_fg,
                previous_color_fg, current_color_bg, previous_color_bg,
                border)
        elif output_type == QRCodeOutput.CAIRO_SURFACE:
            qr_code = self._encode_to_cairo(text, current_color_fg,
                previous_color_fg, current_color_bg, previous_color_bg,
                border)

        return qr_code

    def _encode_to_cairo(self, text, current_color_fg=None,
                         previous_color_fg=None, current_color_bg=None,
                         previous_color_bg=None, border=True):
        '''Encodes the given text and returns a Cairo surface'''
        self._encode_to_pil(text, current_color_fg, previous_color_fg,
                            current_color_bg, previous_color_bg, border)

        # Convert the Python PIL image to a Cairo surface.
        # Notice the conversion to BGRA that Cairo expects
        # See http://cairographics.org/pythoncairopil/ and
        # http://mail.gnome.org/archives/gtkmm-list/2007-May/msg00111.html
        bytearr = array.array('B', self.image.tobytes("raw", "BGRA", 0, 1))
        height, width = self.image.size
        #print self.image.size

        self.cr_surface = cairo.ImageSurface.create_for_data(bytearr,
                                                     cairo.FORMAT_ARGB32,
                                                     width, height,
                                                     width * 4)

        return self.cr_surface

    def _encode_to_pil(self, text, current_color_fg=None,
                         previous_color_fg=None, current_color_bg=None,
                         previous_color_bg=None, border=True):
        '''Encodes the given text and returns a Python PIL Image,
        changing colors if necessary'''

        # Returns an 8-bit pixel, black and white image (mode L)
        version, self.qrcode_size, self.image = qrencode.encode(text)

        if (current_color_fg != previous_color_fg) or \
           (current_color_bg != previous_color_bg):
            self.image = ImageOps.colorize(self.image,
                                           (current_color_fg[0],
                                           current_color_fg[1],
                                           current_color_fg[2]),
                                           (current_color_bg[0],
                                           current_color_bg[1],
                                           current_color_bg[2]))

        # Returns a 4x8-bit pixel, true colour with transparency mask image
        # (mode RGBA)
        self.image = self.image.convert('RGBA')
        
        self._resize()
        if border:
            self._add_border(current_color_bg)

        return self.image

    def _resize(self):
        self._set_pixel_size(self.qrcode_size)
        #print self.pixel_size, self.qrcode_size
        self.image = self.image.resize((self.pixel_size * self.qrcode_size,
                                        self.pixel_size * self.qrcode_size),
                                       Image.NEAREST)

    def _set_pixel_size(self, barcode_orig_size):
        pixel_size = 1

        #print pixel_size * barcode_orig_size, self.output_size
        while (pixel_size * barcode_orig_size < self.output_size):
            pixel_size += 1

        while ((pixel_size * barcode_orig_size > self.output_size) or \
                ((pixel_size * barcode_orig_size) % 2 != 0) or \
                (pixel_size * barcode_orig_size + 2 * pixel_size > \
                    self.output_size)):
            pixel_size -= 1

            # FIXME: it needs to automatically go up to the next size,
            # rather than hardcoding it as now
            if pixel_size == 0:
                self.output_size = QRCodeSize.MEDIUM

        self.pixel_size = pixel_size

    def _add_border(self, current_color_bg=None):
        '''Adds a border to the QR code'''
        if current_color_bg:
            fill = (int(current_color_bg[0]), int(current_color_bg[1]),
                    int(current_color_bg[2]), 255)
        else:
            fill = 'white'
        # Add a border
        border_size = int((self.output_size - self.image.size[0]) / 2)
        self.image = ImageOps.expand(self.image, border=border_size,
                                     fill=fill)
