import os
import time
import smtplib
import urllib
import urllib.request
from urllib.parse import urlparse
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

from PyQt4.QtCore import Qt, QRectF, QByteArray, QIODevice, QBuffer, QSizeF
from PyQt4.QtGui import (QGraphicsWidget, QHeaderView, QItemSelectionModel,
                         QGraphicsPixmapItem,  QPainter, QDialog, QPalette, 
                         QGraphicsLayoutItem, QGraphicsView, QGraphicsScene, 
                         QItemSelection, QPixmap, QGraphicsGridLayout,
                         QGraphicsSimpleTextItem, QBrush, QFont, QTableView)

from Orange.data import Table
from Orange.widgets import gui
from Orange.widgets.settings import Setting
from Orange.widgets.utils.itemmodels import PyListModel, PyTableModel
from Orange.widgets.widget import Msg, OWWidget


_IMAGE_UNI = "[encoded data, 21528 characters]"

_IMAGE_ORANGE = "[encoded data, 22280 characters]"
DIR_SHARE = "share/"
DIR_SEND_LATER = "send_later/"
FILE_SEND_LATER = "send_later.txt"


class MailSettingDialog(QDialog, gui.OWComponent):
    from_email_address = ""
    smtp_server_name = ""

    def __init__(self, parent):
        QDialog.__init__(self, parent)
        gui.OWComponent.__init__(self, parent)
        self.setWindowTitle("SMTP server settings")
        self.setFixedSize(340, 110)

        box = gui.vBox(self)
        box.layout().setContentsMargins(10, 10, 0, 0)
        self.email_edit = gui.lineEdit(
            box, self, "from_email_address", "Email address: ",
            orientation=Qt.Horizontal, controlWidth=220)
        gui.separator(box, height=2)
        self.smtp_edit = gui.lineEdit(
            box, self, "smtp_server_name", "SMTP server: ",
            orientation=Qt.Horizontal, controlWidth=220)
        gui.separator(box, height=2)

        bb = gui.hBox(box)
        self.cancel_button = gui.button(bb, self, "Cancel", self._cancel_button)
        gui.separator(bb, 10)
        self.send_button = gui.button(bb, self, "Save", self._save_button,
                                      default=True)

    def _save_button(self):
        self.accept()

    def _cancel_button(self):
        self.reject()


class SendDialog(QDialog, gui.OWComponent):
    email_address = ""
    fb_share = False

    def __init__(self, parent):
        QDialog.__init__(self, parent)
        gui.OWComponent.__init__(self, parent)
        self.setWindowTitle("Send")
        self.setFixedSize(260, 130)

        box = gui.vBox(self)
        box.layout().setContentsMargins(10, 0, 0, 0)
        self.email_edit = gui.lineEdit(
            box, self, "email_address", box="Email address",
            orientation=Qt.Horizontal, controlWidth=220,
            callback=self._email_changed, callbackOnType=True)
        gui.separator(box)
        self.fb_check = gui.checkBox(box, self, "fb_share", "Share on Facebook")
        gui.separator(box)

        bb = gui.hBox(box)
        self.cancel_button = gui.button(bb, self, "Cancel", self._cancel_button)
        gui.separator(bb, 10)
        self.send_button = gui.button(bb, self, "Send", self._send_button,
                                      default=True, enabled=False)

    def _email_changed(self):
        self.send_button.setEnabled(len(self.email_address) and
                                    "@" in self.email_address)

    def _send_button(self):
        self.accept()

    def _cancel_button(self):
        self.reject()


class GraphicsLayoutItem(QGraphicsLayoutItem):
    def __init__(self, item):
        super().__init__()
        self.item = item
        self.updateGeometry()

    def setGeometry(self, rect):
        QGraphicsLayoutItem.setGeometry(self, rect)
        self.item.setPos(rect.topLeft())

    def sizeHint(self, which, constraint=QSizeF()):
        return self.item.boundingRect().size()


class MainGraphicsWidget(QGraphicsWidget):
    def __init__(self):
        super().__init__()
        self._layout = QGraphicsGridLayout()
        self._layout.setHorizontalSpacing(40)
        self._layout.setVerticalSpacing(40)
        self._layout.setColumnAlignment(0, Qt.AlignLeft)
        self._layout.setColumnAlignment(1, Qt.AlignRight)
        self.setLayout(self._layout)

    def add_items(self, items):
        graphic_items = []
        for item, (row, col) in zip(items, ((0, 0), (1, 0), (0, 1), (1, 1))):
            item.setParentItem(self)
            gitem = GraphicsLayoutItem(item)
            self._layout.addItem(gitem, row, col)
            graphic_items.append(gitem)

        self._layout.activate()
        self.updateGeometry()


class TitleGraphicsWidget(QGraphicsWidget):
    def __init__(self, width):
        super().__init__()
        self._layout = QGraphicsGridLayout()
        self._layout.setColumnFixedWidth(0, width)
        self._layout.setColumnAlignment(0, Qt.AlignCenter)
        self.setLayout(self._layout)

    def add_item(self, item):
        item.setParentItem(self)
        gitem = GraphicsLayoutItem(item)
        self._layout.addItem(gitem, 0, 0)
        self._layout.activate()
        self.updateGeometry()


class OWLookalike(OWWidget):
    name = "Lookalike"
    description = "Show reference and selected neighbor."
    icon = "icons/Lookalike.svg"

    inputs = [("Neighbors", Table, "set_neighbors"),
              ("Reference", Table, "set_reference")]

    neighbor_index = 0
    want_main_area = True

    from_email_address = Setting("")
    smtp_server_name = Setting("")

    class Warning(OWWidget.Warning):
        missing_smtp_settings = Msg("Missing Email address or"
                                    " SMTP server name.")

    class Error(OWWidget.Error):
        no_images_neighbors = Msg("No images found in the neighbors table."
                                  " Make sure the attribute is tagged "
                                  "with 'type=image'.")
        no_images_reference = Msg("No images found in the reference table."
                                  " Make sure the attribute is tagged "
                                  "with 'type=image'.")
        smtp_error = Msg("Error during sending message. Press"
                         " 'Send pending' when connected.")

    def __init__(self):
        super().__init__()
        self.neighbors = None
        self.reference = None
        self.neighbors_img_attr = None
        self.reference_img_attr = None
        self.uni_logo_item = None
        self.orange_logo_item = None

        self.setMinimumSize(1050, 630)
        box = gui.vBox(self.controlArea, "Neighbors")
        self.neighbors_model = PyListModel()
        self.neighbors_model = PyTableModel()

        self.neighbors_view = QTableView()
        self.neighbors_view.setShowGrid(False)
        self.neighbors_view.setSelectionBehavior(QTableView.SelectRows)
        self.neighbors_view.setSelectionMode(QTableView.SingleSelection)
        self.neighbors_view.setWordWrap(False)
        self.neighbors_view.verticalHeader().setResizeMode(QHeaderView.Fixed)
        self.neighbors_view.verticalHeader().setDefaultSectionSize(18)
        self.neighbors_view.verticalHeader().setVisible(False)
        self.neighbors_view.horizontalHeader().setVisible(False)
        self.neighbors_view.setModel(self.neighbors_model)
        self.neighbors_view.selectionModel().selectionChanged.connect(
            self._neighbor_changed)
        self.neighbors_view.setMaximumWidth(207)
        box.layout().addWidget(self.neighbors_view)

        box = gui.vBox(self.controlArea, True)
        self.share_button = gui.button(box, self, "&Share", self.share)
        gui.separator(box, height=1)
        self.smtp_button = gui.button(box, self, "SMTP server", self.set_smtp)
        gui.separator(box, height=1)
        self.resend_button = gui.button(box, self, "Send pending", self.resend)

        self.scene = QGraphicsScene(self)
        self.image_view = QGraphicsView(self.scene, self)
        self.mainArea.layout().addWidget(self.image_view)

        self.send_dialog = SendDialog(self)
        self.smtp_dialog = MailSettingDialog(self)
        self._initialize()

    def _initialize(self):
        self.smtp_dialog.email_edit.setText(self.from_email_address)
        self.smtp_dialog.smtp_edit.setText(self.smtp_server_name)
        self.resend_button.setEnabled(
            os.path.exists(os.path.join(DIR_SEND_LATER, FILE_SEND_LATER)))
        if self.from_email_address == "" or self.smtp_server_name == "":
            self.Warning.missing_smtp_settings()
            self.share_button.setEnabled(False)
            self.resend_button.setEnabled(False)

    def set_neighbors(self, neighbors):
        self.clear_neighbors()
        self.neighbors = neighbors
        if self.neighbors is None:
            return

        self.neighbors_img_attr = self._get_img_attribute(self.neighbors.domain)
        if not self.neighbors_img_attr or len(self.neighbors) < 1:
            self.Error.no_images_neighbors()
            return

        def get_name(path):
            name = urlparse(path).path
            return os.path.splitext(os.path.basename(name))[0].replace("_", " ")

        model = [[get_name(inst[self.neighbors_img_attr].value),
                  inst["similarity"].value if "similarity" in
                                              self.neighbors.domain else ""]
                 for inst in self.neighbors]
        self.neighbors_model.wrap(model)
        selection = QItemSelection(self.neighbors_model.index(0, 0),
                                   self.neighbors_model.index(0, 1))
        self.neighbors_view.selectionModel().select(
            selection, QItemSelectionModel.ClearAndSelect)
        self.neighbors_view.setColumnWidth(0, 160)
        self.neighbors_view.setColumnWidth(1, 45)
        self.apply()

    def set_reference(self, reference):
        self.clear_reference()
        self.reference = reference
        if self.reference is None:
            return

        self.reference_img_attr = self._get_img_attribute(self.reference.domain)
        if not self.reference_img_attr or len(self.reference) < 1:
            self.Error.no_images_reference()
            return

        self.apply()

    def _neighbor_changed(self):
        self.scene.clear()
        rows = self.neighbors_view.selectionModel().selectedRows()
        if rows:
            self.neighbor_index = rows[0].row()
            self.apply()

    def clear_neighbors(self):
        self.neighbors_model[:] = []
        self.scene.clear()
        self.Error.no_images_neighbors.clear()
        self.neighbors_img_attr = None

    def clear_reference(self):
        self.scene.clear()
        self.Error.no_images_reference.clear()
        self.reference_img_attr = None

    def apply(self):
        if not self.neighbors_img_attr or not self.reference_img_attr or \
                not len(self.reference) or \
                        len(self.neighbors) <= self.neighbor_index:
            return

        reference_path = os.path.join(
            self.reference[0][self.reference_img_attr].variable.attributes.get(
                "origin", ""),
            self.reference[0][self.reference_img_attr].value)
        reference_image = self._get_image(reference_path)
        reference_image = reference_image.scaledToHeight(350)

        sel_neigbour = self.neighbors[self.neighbor_index]
        neighbors_path = os.path.join(
            sel_neigbour[self.neighbors_img_attr].variable.attributes.get("origin", ""),
            sel_neigbour[self.neighbors_img_attr].value)
        neighbors_image = self._get_image(neighbors_path)
        neighbors_image = neighbors_image.scaledToHeight(350)

        img_refr = QGraphicsPixmapItem(reference_image)
        img_negh = QGraphicsPixmapItem(neighbors_image)

        uni_ba = QByteArray.fromBase64(_IMAGE_UNI.encode("UTF-8"))
        uni_image = QPixmap()
        uni_image.loadFromData(uni_ba)
        self.uni_logo_item = QGraphicsPixmapItem(uni_image)
        orange_ba = QByteArray.fromBase64(_IMAGE_ORANGE.encode("UTF-8"))
        orange_image = QPixmap()
        orange_image.loadFromData(orange_ba)
        self.orange_logo_item = QGraphicsPixmapItem(orange_image)

        widget = MainGraphicsWidget()
        widget.add_items([img_refr, self.uni_logo_item,
                          img_negh, self.orange_logo_item])
        widget.setPos(0, 60)
        self.scene.addItem(widget)

        title = QGraphicsSimpleTextItem("I am {:.1f}% {}".format(
            self.neighbors_model[self.neighbor_index][1],
            self.neighbors_model[self.neighbor_index][0]))
        title.setFont(QFont("Garamond", 25))

        title_widget = TitleGraphicsWidget(neighbors_image.width() * 2 + 40)
        title_widget.add_item(title)
        self.scene.addItem(title_widget)

        self.uni_logo_item.hide()
        self.orange_logo_item.hide()

    def share(self):
        if not self.send_dialog.exec_():
            return
        image = self._get_image_from_scene()
        name = str(time.time()).replace(".", "")
        self.send_image(image, self.send_dialog.email_address, name)
        if self.send_dialog.fb_share:
            self.store_image(image, name, DIR_SHARE)

    def set_smtp(self):
        if not self.smtp_dialog.exec_():
            return
        self.from_email_address = self.smtp_dialog.from_email_address
        self.smtp_server_name = self.smtp_dialog.smtp_server_name
        self.share_button.setEnabled(True)
        self.resend_button.setEnabled(
            os.path.exists(os.path.join(DIR_SEND_LATER, FILE_SEND_LATER)))
        self.Warning.missing_smtp_settings.clear()
        if self.from_email_address == "" or self.smtp_server_name == "":
            self.Warning.missing_smtp_settings()
            self.share_button.setEnabled(False)
            self.resend_button.setEnabled(False)

    def _get_image_from_scene(self):
        if self.uni_logo_item is not None and self.orange_logo_item is not None:
            self.uni_logo_item.show()
            self.orange_logo_item.show()
        rect = self.scene.sceneRect()
        rect = rect.adjusted(-15, -15, 15, 15)
        image = QPixmap(int(rect.size().width()), int(rect.size().height()))
        painter = QPainter()
        painter.begin(image)
        painter.setRenderHint(QPainter.Antialiasing)
        brush = self.scene.backgroundBrush()
        if brush.style() == Qt.NoBrush:
            brush = QBrush(self.scene.palette().color(QPalette.Base))
        painter.fillRect(image.rect(), brush)
        target = QRectF(0, 0, rect.width(), rect.height())
        self.scene.render(painter, target, rect)
        if self.uni_logo_item is not None and self.orange_logo_item is not None:
            self.uni_logo_item.hide()
            self.orange_logo_item.hide()
        painter.end()
        return image

    def send_image(self, image, email, name, from_scene=True):
        if from_scene:
            byte_array = QByteArray()
            buffer = QBuffer(byte_array)
            buffer.open(QIODevice.WriteOnly)
            image.save(buffer, "jpg")
            buffer.close()

            buffer.open(QIODevice.ReadOnly)
            mime_image = MIMEImage(buffer.readData(buffer.size()), "jpg")
            buffer.close()
        else:
            mime_image = MIMEImage(image)

        msg = MIMEMultipart()
        msg["Subject"] = "Orange Demo Project"
        msg["From"] = self.from_email_address
        msg["To"] = email
        mime_image.add_header("Content-Disposition", "attachment",
                              filename="lookalike.jpg")
        msg.attach(mime_image)
        self.Error.smtp_error.clear()
        try:
            mailserver = smtplib.SMTP(self.smtp_server_name)
            mailserver.send_message(msg)
            mailserver.quit()
        except Exception:
            self.send_image_later(image, email, name)
            self.Error.smtp_error()

    def send_image_later(self, image, email, name):
        if not isinstance(image, QPixmap):
            data = image
            image = QPixmap()
            image.loadFromData(data)
        self.store_image(image, name, DIR_SEND_LATER)
        with open(os.path.join(DIR_SEND_LATER, FILE_SEND_LATER), "a") as f:
            f.write("{}, {}.jpg\n".format(email, name))
        self.resend_button.setEnabled(self.from_email_address != "" and
                                      self.smtp_server_name != "")

    def resend(self):
        with open(os.path.join(DIR_SEND_LATER, FILE_SEND_LATER), "r") as f:
            lines = f.readlines()
        with open(os.path.join(DIR_SEND_LATER, FILE_SEND_LATER), "w") as f:
            f.write("")
        for line in lines:
            email, name = line.split(",")[0], line.split(",")[1].strip()
            image = self._get_raw_image(os.path.join(DIR_SEND_LATER, name))
            self.send_image(image, email, name, from_scene=False)
            os.remove(os.path.join(DIR_SEND_LATER, name))

    @staticmethod
    def store_image(image, name, file_path):
        try:
            os.stat(file_path)
        except FileNotFoundError:
            os.makedirs(file_path)
        image.save(os.path.join(file_path, "{}.jpg".format(name)), "jpg")

    @staticmethod
    def _get_img_attribute(domain):
        return next((attr for attr in domain.metas if
                    ("type", "image") in attr.attributes.items()), None)

    @staticmethod
    def _get_image(path):
        try:
            data = urllib.request.urlopen(path).read()
            image = QPixmap()
            image.loadFromData(data)
        except (ValueError, urllib.error.URLError):
            image = QPixmap(path)
        return image

    @staticmethod
    def _get_raw_image(path):
        with open(path, "rb") as fp:
            return fp.read()


if __name__ == "__main__":
    from PyQt4.QtGui import QApplication

    a = QApplication([])
    ow = OWLookalike()
    ow.show()
    ow.set_neighbors(Table("zoo-with-images"))
    ow.set_reference(Table("zoo-with-images")[1::2])
    ow.raise_()
    a.exec_()
    ow.saveSettings()

