from psycopg2.extensions import new_type, binary_types, string_types

from collections import namedtuple


def get_caster(typoid):
    """Utility method looking for a typoid in both binary_types and
    string_types."""
    return binary_types.get(typoid, None) or string_types[typoid]


def new_composite_type(typoid, typname, keys, types):
    """Create a type caster for the composite type describe by the arguments.

    typoid: the type oid
    typname: a name identifying the type
    keys: a list of attributes names, ordered like in the database
    types: a list of attributes types, indexed exactly like keys.
    """
    tupletype = namedtuple(typname, keys)
    casters = [get_caster(typeoid) for typeoid in types]

    def caster(value, cur):
        if value is None:
            return None
        assert value[0] == '('
        assert value[-1] == ')'
        value = value[1:-1]
        quoted = False
        escaped = False
        values = []
        current_value = ''

        for char in value:
            if escaped:
                escaped = False
                current_value += char
            elif char in ("'", '"'):
                quoted = not quoted
            elif char == "\\":
                escaped = True
            elif char == ',' and not quoted:
                values.append(current_value)
                current_value = ''
            else:
                current_value += char
        else:
            values.append(current_value)
        args = {}
        for idx, parsed in enumerate(values):
            args[keys[idx]] = casters[idx](parsed, cur)
        return tupletype(**args)
    return new_type((typoid,), typname, caster)
