# -*- coding: utf-8 -*-
#
# (c) Copyright 2001-2015 HP Development Company, L.P.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Author: Don Welch
#

# Std Lib
import re
import os
import time

# Local
from base.g import *
from base.codes import *
from base import utils
from prnt import cups
from base.sixext import PY3, to_unicode


from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
    # End
pat_html_remove = re.compile("(?is)<.*?>", re.I)

# databaseChanged signal values (for FABWindow)
FAB_NAME_ADD = 0  # s1 - new name
FAB_NAME_RENAME = 1 # s1 - old name, s2 - new name
FAB_NAME_REMOVE = 2 # s1 - removed name
FAB_NAME_DETAILS_CHANGED = 3 # s1 - name
FAB_GROUP_ADD = 4 # s1 - new group
FAB_GROUP_RENAME = 5 # s1 - old group, s2 - new group
FAB_GROUP_REMOVE = 6 # s1 - removed group
FAB_GROUP_MEMBERSHIP_CHANGED = 7 # s1 - group


def __translate(t):
    return QApplication.translate("ui_utils", t, None)


def beginWaitCursor():
    QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))


def endWaitCursor():
    QApplication.restoreOverrideCursor()


# TODO: Cache pixmaps
def load_pixmap(name, subdir=None, resize_to=None):
    name = ''.join([os.path.splitext(name)[0], '.png'])

    if subdir is None:
        image_dir = prop.image_dir
        ldir = os.path.join(os.getcwd(), 'data', 'images')
    else:
        image_dir = os.path.join(prop.image_dir, subdir)
        ldir = os.path.join(os.getcwd(), 'data', 'images', subdir)

    for d in [image_dir, ldir]:
        f = os.path.join(d, name)
        if os.path.exists(f):
            if resize_to is not None:
                img = QImage(f)
                x, y = resize_to
                return QPixmap.fromImage(img.scaled(x, y, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
            else:
                return QPixmap(f)

        for w in utils.walkFiles(image_dir, recurse=True, abs_paths=True, return_folders=False, pattern=name):
            if resize_to is not None:
                img = QImage(w)
                x, y = resize_to
                return QPixmap.fromImage(img.scaled(x, y, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
            else:
                return QPixmap(w)

    log.error("Pixmap '%s' not found!" % name)
    return QPixmap()

loadPixmap = load_pixmap


def getPynotifyIcon(name, subdir='32x32'):
    name = ''.join([os.path.splitext(name)[0], '.png'])
    return "file://" + os.path.join(prop.image_dir, subdir, name)


def value_str(data):
    if data is None:
        return ""
    try:
        if not PY3:
            try:
                data = data.toString()
            except AttributeError as e:
                return data
    except(ValueError, TypeError) as e:
        log.warn("value_str() Failed to convert data: %s" % e)
        
    return data


def value_int(data):
    i, ok = 0, False
    if data is None:
        return i, ok
    try:
        if PY3:
            i =  int(data)
            ok = True
        else:
            try:
                i, ok = data.toInt()
            except AttributeError as e:
                i = int(data)
                ok = True
    except (ValueError,TypeError) as e:
        log.warn("value_int() Failed to convert data[%s]:%s  "%(data,e))

    return i, ok


def value_bool( data ):
    b = False
    if data is None:
        return b
    try:
        if PY3:
            if type(data) == str and data.lower() in ['false', '0']:
                b = False
            elif data in [False, 0]:
                b = False
            else:
                b= True
        else:
            try:
                b = data.toBool()
            except AttributeError as e:
                if type(data) == str and data.lower() in ['false', '0']:
                    b = False
                elif data in [False, 0]:
                    b = False
                else:
                    b= True

    except (ValueError,TypeError) as e:
        log.warn("value_bool() Failed to convert data :%s"%e)

    return b



class UserSettings(QSettings):
    def __init__(self):
        if prop.user_dir is None:
            QSettings.__init__(self)
        else:
            QSettings.__init__(self, os.path.join(prop.user_dir,  'hplip.conf'),  QSettings.IniFormat)

        self.systray_visible = SYSTRAY_VISIBLE_SHOW_ALWAYS
        self.systray_messages = SYSTRAY_MESSAGES_SHOW_ALL
        self.last_used_device_uri = ''
        self.last_used_printer = ''
        self.version = ''
        self.date_time = ''
        self.auto_refresh = False
        self.auto_refresh_rate = 30
        self.auto_refresh_type = 1
        self.polling_interval = 5
        self.polling = True
        self.device_list = []
        self.working_dir = '.'
        self.voice_phone = ''
        self.email_address = ''
        self.upgrade_notify=True
        self.upgrade_last_update_time=0
        self.upgrade_pending_update_time=0
        self.latest_available_version=""
        self.loadDefaults()


    def __setup(self,  cmds):
        for c in cmds:
            basename = c.split()[0]
            path = utils.which(basename)
            if path:
                return ' '.join([os.path.join(path, basename), ' '.join(c.split()[1:])])

        return ''

    def loadDefaults(self):
        self.cmd_scan = self.__setup(['xsane -V %SANE_URI%', 'kooka', 'xscanimage'])
        self.cmd_fab = self.__setup(['hp-fab'])


    def load(self):
        log.debug("Loading user settings...")
        self.sync()

        self.beginGroup("settings")
        self.systray_visible = value_int(self.value("systray_visible"))[0]
        
        self.systray_messages = value_int(self.value("systray_messages"))[0]
 
        self.endGroup()

        self.beginGroup("last_used")
        self.last_used_device_uri = value_str(self.value("device_uri")) or self.last_used_device_uri
        self.last_used_printer = value_str(self.value("printer_name")) or self.last_used_printer
        self.working_dir = value_str(self.value("working_dir")) or self.working_dir
        self.endGroup()

        self.beginGroup("commands")
        self.cmd_scan = value_str(self.value("scan")) or self.cmd_scan
        self.endGroup()

        self.beginGroup("refresh")
        self.auto_refresh_rate = value_int(self.value("rate"))[0] or int(self.auto_refresh_rate)
        self.auto_refresh = value_bool(self.value("enable"))
        self.auto_refresh_type = value_int(self.value("type"))[0] or int(self.auto_refresh_type)
        self.endGroup()

        self.beginGroup("installation")
        self.version = value_str(self.value("version"))
        self.date_time = value_str(self.value("date_time"))
        self.endGroup()

        self.beginGroup("polling")
        self.polling = value_bool(self.value("enable"))
        self.polling_interval = value_int(self.value("interval"))[0] or int(self.polling_interval)
        self.polling_device_list = to_unicode(value_str(self.value("device_list"))).split(to_unicode(','))
        self.endGroup()

        self.beginGroup("fax")
        self.voice_phone = value_str(self.value("voice_phone"))
        self.email_address = to_unicode(value_str(self.value("email_address")))
        self.endGroup()
        
        self.beginGroup("upgrade")
        self.upgrade_notify= value_bool(self.value("notify_upgrade"))
        self.latest_available_version=value_str(self.value("latest_available_version"))
        
        self.upgrade_last_update_time = value_int(self.value("last_upgraded_time"))[0]
        
        self.upgrade_pending_update_time = value_int(self.value("pending_upgrade_time"))[0]
            
        self.endGroup()


    def save(self):
        log.debug("Saving user settings...")

        self.beginGroup("settings")
        self.setValue("systray_visible", self.systray_visible)
        self.setValue("systray_messages", self.systray_messages)
        self.endGroup()

        self.beginGroup("last_used")
        self.setValue("device_uri",  self.last_used_device_uri)
        self.setValue("printer_name", self.last_used_printer)
        self.setValue("working_dir", self.working_dir)
        self.endGroup()

        self.beginGroup("commands")
        self.setValue("scan",  self.cmd_scan)
        self.endGroup()

        self.beginGroup("refresh")
        self.setValue("rate", self.auto_refresh_rate)
        self.setValue("enable", self.auto_refresh)
        self.setValue("type", self.auto_refresh_type)
        self.endGroup()

        self.beginGroup("polling")
        self.setValue("enable", self.polling)
        self.setValue("interval", self.polling_interval)
        self.setValue("device_list", (to_unicode(',').join(self.polling_device_list)))
        self.endGroup()

        self.beginGroup("fax")
        self.setValue("voice_phone", self.voice_phone)
        self.setValue("email_address", self.email_address)
        self.endGroup()
        
        self.beginGroup("upgrade")
        self.setValue("notify_upgrade", self.upgrade_notify)
        if self.upgrade_last_update_time <1:
            self.upgrade_last_update_time = int(time.time())          # <---Need to verify code once
            
        self.setValue("last_upgraded_time", self.upgrade_last_update_time)
        self.setValue("pending_upgrade_time", self.upgrade_pending_update_time)
        self.endGroup()


        self.sync()


    def debug(self):
        log.debug("FAB command: %s" % self.cmd_fab)
        log.debug("Scan command: %s" % self.cmd_scan)
        log.debug("Auto refresh: %s" % self.auto_refresh)
        log.debug("Auto refresh rate: %s" % self.auto_refresh_rate)
        log.debug("Auto refresh type: %s" % self.auto_refresh_type)
        log.debug("Systray visible: %d" % self.systray_visible)
        log.debug("Systray messages: %d" % self.systray_messages)
        log.debug("Last used device URI: %s" % self.last_used_device_uri)
        log.debug("Last used printer: %s" % self.last_used_printer)
        log.debug("Working directory: %s" % self.working_dir)


DEFAULT_TITLE =  __translate("HP Device Manager")


def FailureUI(parent, error_text, title_text=None):
    log.error(pat_html_remove.sub(' ', to_unicode(error_text)))

    if title_text is None:
        if parent is not None:
            title_text = parent.windowTitle()
        else:
            title_text = DEFAULT_TITLE

    QMessageBox.critical(parent,
        title_text,
        error_text,
        QMessageBox.Ok|\
        QMessageBox.NoButton,
        QMessageBox.NoButton)

showFailureUi = FailureUI


def WarningUI(parent,  warn_text, title_text=None):
    log.warn(pat_html_remove.sub(' ', to_unicode(warn_text)))

    if title_text is None:
        if parent is not None:
            title_text = parent.windowTitle()
        else:
            title_text = DEFAULT_TITLE


    QMessageBox.warning(parent,
        title_text,
        warn_text,
        QMessageBox.Ok|\
        QMessageBox.NoButton,
        QMessageBox.NoButton)

showWarningUi = WarningUI


def SuccessUI(parent, text, title_text=None):
    log.info(pat_html_remove.sub(' ', to_unicode(text)))

    if title_text is None:
        if parent is not None:
            title_text = parent.windowTitle()
        else:
            title_text = DEFAULT_TITLE


    QMessageBox.information(parent,
        title_text,
        text,
        QMessageBox.Ok|\
        QMessageBox.NoButton,
        QMessageBox.NoButton)

showSuccessUi = SuccessUI


def CheckDeviceUI(parent, title_text=None):
    text = __translate("<b>Unable to communicate with device or device is in an error state.</b><p>Please check device setup and try again.</p>")
    return FailureUI(parent, text, title_text)

checkDeviceUi = CheckDeviceUI


class PrinterNameValidator(QValidator):
    def __init__(self, parent=None):
        QValidator.__init__(self, parent)

    def validate(self, input_data, pos):
        returnCode = QValidator.Invalid
        input_data = to_unicode(input_data)

        if not input_data:
            returnCode = QValidator.Acceptable
        elif input_data[pos-1] in cups.INVALID_PRINTER_NAME_CHARS:
            returnCode = QValidator.Invalid
        else:
            returnCode = QValidator.Acceptable

        # TODO: How to determine if unicode char is "printable" and acceptable
        # to CUPS?
        #elif input_data != utils.printable(input_data):
        #    return QValidator.Invalid, pos

        return returnCode, input_data, pos


class PhoneNumValidator(QValidator):
    def __init__(self, parent=None):
        QValidator.__init__(self, parent)

    def validate(self, input_data, pos):
        returnCode = QValidator.Invalid 
        input_data = to_unicode(input_data)

        if not input_data:
            returnCode =  QValidator.Acceptable
        elif input_data[pos-1] not in to_unicode('0123456789-(+).,#* '):
            returnCode = QValidator.Invalid
        else:
            returnCode = QValidator.Acceptable

        return returnCode, input_data, pos


class AddressBookNameValidator(QValidator):
    def __init__(self, db, parent=None):
        QValidator.__init__(self, parent)
        self.db = db

    def validate(self, input_data, pos):
        returnCode = QValidator.Invalid
        input_data = to_unicode(input_data)

        if not input_data:
            returnCode = QValidator.Acceptable
        elif input_data in self.db.get_all_names():
            returnCode = QValidator.Invalid
        elif input_data[pos-1] in to_unicode('''|\\/"'''): # | is the drag 'n drop separator
            returnCode = QValidator.Invalid
        else:
            returnCode = QValidator.Acceptable

        return returnCode, input_data, pos



MIME_TYPES_DESC = \
{
    "application/pdf" : (__translate("PDF Document"), '.pdf'),
    "application/postscript" : (__translate("Postscript Document"), '.ps'),
    "application/vnd.hp-HPGL" : (__translate("HP Graphics Language File"), '.hgl, .hpg, .plt, .prn'),
    "application/x-cshell" : (__translate("C Shell Script"), '.csh, .sh'),
    "application/x-csource" : (__translate("C Source Code"), '.c'),
    "text/cpp": (__translate("C/C++ Source Code"), '.c, .cpp, .cxx'),
    "application/x-perl" : (__translate("Perl Script"), '.pl'),
    "application/x-python" : (__translate("Python Program"), '.py'),
    "application/x-shell" : (__translate("Shell Script"), '.sh'),
    "application/x-sh" : (__translate("Shell Script"), '.sh'),
    "text/plain" : (__translate("Plain Text"), '.txt, .log'),
    "text/html" : (__translate("HTML Dcoument"), '.htm, .html'),
    "image/gif" : (__translate("GIF Image"), '.gif'),
    "image/png" : (__translate("PNG Image"), '.png'),
    "image/jpeg" : (__translate("JPEG Image"), '.jpg, .jpeg'),
    "image/tiff" : (__translate("TIFF Image"), '.tif, .tiff'),
    "image/x-bitmap" : (__translate("Bitmap (BMP) Image"), '.bmp'),
    "image/x-bmp" : (__translate("Bitmap (BMP) Image"), '.bmp'),
    "image/x-photocd" : (__translate("Photo CD Image"), '.pcd'),
    "image/x-portable-anymap" : (__translate("Portable Image (PNM)"), '.pnm'),
    "image/x-portable-bitmap" : (__translate("Portable B&W Image (PBM)"), '.pbm'),
    "image/x-portable-graymap" : (__translate("Portable Grayscale Image (PGM)"), '.pgm'),
    "image/x-portable-pixmap" : (__translate("Portable Color Image (PPM)"), '.ppm'),
    "image/x-sgi-rgb" : (__translate("SGI RGB"), '.rgb'),
    "image/x-xbitmap" : (__translate("X11 Bitmap (XBM)"), '.xbm'),
    "image/x-xpixmap" : (__translate("X11 Pixmap (XPM)"), '.xpm'),
    "image/x-sun-raster" : (__translate("Sun Raster Format"), '.ras'),
    "application/hplip-fax" : (__translate("HPLIP Fax File"), '.g3, .g4'),
}

# pixmaps for status list(s) (inkjet, laserjet)
status_icons = None

def getStatusListIcon(error_state):
    global status_icons
    if status_icons is None:
        status_icons = {
          ERROR_STATE_CLEAR : (load_pixmap('idle', '16x16'), load_pixmap('idle', '16x16')),
          ERROR_STATE_BUSY : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
          ERROR_STATE_ERROR : (load_pixmap('error', '16x16'), load_pixmap('error', '16x16')),
          ERROR_STATE_LOW_SUPPLIES : (load_pixmap('inkdrop', '16x16'), load_pixmap('toner', '16x16')),
          ERROR_STATE_OK : (load_pixmap('ok', '16x16'), load_pixmap('ok', '16x16')),
          ERROR_STATE_WARNING : (load_pixmap('warning', '16x16'), load_pixmap('warning', '16x16')),
          ERROR_STATE_LOW_PAPER: (load_pixmap('paper', '16x16'), load_pixmap('paper', '16x16')),
          ERROR_STATE_PRINTING : (load_pixmap("print", '16x16'), load_pixmap("print", '16x16')),
          ERROR_STATE_SCANNING : (load_pixmap("scan", '16x16'), load_pixmap("scan", '16x16')),
          ERROR_STATE_PHOTOCARD : (load_pixmap("pcard", '16x16'), load_pixmap("pcard", '16x16')),
          ERROR_STATE_FAXING : (load_pixmap("fax", '16x16'), load_pixmap("fax", '16x16')),
          ERROR_STATE_COPYING :  (load_pixmap("makecopies", '16x16'), load_pixmap("makecopies", '16x16')),
        }

    return status_icons.get(error_state, status_icons[ERROR_STATE_CLEAR])

# pixmaps for device icons (inkjet, laserjet)
overlay_icons = None

def getStatusOverlayIcon(error_state):
    global overlay_icons
    if overlay_icons is None:
        overlay_icons = {
            ERROR_STATE_CLEAR : (None, None),
            ERROR_STATE_BUSY : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
            ERROR_STATE_ERROR : (load_pixmap('error', '16x16'), load_pixmap('error', '16x16')),
            ERROR_STATE_LOW_SUPPLIES : (load_pixmap('inkdrop', '16x16'), load_pixmap('toner', '16x16')),
            ERROR_STATE_OK : (load_pixmap('ok', '16x16'), load_pixmap('ok', '16x16')),
            ERROR_STATE_WARNING : (load_pixmap('warning', '16x16'), load_pixmap('warning', '16x16')),
            ERROR_STATE_LOW_PAPER: (load_pixmap('paper', '16x16'), load_pixmap('paper', '16x16')),
            ERROR_STATE_PRINTING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
            ERROR_STATE_SCANNING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
            ERROR_STATE_PHOTOCARD : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
            ERROR_STATE_FAXING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
            ERROR_STATE_COPYING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
            ERROR_STATE_REFRESHING : (load_pixmap('refresh1', '16x16'), load_pixmap('refresh1', '16x16')),
        }

    return overlay_icons.get(error_state, overlay_icons[ERROR_STATE_CLEAR])


NUM_REPRS = {
      1 : __translate("one"),
      2 : __translate("two"),
      3 : __translate("three"),
      4 : __translate("four"),
      5 : __translate("five"),
      6 : __translate("six"),
      7 : __translate("seven"),
      8 : __translate("eight"),
      9 : __translate("nine"),
      10 : __translate("ten"),
      11 : __translate("eleven"),
      12 : __translate("twelve")
}

UNIT_NAMES = {
    "year" : (__translate("year"), __translate("years")),
    "month" : (__translate("month"), __translate("months")),
    "week" : (__translate("week"), __translate("weeks")),
    "day" : (__translate("day"), __translate("days")),
    "hour" : (__translate("hour"), __translate("hours")),
    "minute" : (__translate("minute"), __translate("minutes")),
    "second" : (__translate("second"), __translate("seconds")),
}


def getTimeDeltaDesc(past):
    t1 = QDateTime()
    t1.setTime_t(int(past))
    t2 = QDateTime.currentDateTime()
    delta = t1.secsTo(t2)
    return __translate("(%s ago)"%stringify(delta))


# "Nicely readable timedelta"
# Credit: Bjorn Lindqvist
# ASPN Python Recipe 498062
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/498062
# Note: Modified from recipe
def getSecondsInUnits(seconds):
    unit_limits = [("year", 31536000),
                   ("month", 2592000),
                   ("week", 604800),
                   ("day", 86400),
                   ("hour", 3600),
                   ("minute", 60)]

    for unit_name, limit in unit_limits:
        if seconds >= limit:
            amount = int(round(float(seconds) / limit))
            return amount, unit_name

    return seconds, "second"


def stringify(seconds):
    amount, unit_name = getSecondsInUnits(seconds)

    try:
        i18n_amount = NUM_REPRS[amount]
    except KeyError:
        i18n_amount = to_unicode(amount)

    if amount == 1:
        i18n_unit = UNIT_NAMES[unit_name][0]
    else:
        i18n_unit = UNIT_NAMES[unit_name][1]

    return "%s %s"%(i18n_amount, i18n_unit)
