#!/usr/bin/python

import sys, struct, logging
from ippconstants import *

# initialize logger
logger = logging.getLogger("ippLogger")

class Value():
    """
    An IPP value consists of a tag and a value, and optionally, a name.

    From RFC 2565:
     -----------------------------------------------
     |                   value-tag                 |   1 byte
     -----------------------------------------------
     |               name-length  (value is u)     |   2 bytes
     -----------------------------------------------
     |                     name                    |   u bytes
     -----------------------------------------------
     |              value-length  (value is v)     |   2 bytes
     -----------------------------------------------
     |                     value                   |   v bytes
     -----------------------------------------------    
    """

    def __init__(self, value_tag, value, unpack=True):
        """
        Initialize a Value:

        Arguments:

            value_tag -- one byte, identifying the type of value

            value -- variable size, containing the actual value
        """

        # make sure value_tag isn't empty
        assert value_tag is not None
        # make sure value isn't empty
        assert value is not None

        self.value_tag = value_tag
        self.value = value

        if not unpack: return

        # out-of-band value tags
        if self.value_tag == OutOfBandTags.UNSUPPORTED or \
               self.value_tag == OutOfBandTags.DEFAULT or \
               self.value_tag == OutOfBandTags.UNKNOWN or \
               self.value_tag == OutOfBandTags.NO_VALUE:
            self.value = ''

        # integer value tags
        elif self.value_tag == IntegerTags.GENERIC:
            pass # not supported
        elif self.value_tag == IntegerTags.INTEGER:
            self.value = struct.unpack('>i', value)[0]
        elif self.value_tag == IntegerTags.BOOLEAN:
            self.value = struct.unpack('>?', value)[0]
        elif self.value_tag == IntegerTags.ENUM:
            self.value = struct.unpack('>i', value)[0]

        # octet string value tags
        elif self.value_tag == OctetStringTags.UNSPECIFIED_OCTETSTRING:
            pass
        
        elif self.value_tag == OctetStringTags.DATETIME:
            # field  octets  contents                  range
            # -----  ------  --------                  -----
            #   1      1-2   year                      0..65536
            #   2       3    month                     1..12
            #   3       4    day                       1..31
            #   4       5    hour                      0..23
            #   5       6    minutes                   0..59
            #   6       7    seconds                   0..60
            #                (use 60 for leap-second)
            #   7       8    deci-seconds              0..9
            #   8       9    direction from UTC        '+' / '-'
            #   9      10    hours from UTC            0..11
            #  10      11    minutes from UTC          0..59

            self.value = struct.unpack('>hbbbbbbcbb', value)[0]
            
        elif self.value_tag == OctetStringTags.RESOLUTION:
            # OCTET-STRING consisting of nine octets of 2
            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
            # SIGNED-INTEGER contains the value of cross feed
            # direction resolution. The second SIGNED-INTEGER contains
            # the value of feed direction resolution. The SIGNED-BYTE
            # contains the units

            self.value = struct.unpack('>iib', value)
            
        elif self.value_tag == OctetStringTags.RANGE_OF_INTEGER:
            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
            # SIGNED-INTEGER contains the lower bound and the second
            # SIGNED-INTEGER contains the upper bound.

            self.value = struct.unpack('>ii', value)

        elif self.value_tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
                 self.value_tag == OctetStringTags.NAME_WITH_LANGUAGE:
            a = struct.unpack('>h', value[:2])[0]
            b = struct.unpack('>%ss' % a, value[2:a+2])[0]
            c = struct.unpack('>h', value[a+2:a+4])[0]
            d = struct.unpack('>%ss' % c, value[a+4:][0])
            self.value = (a, b, c, d)

        # character string value tags
        elif self.value_tag == CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
                 self.value_tag == CharacterStringTags.NAME_WITHOUT_LANGUAGE:
            self.value = str(value)
        elif self.value_tag == CharacterStringTags.GENERIC or \
                 self.value_tag == CharacterStringTags.KEYWORD or \
                 self.value_tag == CharacterStringTags.URI or \
                 self.value_tag == CharacterStringTags.URI_SCHEME or \
                 self.value_tag == CharacterStringTags.CHARSET or \
                 self.value_tag == CharacterStringTags.NATURAL_LANGUAGE or \
                 self.value_tag == CharacterStringTags.MIME_MEDIA_TYPE:
            self.value = str(value)

    def valueToBinary(self):

        # out-of-band value tags
        if self.value_tag == OutOfBandTags.UNSUPPORTED or \
               self.value_tag == OutOfBandTags.DEFAULT or \
               self.value_tag == OutOfBandTags.UNKNOWN or \
               self.value_tag == OutOfBandTags.NO_VALUE:
            return (0, '')

        # integer value tags
        elif self.value_tag == IntegerTags.GENERIC:
            pass
        elif self.value_tag == IntegerTags.INTEGER:
            return (4, struct.pack('>i', self.value))
        elif self.value_tag == IntegerTags.BOOLEAN:
            return (1, struct.pack('>?', self.value))
        elif self.value_tag == IntegerTags.ENUM:
            return (4, struct.pack('>i', self.value))

        # octet string value tags
        elif self.value_tag == OctetStringTags.UNSPECIFIED_OCTETSTRING:
            pass
        elif self.value_tag == OctetStringTags.DATETIME:
            # field  octets  contents                  range
            # -----  ------  --------                  -----
            #   1      1-2   year                      0..65536
            #   2       3    month                     1..12
            #   3       4    day                       1..31
            #   4       5    hour                      0..23
            #   5       6    minutes                   0..59
            #   6       7    seconds                   0..60
            #                (use 60 for leap-second)
            #   7       8    deci-seconds              0..9
            #   8       9    direction from UTC        '+' / '-'
            #   9      10    hours from UTC            0..11
            #  10      11    minutes from UTC          0..59
            
            return (11, struct.pack('>hbbbbbbcbb', self.value))
            
        elif self.value_tag == OctetStringTags.RESOLUTION:
            # OCTET-STRING consisting of nine octets of 2
            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
            # SIGNED-INTEGER contains the value of cross feed
            # direction resolution. The second SIGNED-INTEGER contains
            # the value of feed direction resolution. The SIGNED-BYTE
            # contains the units
            
            return (9, struct.pack('>iib', self.value))
            
        elif self.value_tag == OctetStringTags.RANGE_OF_INTEGER:
            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
            # SIGNED-INTEGER contains the lower bound and the second
            # SIGNED-INTEGER contains the upper bound.
            
            return (8, struct.pack('>ii', self.value))

        elif self.value_tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
                 self.value_tag == OctetStringTags.NAME_WITH_LANGUAGE:
            a_bin = struct.pack('>h', self.value[0])
            b_bin = struct.pack('>%ss' % self.value[0], self.value[1])
            c_bin = struct.pack('>h', self.value[2])
            d_bin = struct.pack('>%ss' % self.value[2], self.value[3])
            return (4 + self.value[0] + self.value[2],
                    a_bin + b_bin + c_bin + d_bin)

        # character string value tags
        elif self.value_tag == CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
                 self.value_tag == CharacterStringTags.NAME_WITHOUT_LANGUAGE:
            return (len(self.value), struct.pack('>%ss' % len(self.value), self.value))
        elif self.value_tag == CharacterStringTags.GENERIC or \
                 self.value_tag == CharacterStringTags.KEYWORD or \
                 self.value_tag == CharacterStringTags.URI or \
                 self.value_tag == CharacterStringTags.URI_SCHEME or \
                 self.value_tag == CharacterStringTags.CHARSET or \
                 self.value_tag == CharacterStringTags.NATURAL_LANGUAGE or \
                 self.value_tag == CharacterStringTags.MIME_MEDIA_TYPE:
            return (len(self.value), struct.pack('>%ss' % len(self.value), self.value))

        return len(self.value), self.value
