from .attributes import Attribute
from .constants import Utf8


# From: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html


##########################################################################
# 4.5. Fields
##########################################################################

# Each field is described by a field_info structure. No two fields in one class
# file may have the same name and descriptor (§4.3.2).

# The structure has the following format:

class Field:
    # u2 access_flags;
    # u2 name_index;
    # u2 descriptor_index;
    # u2 attributes_count;
    # attribute_info attributes[attributes_count];

    # Table 4.4. Field access and property flags

    # Flag Name   Value   Interpretation
    ACC_PUBLIC = 0x0001     # Declared public; may be accessed from outside its package.
    ACC_PRIVATE = 0x0002    # Declared private; usable only within the defining class.
    ACC_PROTECTED = 0x0004  # Declared protected; may be accessed within subclasses.
    ACC_STATIC = 0x0008     # Declared static.
    ACC_FINAL = 0x0010      # Declared final; never directly assigned to after object construction (JLS §17.5).
    ACC_VOLATILE = 0x0040   # Declared volatile; cannot be cached.
    ACC_TRANSIENT = 0x0080  # Declared transient; not written or read by a persistent object manager.
    ACC_SYNTHETIC = 0x1000  # Declared synthetic; not present in the source code.
    ACC_ENUM = 0x4000       # Declared as an element of an enum.

    def __init__(
                self, name, descriptor,
                public=True, private=False, protected=False,
                static=False, final=False, volatile=False, transient=False,
                synthetic=False, enum=False,
                attributes=None
            ):

        # A field may be marked with the ACC_SYNTHETIC flag to indicate that it
        # was generated by a compiler and does not appear in source code.
        #
        # The ACC_ENUM flag indicates that this field is used to hold an element
        # of an enumerated type.
        #
        # Fields of classes may set any of the flags in Table 4.4. However, a
        # specific field of a class may have at most one of its ACC_PRIVATE,
        # ACC_PROTECTED, and ACC_PUBLIC flags set (JLS §8.3.1) and must not have
        # both its ACC_FINAL and ACC_VOLATILE flags set (JLS §8.3.1.4).
        #
        # All fields of interfaces must have their ACC_PUBLIC, ACC_STATIC, and
        # ACC_FINAL flags set; they may have their ACC_SYNTHETIC flag set and
        # must not have any of the other flags in Table 4.4 set (JLS §9.3).

        self.private = private
        if self.private:
            self.protected = False
            self.public = False
        else:
            self.protected = protected
            if self.protected:
                self.public = False
            else:
                self.public = True

        self.static = static
        self.final = final
        self.volatile = volatile
        self.transient = transient
        self.synthetic = synthetic
        self.enum = enum

        # The value of the name_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be a
        # CONSTANT_Utf8_info (§4.4.7) structure which must represent a valid
        # unqualified name (§4.2.2) denoting a field.
        self.name = Utf8(name)

        # The value of the descriptor_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be a
        # CONSTANT_Utf8_info (§4.4.7) structure that must represent a valid
        # field descriptor (§4.3.2).
        self.descriptor = Utf8(descriptor)

        # Each value of the attributes table must be an attribute structure
        # (§4.7). A field can have any number of attributes associated with it.
        #
        # The attributes defined by this specification as appearing in the
        # attributes table of a field_info structure are ConstantValue (§4.7.2),
        # Synthetic (§4.7.8), Signature (§4.7.9), Deprecated (§4.7.15),
        # RuntimeVisibleAnnotations (§4.7.16) and RuntimeInvisibleAnnotations
        # (§4.7.17).
        #
        # A Java Virtual Machine implementation must recognize and correctly
        # read ConstantValue (§4.7.2) attributes found in the attributes table
        # of a field_info structure. If a Java Virtual Machine implementation
        # recognizes class files whose version number is 49.0 or above, it must
        # recognize and correctly read Signature (§4.7.9),
        # RuntimeVisibleAnnotations (§4.7.16) and RuntimeInvisibleAnnotations
        # (§4.7.17) attributes found in the attributes table of a field_info
        # structure of a class file whose version number is 49.0 or above.
        #
        # A Java Virtual Machine implementation is required to silently ignore
        # any or all attributes that it does not recognize in the attributes
        # table of a field_info structure. Attributes not defined in this
        # specification are not allowed to affect the semantics of the class
        # file, but only to provide additional descriptive information (§4.7.1).
        self.attributes = attributes if attributes else []

    def __repr__(self):
        return '<Field access:0x%04x name:%s, descriptor:%s>' % (self.access_flags, self.name, self.descriptor)

    @staticmethod
    def read(reader, dump=None):
        access_flags = reader.read_u2()

        name = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')
        descriptor = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')
        attributes_count = reader.read_u2()

        if dump:
            reader.debug("    " * dump, 'Field %s (%s)' % (name, descriptor))

            access_description = ', '.join(f for f in [
                    flag if access_flags & mask else None
                    for flag, mask in [
                        ('public', Field.ACC_PUBLIC),
                        ('private', Field.ACC_PRIVATE),
                        ('protected', Field.ACC_PROTECTED),
                        ('static', Field.ACC_STATIC),
                        ('final', Field.ACC_FINAL),
                        ('volatile', Field.ACC_VOLATILE),
                        ('transient', Field.ACC_TRANSIENT),
                        ('synthetic', Field.ACC_SYNTHETIC),
                        ('enum', Field.ACC_ENUM),
                    ]
                ] if f)
            reader.debug("    " * dump, '    Flags: 0x%04x%s' % (access_flags, ' (%s)') % (
                access_description if access_description else ''
            ))

            reader.debug("    " * dump, '    Attributes: (%s)' % attributes_count)

        attributes = []
        for i in range(0, attributes_count):
            attributes.append(
                Attribute.read(reader, dump=dump + 2 if dump is not None else dump)
            )

        return Field(
            name=name,
            descriptor=descriptor,
            public=bool(access_flags & Field.ACC_PUBLIC),
            private=bool(access_flags & Field.ACC_PRIVATE),
            protected=bool(access_flags & Field.ACC_PROTECTED),
            static=bool(access_flags & Field.ACC_STATIC),
            final=bool(access_flags & Field.ACC_FINAL),
            volatile=bool(access_flags & Field.ACC_VOLATILE),
            transient=bool(access_flags & Field.ACC_TRANSIENT),
            synthetic=bool(access_flags & Field.ACC_SYNTHETIC),
            enum=bool(access_flags & Field.ACC_ENUM),
            attributes=attributes,
        )

    def write(self, writer):
        writer.write_u2(self.access_flags)
        writer.write_u2(writer.constant_pool.index(self.name))
        writer.write_u2(writer.constant_pool.index(self.descriptor))
        writer.write_u2(self.attributes_count)

        for attribute in self.attributes:
            attribute.write(writer)

    def resolve(self, constant_pool):
        constant_pool.add(self.name)
        constant_pool.add(self.descriptor)

        for attribute in self.attributes:
            attribute.resolve(constant_pool)

    @property
    def attributes_count(self):
        """The value of the attributes_count item indicates the number of
        additional attributes (§4.7) of this field.
        """
        return len(self.attributes)

    @property
    def access_flags(self):
        """The value of the access_flags item is a mask of flags used to denote
        access permission to and properties of this field. The interpretation
        of each flag, when set, is as shown in Table 4.4.

        All bits of the access_flags item not assigned in Table 4.4 are
        reserved for future use. They should be set to zero in generated class
        files and should be ignored by Java Virtual Machine implementations.
        """
        return (
            (self.ACC_PUBLIC if self.public else 0) |
            (self.ACC_PRIVATE if self.private else 0) |
            (self.ACC_PROTECTED if self.protected else 0) |
            (self.ACC_STATIC if self.static else 0) |
            (self.ACC_FINAL if self.final else 0) |
            (self.ACC_VOLATILE if self.volatile else 0) |
            (self.ACC_TRANSIENT if self.transient else 0) |
            (self.ACC_SYNTHETIC if self.synthetic else 0) |
            (self.ACC_ENUM if self.enum else 0)
        )
