# -*- coding: utf-8 -*-
"""
    https://tools.ietf.org/html/draft-sweet-rfc2910bis-07
    Internet Printing Protocol/1.1: Encoding and Transport

    3.1.1.  Request and Response

       An operation request or response is encoded as follows:

       -----------------------------------------------
       |                  version-number             |   2 bytes  - required
       -----------------------------------------------
       |               operation-id (request)        |
       |                      or                     |   2 bytes  - required
       |               status-code (response)        |
       -----------------------------------------------
       |                   request-id                |   4 bytes  - required
       -----------------------------------------------
       |                 attribute-group             |   n bytes - 0 or more
       -----------------------------------------------
       |              end-of-attributes-tag          |   1 byte   - required
       -----------------------------------------------
       |                     data                    |   q bytes  - optional
       -----------------------------------------------



    IPP objects contain
        - base types (1-values)
        - lists (continuations) containing a single type
        - dicts (groups)

    We represent an IPP object as dict of objects where each object can be one of the IPP object
    types

    e.g. ipp_object = {
        'a': true,
        'b': 1,
        'c': 'a string',
        'd': [1, 2],
        'e': {
            'a': false,
            'b': 2,
            ...
        }

    }


"""
from __future__ import division, print_function
import sys
import os
import csv
from datetime import datetime
from collections import defaultdict, OrderedDict
from pprint import pprint


IPP_BUF_SIZE = 32767

IPP_TAG_ZERO = 0x00             # Zero tag - used for separators
IPP_TAG_OPERATION = 1           # Operation group
IPP_TAG_JOB = 2                 # Job group
IPP_TAG_END = 3                 # End-of-attributes
IPP_TAG_PRINTER = 4             # Printer group
IPP_TAG_UNSUPPORTED_GROUP = 5   # Unsupported attributes group
IPP_TAG_SUBSCRIPTION = 6        # Subscription group
IPP_TAG_EVENT_NOTIFICATION = 7  # Event group
IPP_TAG_RESOURCE = 8            # Resource group @private@
IPP_TAG_DOCUMENT = 9            # Document group

GROUP_TAGS = set(range(IPP_TAG_OPERATION, IPP_TAG_DOCUMENT + 1))

IPP_TAG_UNSUPPORTED_VALUE = 0x10  # Unsupported value
IPP_TAG_DEFAULT = 0x11          # Default value
IPP_TAG_UNKNOWN = 0x12          # Unknown value
IPP_TAG_NOVALUE = 0x13          # No-value value

IPP_TAG_NOTSETTABLE = 0x15      # Not-settable value
IPP_TAG_DELETEATTR = 0x16       # Delete-attribute value
IPP_TAG_ADMINDEFINE = 0x17      # Admin-defined value

IPP_TAG_INTEGER = 0x21          # Integer value
IPP_TAG_BOOLEAN = 0x22          # Boolean value
IPP_TAG_ENUM = 0x23             # Enumeration value

IPP_TAG_STRING = 0x30           # Octet string value
IPP_TAG_DATE = 0x31             # Date/time value
IPP_TAG_RESOLUTION = 0x32       # Resolution value
IPP_TAG_RANGE = 0x33            # Range value
IPP_TAG_BEGIN_COLLECTION = 0x34 # Beginning of collection value
IPP_TAG_TEXTLANG = 0x35         # Text-with-language value
IPP_TAG_NAMELANG = 0x36         # Name-with-language value
IPP_TAG_END_COLLECTION = 0x37   # End of collection value

IPP_TAG_TEXT = 0x41             # Text value
IPP_TAG_NAME = 0x42             # Name value
IPP_TAG_RESERVED_STRING = 0x43  # Reserved for future string value @private@
IPP_TAG_KEYWORD = 0x44          # Keyword value
IPP_TAG_URI = 0x45              # URI value
IPP_TAG_URISCHEME = 0x46        # URI scheme value
IPP_TAG_CHARSET = 0x47          # Character set value
IPP_TAG_LANGUAGE = 0x48         # Language value
IPP_TAG_MIMETYPE = 0x49         # MIME media type value
IPP_TAG_MEMBERNAME = 0x4A       # Collection member name value

IPP_TAG_EXTENSION = 0x7f     # Extension point for 32-bit tags
IPP_TAG_CUPS_MASK = 0x7fffffff,   # Mask for copied attribute values @private@
# The following expression is used to avoid compiler warnings with +/-0x80000000
IPP_TAG_CUPS_CONST = -0x7fffffff - 1    # Bitflag for copied/const attribute values @private@

TAG_DICT = {k: v for k, v in globals().items() if k.startswith('IPP_TAG_')}
assert len(TAG_DICT.values()) == len(set(TAG_DICT.values()))
TAG_NAME = {v: k for k, v in TAG_DICT.items()}


DEBUG = False


def tag_name(tag):
    return TAG_NAME.get(tag, 'UNKNOWN')


def tag_describe(tag):
    if tag is None:
        return 'tag is None  WTF!!!'
    return '0x%02x %s' % (tag, tag_name(tag))


def dprint(msg):
    if DEBUG:
        print(msg)


def recursive_glob(path):
    """Generator that returns all files in directory `path` if path is a directory, or path itself
        if path is not a directory.
    """
    if not os.path.isdir(path):
        yield path
    else:
        for root, _, files in os.walk(path):
            for filename in files:
                if not filename.startswith('.'):
                    yield(os.path.join(root, filename))


def T(text):
    return ''.join(chr(x) for x in text)


def H(text, n=None):
    if n is not None:
        text = text[:n]
    return ','.join(['%02x' % x for x in text])


def R(obj):
    if isinstance(obj, tuple):
        return list(obj)
    return obj


def be2(text):
    return text[0] << 8 | text[1]


def be4(text):
    return (((((text[0] << 8) | text[1]) << 8) | text[2]) << 8) | text[3]


class IPP:

    def __init__(ipp, text):
        ipp.text = text
        ipp.i = 0
        ipp.i_tag = -1
        ipp.attrs = []

    def __repr__(self):
        return 'IPP{len=%d,i=%d}' % (len(self.text), self.i)

    def peek(ipp, n, is_read=False):
        assert 0 <= ipp.i, (n, ipp.i, len(ipp.text))
        assert n > 0
        op = 'read' if is_read else 'peek'
        dprint('    %s len=%d i=%4d n=%2d data=%s' %
               (op, len(ipp.text), ipp.i, n, H(ipp.text[ipp.i:], min(n, 20))))
        assert ipp.i + n <= len(ipp.text)
        assert ipp.i < len(ipp.text)
        if ipp.i + n >= len(ipp.text):
            dprint('Done reading IPP')
            return None
        return ipp.text[ipp.i:ipp.i + n]

    def read(ipp, n):
        byts = ipp.peek(n, is_read=True)
        ipp.i += n
        return byts

    def peek_tag(ipp, is_read=False):
        byts = ipp.peek(1, is_read)
        if byts is None:
            return None
        tag = byts[0]
        assert tag != IPP_TAG_END
        assert tag in TAG_NAME, tag
        op = 'r' if is_read else 'p'
        dprint('!@@%s: %s i=%d' % (op, tag_describe(tag), ipp.i))
        ipp.i_tag = ipp.i
        return tag

    def read_tag(ipp):
        tag = ipp.peek_tag(is_read=True)
        ipp.i += 1
        return tag


def decode_datetime(value):
    """https://tools.ietf.org/html/rfc1903
        DateAndTime ::= TEXTUAL-CONVENTION
        DISPLAY-HINT "2d-1d-1d,1d:1d:1d.1d,1a1d:1d"
        STATUS       current
        DESCRIPTION
                "A date-time specification.

                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
    """
    year = be2(value[:2])
    month = value[2]