#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (c) Copyright 2003-2008 Hewlett-Packard 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

from base.g import *
from base import utils

import os.path
import re
import glob

pat_prod_num = re.compile("""(\d+)""", re.I)

TYPE_UNKNOWN = 0
TYPE_STRING = 1
TYPE_STR = 1
TYPE_LIST = 2
TYPE_BOOL = 3
TYPE_INT = 4
TYPE_HEX = 5
TYPE_BITFIELD = 6
TYPE_URI = TYPE_STR # (7) not used (yet)

TECH_CLASSES = [
    "Undefined", # This will show an error (and its the default)
    "Unsupported", # This is for unsupported models, and it will not show an error
    "Postscript",
    "DJGenericVIP",
    #"PSB9100", not used on HPLIP
    "LJMono",
    "LJColor",
    "LJFastRaster",
    "LJJetReady",
    "DJ350",
    #"DJ400", not used on HPLIP
    "DJ540",
    "DJ600",
    "DJ6xx",
    "DJ6xxPhoto",
    "DJ630",
    #"DJ660", not used in HPLIP
    "DJ8xx",
    "DJ8x5",
    "DJ850",
    "DJ890",
    "DJ9xx",
    "DJ9xxVIP",
    "DJ3600",
    "DJ3320",
    "DJ4100",
    "AP2xxx",
    "AP21xx",
    "AP2560",
    "PSP100",
    "PSP470",
    "LJZjsMono",
    "LJZjsColor",
    "LJm1005",
    "QuickConnect",
    "DJ55xx",
    "OJProKx50",
]

TECH_CLASSES.sort()

TECH_CLASS_PDLS = {
    #"Undefined"    : '?',
    "Postscript"   : 'ps',
    "DJGenericVIP" : 'pcl3',
    #"PSB9100"      : 'pcl3',
    "LJMono"       : 'pcl3',
    "LJColor"      : 'pcl3',
    "LJFastRaster" : 'pclxl',
    "LJJetReady"   : 'pclxl',
    "DJ350"        : 'pcl3', 
    #"DJ400"        : 'pcl3',
    "DJ540"        : 'pcl3',
    "DJ600"        : 'pcl3',
    "DJ6xx"        : 'pcl3',
    "DJ6xxPhoto"   : 'pcl3',
    "DJ630"        : 'pcl3',
    #"DJ660"        : 'pcl3',
    "DJ8xx"        : 'pcl3',
    "DJ8x5"        : 'pcl3',
    "DJ850"        : 'pcl3',
    "DJ890"        : 'pcl3',
    "DJ9xx"        : 'pcl3',
    "DJ9xxVIP"     : 'pcl3',
    "DJ3600"       : 'lidil',
    "DJ3320"       : 'lidil',
    "DJ4100"       : 'lidil',
    "AP2xxx"       : 'pcl3',
    "AP21xx"       : 'pcl3',
    "AP2560"       : 'pcl3',
    "PSP100"       : 'pcl3',
    "PSP470"       : 'pcl3',
    "LJZjsMono"    : 'zjs',
    "LJZjsColor"   : 'zjs',
    "LJm1005"      : 'zxstream',
    "QuickConnect" : 'jpeg',
    "DJ55xx"       : 'pcl3',
    "OJProKx50"    : 'pcl3',
}


TECH_SUBCLASSES = [
    "LargeFormatSuperB",
    "LargeFormatA3",
    "CoverMedia", # 3425
    "FullBleed",
    "Duplex",
    "Normal",
    "Apollo2000",
    "Apollo2200",
    "Apollo2500",
    "NoPhotoMode",
    "NoPhotoBestHiresModes",
    "No1200dpiNoSensor",
    "NoFullBleed",
    "4x6FullBleed",
    "300dpiOnly",  # LaserJet 4L
    "GrayscaleOnly", # DJ540
]

TECH_SUBCLASSES.sort()


# Items will be capitalized unless in this dict
MODEL_UI_REPLACEMENTS = {'laserjet'   : 'LaserJet',
                          'psc'        : 'PSC',
                          'officejet'  : 'Officejet',
                          'deskjet'    : 'Deskjet',
                          'hp'         : 'HP',
                          'business'   : 'Business',
                          'inkjet'     : 'Inkjet',
                          'photosmart' : 'Photosmart',
                          'color'      : 'Color',
                          'series'     : 'series',
                          'printer'    : 'Printer',
                          'mfp'        : 'MFP',
                          'mopier'     : 'Mopier',
                          'pro'        : 'Pro',
                          'apollo'     : 'Apollo',
                        }


def normalizeModelUIName(model):
    ml = model.lower().strip()

    if 'apollo' in ml:
        z = ml.replace('_', ' ')
    else:
        if ml.startswith("hp"):
            z = ml[3:].replace('_', ' ')
        else:
            z = ml.replace('_', ' ')

    y = []
    for x in z.split():
        if pat_prod_num.search(x): # don't cap items like cp1700dn
            y.append(x)
        else:
            y.append(MODEL_UI_REPLACEMENTS.get(x, x.capitalize()))

    if 'apollo' in ml:
        return ' '.join(y)
    else:
        return "HP " + ' '.join(y)


def normalizeModelName(model):
    return utils.xstrip(model.replace(' ', '_').replace('__', '_').replace('~','').replace('/', '_'), '_')


class ModelData:
    def __init__(self, root_path=None):
        if root_path is None:
            self.root_path = prop.models_dir
        else:
            self.root_path = root_path

        self.__cache = {}
        self.reset_includes()
        self.sec = re.compile(r'^\[(.*)\]')
        self.inc = re.compile(r'^\%include (.*)', re.IGNORECASE)
        self.inc_line = re.compile(r'^\%(.*)\%')
        self.eq = re.compile(r'^([^=]+)=(.*)')

        files = [(os.path.join(self.root_path, "models.dat"), 
                  os.path.join(self.root_path, "unreleased", "unreleased.dat")),
                 (os.path.join(os.getcwd(), 'data', 'models', 'models.dat'), 
                  os.path.join(os.getcwd(), 'data', 'models', 'unreleased', 'unreleased.dat'))]

        for self.released_dat, self.unreleased_dat in files:
            if os.path.exists(self.released_dat):
                break
            
        else:
            self.released_dat, self.unreleased_dat = None, None
            log.error("Unable to locate models.dat file")

        self.FIELD_TYPES = {
            # Static model query data (from models.dat)
            'align-type' : TYPE_INT,
            'clean-type' : TYPE_INT,
            'color-cal-type' : TYPE_INT,
            'copy-type' : TYPE_INT,
            'embedded-server-type' : TYPE_INT,
            'fax-type' : TYPE_INT,
            'fw-download' : TYPE_BOOL,
            'icon' : TYPE_STR,
            'io-mfp-mode' : TYPE_INT,
            'io-mode' : TYPE_INT,
            'io-support' : TYPE_BITFIELD,
            'monitor-type' : TYPE_INT,
            'linefeed-cal-type' : TYPE_INT,
            'panel-check-type' : TYPE_INT,
            'pcard-type' : TYPE_INT,
            'plugin' : TYPE_INT,
            'power-settings': TYPE_INT,
            'pq-diag-type' : TYPE_INT,
            'r-type' : TYPE_INT,
            'scan-style' : TYPE_INT,
            'scan-type' : TYPE_INT,
            'status-battery-check' : TYPE_INT,
            'status-dynamic-counters' : TYPE_INT,
            'status-type' : TYPE_INT,
            'support-released' : TYPE_BOOL,
            'support-type' : TYPE_INT,
            'support-ver' : TYPE_STR,
            'tech-class' : TYPE_LIST,
            'tech-subclass' : TYPE_LIST,
            'tech-type' : TYPE_INT,
            'usb-pid' : TYPE_HEX,
            'usb-vid' : TYPE_HEX,
            'job-storage' : TYPE_INT,

            # Dynamic model data (from device query)
            'dev-file' : TYPE_STR,
            'fax-uri' : TYPE_STR,
            'scan-uri' : TYPE_STR,
            'is-hp' : TYPE_BOOL,
            'host' : TYPE_STR,
            'status-desc' : TYPE_STR,
            'cups-printer' : TYPE_STR,
            'serial' : TYPE_STR,
            'error-state' : TYPE_INT,
            'device-state' : TYPE_INT,
            'panel' : TYPE_INT,
            'device-uri' : TYPE_STR,
            'panel-line1' : TYPE_STR,
            'panel-line2' : TYPE_STR,
            'back-end' : TYPE_STR,
            'port' : TYPE_INT,
            'deviceid' : TYPE_STR,
            'cups-uri' : TYPE_STR,
            'status-code' : TYPE_INT,
            'rs' : TYPE_STR,
            'rr' : TYPE_STR,
            'rg' : TYPE_STR,
            'r' : TYPE_INT,
            'duplexer' : TYPE_INT,
            'supply-door' : TYPE_INT,
            'revision' : TYPE_INT,
            'media-path' : TYPE_INT,
            'top-door' : TYPE_BOOL,
            'photo-tray' : TYPE_BOOL,
            }

        self.RE_FIELD_TYPES = {
            re.compile('^r(\d+)-agent(\d+)-kind', re.IGNORECASE) : TYPE_INT,
            re.compile('^r(\d+)-agent(\d+)-type', re.IGNORECASE) : TYPE_INT,
            re.compile('^r(\d+)-agent(\d+)-sku', re.IGNORECASE) : TYPE_STR,
            re.compile('^agent(\d+)-desc', re.IGNORECASE) : TYPE_STR,
            re.compile('^agent(\d+)-virgin', re.IGNORECASE) : TYPE_BOOL,
            re.compile('^agent(\d+)-dvc', re.IGNORECASE) : TYPE_INT,
            re.compile('^agent(\d+)-kind', re.IGNORECASE) : TYPE_INT,
            re.compile('^agent(\d+)-type', re.IGNORECASE) : TYPE_INT,
            re.compile('^agent(\d+)-id', re.IGNORECASE) : TYPE_INT,
            re.compile('^agent(\d+)-hp-ink', re.IGNORECASE) : TYPE_BOOL,
            re.compile('^agent(\d+)-health-desc', re.IGNORECASE) : TYPE_STR,
            re.compile('^agent(\d+)-health$', re.IGNORECASE) : TYPE_INT,
            re.compile('^agent(\d+)-known', re.IGNORECASE) : TYPE_BOOL,
            re.compile('^agent(\d+)-level', re.IGNORECASE) : TYPE_INT,
            re.compile('^agent(\d+)-ack', re.IGNORECASE) : TYPE_BOOL,
            re.compile('^agent(\d+)-sku', re.IGNORECASE) : TYPE_STR,
            re.compile('^in-tray(\d+)', re.IGNORECASE) : TYPE_BOOL,
            re.compile('^out-tray(\d+)', re.IGNORECASE) : TYPE_BOOL,
            re.compile('model(\d+)', re.IGNORECASE) : TYPE_STR,
            }

        self.TYPE_CACHE = {}


    def read_all_files(self, unreleased=True):
        if os.path.exists(self.released_dat):
            self.read_section(self.released_dat)

            if self.unreleased_dat is not None and os.path.exists(self.unreleased_dat):
                self.read_section(self.unreleased_dat )

        return self.__cache


    def read_section(self, filename, section=None, is_include=False): # section==None, read all sections
        found, in_section = False, False

        if section is not None:
            section = section.lower()

            if is_include:
                log.debug("Searching for include [%s] in file %s" % (section, filename))
            else:
                log.debug("Searching for section [%s] in file %s" % (section, filename))

        if is_include:
            cache = self.__includes
        else:
            cache = self.__cache

        try:
            fd = file(filename)
        except IOError, e:
            log.error("I/O Error: %s (%s)" % (filename, e.strerror))
            return False

        while True:
            line = fd.readline()

            if not line:
                break

            if line[0] in ('#', ';'):
                continue

            if line[0] == '[':
                if in_section and section is not None:
                    break

                match = self.sec.search(line)

                if match is not None:
                    in_section = True

                    read_section = match.group(1).lower()

                    if section is not None:
                        found = in_section = (read_section == section)

                    if in_section:
                        if section is not None:
                            log.debug("Found section [%s] in file %s" % (read_section, filename))

                        cache[read_section] = {}

                continue

            if line[0] == '%':
                match = self.inc.match(line)

                if match is not None:
                    inc_file = match.group(1)
                    log.debug("Found include file directive: %%include %s" % inc_file)
                    self.__include_files.append(os.path.join(os.path.dirname(filename), inc_file))
                    continue

                if in_section:
                    match = self.inc_line.match(line)

                    if match is not None: 
                        inc_sect = match.group(1)
                        log.debug("Found include directive %%%s%%" % inc_sect)

                        try:
                            self.__includes[inc_sect]
                        except KeyError:
                            for inc in self.__include_files:

                                if self.read_section(inc, inc_sect, True):
                                    break
                            else:
                                log.error("Include %%%s%% not found." % inc_sect)

            if in_section:
                match = self.eq.search(line)

                if match is not None:
                    key = match.group(1)
                    value = match.group(2)
                    value = self.convert_data(key, value)
                    cache[read_section][key] = value

        fd.close()
        return found


    def reset_includes(self):
        self.__include_files = []
        self.__includes = {}


    def __getitem__(self, model):
        model = model.lower()

        try:
            return self.__cache[model]
        except:
            log.debug("Cache miss: %s" % model)

            log.debug("Reading file: %s" % self.released_dat)

            if self.read_section(self.released_dat, model):
                return self.__cache[model]

            if self.unreleased_dat is not None and os.path.exists(self.unreleased_dat):
                log.debug("Reading file: %s" % self.unreleased_dat)

                if self.read_section(self.unreleased_dat, model):
                    return self.__cache[model]

            return {}


    def all_models(self):
        return self.__cache


    def get_data_type(self, key):
        try:
            return self.FIELD_TYPES[key]
        except KeyError:
            try:
                return self.TYPE_CACHE[key]
            except KeyError:
                for pat, typ in self.RE_FIELD_TYPES.items():
                    match = pat.match(key)
                    if match is not None:
                        self.TYPE_CACHE[key] = typ
                        return typ

        log.warn("get_data_type(): Defaulted to TYPE_STR for key %s" % key)
        return TYPE_STR


    def convert_data(self, key, value, typ=None):
        if typ is None:
            typ = self.get_data_type(key)

        if  typ in (TYPE_BITFIELD, TYPE_INT):
            value = int(value)

        elif typ == TYPE_BOOL:
            value = utils.to_bool(value)
            #value = int(value)

        elif typ == TYPE_LIST:
            value = [x for x in value.split(',') if x]

        return value
