"""
codegen.py generates pika/spec.py

The required spec json file can be found at
https://github.com/rabbitmq/rabbitmq-codegen
.

After cloning it run the following to generate a spec.py file:
python2 ./codegen.py ../../rabbitmq-codegen
"""
from __future__ import nested_scopes

import os
import re
import sys

if sys.version_info.major != 2:
    sys.exit('Python 2 is required at this time')

RABBITMQ_CODEGEN_PATH = sys.argv[1]
PIKA_SPEC = '../pika/spec.py'
print('codegen-path: %s' % RABBITMQ_CODEGEN_PATH)
sys.path.append(RABBITMQ_CODEGEN_PATH)

import amqp_codegen

DRIVER_METHODS = {
    "Exchange.Bind": ["Exchange.BindOk"],
    "Exchange.Unbind": ["Exchange.UnbindOk"],
    "Exchange.Declare": ["Exchange.DeclareOk"],
    "Exchange.Delete": ["Exchange.DeleteOk"],
    "Queue.Declare": ["Queue.DeclareOk"],
    "Queue.Bind": ["Queue.BindOk"],
    "Queue.Purge": ["Queue.PurgeOk"],
    "Queue.Delete": ["Queue.DeleteOk"],
    "Queue.Unbind": ["Queue.UnbindOk"],
    "Basic.Qos": ["Basic.QosOk"],
    "Basic.Get": ["Basic.GetOk", "Basic.GetEmpty"],
    "Basic.Ack": [],
    "Basic.Reject": [],
    "Basic.Recover": ["Basic.RecoverOk"],
    "Basic.RecoverAsync": [],
    "Tx.Select": ["Tx.SelectOk"],
    "Tx.Commit": ["Tx.CommitOk"],
    "Tx.Rollback": ["Tx.RollbackOk"]
}


def fieldvalue(v):
    if isinstance(v, unicode):
        return repr(v.encode('ascii'))
    elif isinstance(v, dict):
        return repr(None)
    elif isinstance(v, list):
        return repr(None)
    else:
        return repr(v)


def normalize_separators(s):
    s = s.replace('-', '_')
    s = s.replace(' ', '_')
    return s


def pyize(s):
    s = normalize_separators(s)
    if s in ('global', 'class'):
        s += '_'
    if s == 'global_':
        s = 'global_qos'
    return s


def camel(s):
    return normalize_separators(s).title().replace('_', '')


amqp_codegen.AmqpMethod.structName = lambda m: camel(m.klass.name) + '.' + camel(m.name)
amqp_codegen.AmqpClass.structName = lambda c: camel(c.name) + "Properties"


def constantName(s):
    return '_'.join(re.split('[- ]', s.upper()))


def flagName(c, f):
    if c:
        return c.structName() + '.' + constantName('flag_' + f.name)
    else:
        return constantName('flag_' + f.name)


def generate(specPath):
    spec = amqp_codegen.AmqpSpec(specPath)

    def genSingleDecode(prefix, cLvalue, unresolved_domain):
        type = spec.resolveDomain(unresolved_domain)
        if type == 'shortstr':
            print(prefix +
                  "%s, offset = data.decode_short_string(encoded, offset)" %
                  cLvalue)
        elif type == 'longstr':
            print(prefix +
                  "length = struct.unpack_from('>I', encoded, offset)[0]")
            print(prefix + "offset += 4")
            print(prefix + "%s = encoded[offset:offset + length]" % cLvalue)
            print(prefix + "try:")
            print(prefix + "    %s = str(%s)" % (cLvalue, cLvalue))
            print(prefix + "except UnicodeEncodeError:")
            print(prefix + "    pass")
            print(prefix + "offset += length")
        elif type == 'octet':
            print(prefix +
                  "%s = struct.unpack_from('B', encoded, offset)[0]" % cLvalue)
            print(prefix + "offset += 1")
        elif type == 'short':
            print(prefix +
                  "%s = struct.unpack_from('>H', encoded, offset)[0]" % cLvalue)
            print(prefix + "offset += 2")
        elif type == 'long':
            print(prefix +
                  "%s = struct.unpack_from('>I', encoded, offset)[0]" % cLvalue)
            print(prefix + "offset += 4")
        elif type == 'longlong':
            print(prefix +
                  "%s = struct.unpack_from('>Q', encoded, offset)[0]" % cLvalue)
            print(prefix + "offset += 8")
        elif type == 'timestamp':
            print(prefix +
                  "%s = struct.unpack_from('>Q', encoded, offset)[0]" % cLvalue)
            print(prefix + "offset += 8")
        elif type == 'bit':
            raise Exception("Can't decode bit in genSingleDecode")
        elif type == 'table':
            print(
                Exception(prefix +
                          "(%s, offset) = data.decode_table(encoded, offset)" %
                          cLvalue))
        else:
            raise Exception("Illegal domain in genSingleDecode", type)

    def genSingleEncode(prefix, cValue, unresolved_domain):
        type = spec.resolveDomain(unresolved_domain)
        if type == 'shortstr':
            print(
                prefix +
                "assert isinstance(%s, str_or_bytes),\\\n%s       'A non-string value was supplied for %s'"
                % (cValue, prefix, cValue))
            print(prefix + "data.encode_short_string(pieces, %s)" % cValue)
        elif type == 'longstr':
            print(
                prefix +
                "assert isinstance(%s, str_or_bytes),\\\n%s       'A non-string value was supplied for %s'"
                % (cValue, prefix, cValue))
            print(
                prefix +
                "value = %s.encode('utf-8') if isinstance(%s, unicode_type) else %s"
                % (cValue, cValue, cValue))
            print(prefix + "pieces.append(struct.pack('>I', len(value)))")
            print(prefix + "pieces.append(value)")
        elif type == 'octet':
            print(prefix + "pieces.append(struct.pack('B', %s))" % cValue)
        elif type == 'short':
            print(prefix + "pieces.append(struct.pack('>H', %s))" % cValue)
        elif type == 'long':
            print(prefix + "pieces.append(struct.pack('>I', %s))" % cValue)
        elif type == 'longlong':
            print(prefix + "pieces.append(struct.pack('>Q', %s))" % cValue)
        elif type == 'timestamp':
            print(prefix + "pieces.append(struct.pack('>Q', %s))" % cValue)
        elif type == 'bit':
            raise Exception("Can't encode bit in genSingleEncode")
        elif type == 'table':
            print(Exception(prefix + "data.encode_table(pieces, %s)" % cValue))
        else:
            raise Exception("Illegal domain in genSingleEncode", type)

    def genDecodeMethodFields(m):
        print("        def decode(self, encoded, offset=0):")
        bitindex = None
        for f in m.arguments:
            if spec.resolveDomain(f.domain) == 'bit':
                if bitindex is None:
                    bitindex = 0
                if bitindex >= 8:
                    bitindex = 0
                if not bitindex:
                    print(
                        "            bit_buffer = struct.unpack_from('B', encoded, offset)[0]"
                    )
                    print("            offset += 1")
                print("            self.%s = (bit_buffer & (1 << %d)) != 0" %
                      (pyize(f.name), bitindex))
                bitindex += 1
            else:
                bitindex = None
                genSingleDecode("            ", "self.%s" % (pyize(f.name),),
                                f.domain)
        print("            return self")
        print('')

    def genDecodeProperties(c):
        print("    def decode(self, encoded, offset=0):")
        print("        flags = 0")
        print("        flagword_index = 0")
        print("        while True:")
        print(
            "            partial_flags = struct.unpack_from('>H', encoded, offset)[0]"
        )
        print("            offset += 2")
        print(
            "            flags = flags | (partial_flags << (flagword_index * 16))"
        )
        print("            if not (partial_flags & 1):")
        print("                break")
        print("            flagword_index += 1")
        for f in c.fields:
            if spec.resolveDomain(f.domain) == 'bit':
                print("        self.%s = (flags & %s) != 0" % (pyize(f.name),
                                                               flagName(c, f)))
            else:
                print("        if flags & %s:" % (flagName(c, f),))
                genSingleDecode("            ", "self.%s" % (pyize(f.name),),
                                f.domain)
                print("        else:")
                print("            self.%s = None" % (pyize(f.name),))
        print("        return self")
        print('')

    def genEncodeMethodFields(m):
        print("        def encode(self):")
        print("            pieces = list()")
        bitindex = None

        def finishBits():
            if bitindex is not None:
                print("            pieces.append(struct.pack('B', bit_buffer))")

        for f in m.arguments:
            if spec.resolveDomain(f.domain) == 'bit':
                if bitindex is None:
                    bitindex = 0
                    print("            bit_buffer = 0")
                if bitindex >= 8:
                    finishBits()
                    print("            bit_buffer = 0")
                    bitindex = 0
                print("            if self.%s:" % pyize(f.name))
                print("                bit_buffer |= 1 << %d" % bitindex)
                bitindex += 1
            else:
                finishBits()
                bitindex = None
                genSingleEncode("            ", "self.%s" % (pyize(f.name),),
                                f.domain)
        finishBits()
        print("            return pieces")
        print('')

    def genEncodeProperties(c):
        print("    def encode(self):")
        print("        pieces = list()")
        print("        flags = 0")
        for f in c.fields:
            if spec.resolveDomain(f.domain) == 'bit':
                print("        if self.%s: flags = flags | %s" % (pyize(
                    f.name), flagName(c, f)))
            else:
                print("        if self.%s is not None:" % (pyize(f.name),))
                print("            flags = flags | %s" % (flagName(c, f),))
                genSingleEncode("            ", "self.%s" % (pyize(f.name),),
                                f.domain)
        print("        flag_pieces = list()")
        print("        while True:")
        print("            remainder = flags >> 16")
        print("            partial_flags = flags & 0xFFFE")
        print("            if remainder != 0:")
        print("                partial_flags |= 1")
        print(
            "            flag_pieces.append(struct.pack('>H', partial_flags))")
        print("            flags = remainder")
        print("            if not flags:")
        print("                break")
        print("        return flag_pieces + pieces")
        print('')

    def fieldDeclList(fields):