@interface IoT{{schema_data.name|class_name_syntax}}Attr ()
@property (nonatomic, readonly, strong) dispatch_queue_t syncQueue;
{% for prop, prop_data in schema_data.props.items() %}
{% if 'readOnly' in prop_data and prop_data['readOnly']%}
{% if 'enum' in prop_data %}
@property (nonatomic, assign) {{schema_data.name|enum_name_syntax(prop)}} {{prop|variable_syntax(True)}};
{% else %}
@property (nonatomic, {{objc_property_qualifier(prop_data, prop)}}) {{prop_data | convert_to_objc_type(prop)}} {{prop|variable_syntax(True)}};
{% endif %}
{% endif %}
{% endfor %}
{% for prop, prop_data in schema_data.props.items() %}
@property (nonatomic, assign) BOOL {{prop|variable_syntax(True)}}Changed;
{% endfor %}
@end

@implementation IoT{{schema_data.name|class_name_syntax}}Attr {
{% for prop, prop_data in schema_data.props.items() %}
{% if 'enum' in prop_data %}
    {{schema_data.name|enum_name_syntax(prop)}}  _{{prop|variable_syntax(True)}};
{% else %}
    {{prop_data | convert_to_objc_type(prop)}}  _{{prop|variable_syntax(True)}};
{% endif %}
{% endfor %}
}

- (instancetype)initWithStateSyncQueue:(dispatch_queue_t)syncQueue
{
    NSParameterAssert(NULL != syncQueue);
    self = [super init];
    if (self) {
        self->_syncQueue = syncQueue;
{% for prop, prop_data in schema_data.props.items() %}
{% if 'enum' in prop_data %}
{% if "unknown" in prop_data['enum'][-1] %}
        self->_{{prop|variable_syntax(True)}} = {{schema_data.name|enum_name_syntax(prop)}}Unknown;
{% else %}
        self->_{{prop|variable_syntax(True)}} = {{schema_data.name|enum_name_syntax(prop)}}{{prop_data['enum'][0]|class_name_syntax}};
{% endif %}
{% else %}
        self->_{{prop|variable_syntax(True)}} = {{prop_data | default_value_objc_type(prop)}};
{% endif %}
{% endfor %}
        [self resetChangedFlags];
    }
    return self;
}

- (void)resetChangedFlags {
    makeWeak(self)
    dispatch_barrier_async(self.syncQueue, ^{
        makeStrongAndReturnIfNil(self)
{% for prop, prop_data in schema_data.props.items() %}
        self->_{{prop|variable_syntax(True)}}Changed = NO;
{% endfor %}
    });
}

- (BOOL)selfOrSubtreeDirty
{
    BOOL working = NO;
{% for prop, prop_data in schema_data.props.items() %}
{% if not 'readOnly' in prop_data or not prop_data['readOnly'] %}
    working = working || self.{{prop|variable_syntax(True)}}Changed;
{% if is_prop_an_object(prop_data) %}
    working = working || [self.{{prop|variable_syntax(True)}} selfOrSubtreeDirty];
{% endif %}
{% endif %}
{% endfor %}
    return working;
}

- (instancetype)copyWithZone:(NSZone *)zone
{
    IoT{{schema_data.name|class_name_syntax}}Attr *copy = [[IoT{{schema_data.name|class_name_syntax}}Attr alloc] initWithStateSyncQueue:self.syncQueue];
    if (copy) {
{% if props_have_object_array(schema_data.props) %}
        NSMutableArray *copyArray = nil;
{% endif %}
{% for prop, prop_data in schema_data.props.items() %}
{% if is_prop_an_object(prop_data) %}
        copy.{{prop|variable_syntax(True)}} = [self.{{prop|variable_syntax(True)}} copy];
{% elif is_prop_an_object_array(prop_data) %}
        copyArray = [[NSMutableArray alloc] init];
        for (id item in self.{{prop|variable_syntax(True)}}) {
            [copyArray addObject:[item copy]];
        }
        copy.{{prop|variable_syntax(True)}} = copyArray;
{% else %}
        copy.{{prop|variable_syntax(True)}} = self.{{prop|variable_syntax(True)}};
{% endif %}
{% endfor %}
        [copy resetChangedFlags];
    }
    return copy;
}

{% for prop, prop_data in schema_data.props.items() %}
{% if 'enum' in prop_data %}
GETTER_SYNCHRONIZED({{schema_data.name|enum_name_syntax(prop)}},{{prop|variable_syntax(True)}})
SETTER_SYNCHRONIZED({{schema_data.name|enum_name_syntax(prop)}},set{{prop|variable_syntax(True,True)}},{{prop|variable_syntax(True)}},{{prop|variable_syntax(True)}}) 
{% else %}
GETTER_SYNCHRONIZED({{prop_data | convert_to_objc_type(prop)}},{{prop|variable_syntax(True)}})
{% if is_prop_string_type(prop_data) or  is_prop_array_type(prop_data) %}
SETTER_SYNCHRONIZED({{prop_data | convert_to_objc_type(prop)}},set{{prop|variable_syntax(True,True)}},{{prop|variable_syntax(True)}},[{{prop|variable_syntax(True)}} copy]) 
{% else %}
SETTER_SYNCHRONIZED({{prop_data | convert_to_objc_type(prop)}},set{{prop|variable_syntax(True,True)}},{{prop|variable_syntax(True)}},{{prop|variable_syntax(True)}}) 
{% endif %}
{% endif %}
GETTER_SYNCHRONIZED(BOOL,{{prop|variable_syntax(True)}}Changed)
{% endfor %}

{% for prop, prop_data in schema_data.props.items() %}
{% if 'enum' in prop_data %}
- (std::string)pack{{schema_data.name|enum_name_syntax(prop)}}ToString:({{schema_data.name|enum_name_syntax(prop)}})value
{
    switch (value) {
{% for value in prop_data['enum'] %}
        case {{schema_data.name|enum_name_syntax(prop)}}{{value|class_name_syntax}}:
            return std::string("{{value}}");
            break;
{% endfor %}
        default:
{% if "unknown" in prop_data['enum'][-1] %}
            return std::string("unknown");
{% else %}
            return std::string("{{prop_data['enum'][0]}}");
{% endif %}
            break;
    }
}

- ({{schema_data.name|enum_name_syntax(prop)}})unpackStringTo{{schema_data.name|enum_name_syntax(prop)}}:(std::string)value
{
{% for value in prop_data['enum'] %}
    if (value == std::string("{{value}}")) {
        return {{schema_data.name|enum_name_syntax(prop)}}{{value|class_name_syntax}};
    }
{% endfor %}
{% if "unknown" in prop_data['enum'][-1] %}
    return {{schema_data.name|enum_name_syntax(prop)}}Unknown;
{% else %}
    return {{schema_data.name|enum_name_syntax(prop)}}{{prop_data['enum'][0]|class_name_syntax}};
{% endif %}
}

{% endif %}
{% endfor %}
- (IoTWrappedRepresentation *)packWritable
{
    OC::OCRepresentation cppRep = [self cppPackWritable: NO];
    return [IoTWrappedRepresentation wrapperWithRepresentation:cppRep];
}

// We only pack writable properties.
- (OC::OCRepresentation)cppPackWritable:(BOOL)ignoreChangedFlags
{
    OC::OCRepresentation rep;
    if (!(ignoreChangedFlags || [self selfOrSubtreeDirty])) {
        NSLog(@"%@ not dirty - returning empty rep", NSStringFromClass([self class]));
        return rep;
    }
    NSLog(@"Packing writable for %@", NSStringFromClass([self class]));
    std::string rep_uri { "/{{schema_data.name|local_variable}}_rep_uri" };
{% if props_have_writable_object_array(schema_data.props) %}
    std::vector<OC::OCRepresentation> reps;
{% endif %}
    rep.setUri(rep_uri);

{% for prop, prop_data in schema_data.props.items() %}
{% if not 'readOnly' in prop_data or not prop_data['readOnly'] %}
{% if is_prop_an_object(prop_data) %}
    if (ignoreChangedFlags ||
        self.{{prop|variable_syntax(True)}}Changed ||
        [self.{{prop|variable_syntax(True)}} selfOrSubtreeDirty]) {
        if (self.{{prop|variable_syntax(True)}}) {
            rep[{{prop|string_constant}}] = [self.{{prop|variable_syntax(True)}} cppPackWritable: NO];
            NSLog(@"Packed {{prop|variable_syntax(True)}} as changed and not nil");
        }
{% else %}
    if (ignoreChangedFlags ||
        self.{{prop|variable_syntax(True)}}Changed) {
{% if is_prop_an_object_array(prop_data) %}
        reps.clear();
        for (id elem in self.{{prop|variable_syntax(True)}}) {
            reps.emplace_back([elem cppPackWritable: YES]);
        }
        rep[{{prop|string_constant}}] = reps;
{% else %}
{% if 'enum' in prop_data %}
        rep[{{prop|string_constant}}] = [self pack{{schema_data.name|enum_name_syntax(prop)}}ToString:self.{{prop|variable_syntax(True)}}];
{% else %}
        rep[{{prop|string_constant}}] = {{pack_objc_type(prop_data, prop)}};
{% endif %}
{% endif %}
        NSLog(@"Packed {{prop|variable_syntax(True)}} as changed");
{% endif %}
    }
{% endif %}
{% endfor %}
    [self resetChangedFlags];
    return rep;
}

- (IoTWrappedRepresentation *)packAll
{
    OC::OCRepresentation cppRep = [self cppPackAll];
    return [IoTWrappedRepresentation wrapperWithRepresentation:cppRep];
}

// We only pack writable properties.
- (OC::OCRepresentation)cppPackAll
{
    OC::OCRepresentation rep;
    std::string rep_uri { "/{{schema_data.name|local_variable}}_rep_uri" };
{% if props_have_object_array(schema_data.props) %}
    std::vector<OC::OCRepresentation> reps;
{% endif %}
    rep.setUri(rep_uri);

{% for prop, prop_data in schema_data.props.items() %}
{% if is_prop_an_object(prop_data) %}
    if (self.{{prop|variable_syntax(True)}}) {
        rep[{{prop|string_constant}}] = [self.{{prop|variable_syntax(True)}} cppPackAll];
    }
{% elif is_prop_an_object_array(prop_data) %}
    reps.clear();
    for (id elem in self.{{prop|variable_syntax(True)}}) {
        reps.emplace_back([elem cppPackAll]);
    }
    rep[{{prop|string_constant}}] = reps;
{% else %}
{% if 'enum' in prop_data %}
    rep[{{prop|string_constant}}] = [self pack{{schema_data.name|enum_name_syntax(prop)}}ToString:self.{{prop|variable_syntax(True)}}];
{% else %}
    rep[{{prop|string_constant}}] = {{pack_objc_type(prop_data, prop)}};
{% endif %}
{% endif %}
{% endfor %}
    return rep;
}

- (BOOL)unpack:(IoTWrappedRepresentation *)packed
{
    return [self cppUnpack:[packed representation]];
}

- (BOOL)cppUnpack:(OC::OCRepresentation)rep
{
{% if props_have_object_array(schema_data.props) %}
    std::vector<OC::OCRepresentation> reps;
{% endif %}
    BOOL didUnpack = NO;

{% for prop, prop_data in schema_data.props.items() %}
    if (rep.hasAttribute({{prop|string_constant}})) {
        didUnpack = YES;
{% if is_prop_an_object(prop_data) %}
        if (!self.{{prop|variable_syntax(True)}}) {
            self.{{prop|variable_syntax(True)}} = [[{{prop_data | convert_to_objc_alloc_type(prop)}} alloc] initWithStateSyncQueue:self.syncQueue];
        }
        [self.{{prop|variable_syntax(True)}} cppUnpack:rep[{{prop|string_constant}}]];
{% elif is_prop_an_object_array(prop_data) %}
        reps = rep[{{prop|string_constant}}];
        NSMutableArray *temp = [[NSMutableArray alloc] init];
        for (auto& elem_rep: reps) {
            {{get_objc_object_name_from_obj_array(prop_data)}} obj = [[{{get_objc_alloc_object_name_from_obj_array(prop_data)}} alloc] initWithStateSyncQueue:self.syncQueue];
            [obj cppUnpack:elem_rep];
            [temp addObject:obj];
        }
        self.{{prop|variable_syntax(True)}} = temp;
{% else %}
{% if 'enum' in prop_data %}
        self.{{prop|variable_syntax(True)}} = [self unpackStringTo{{schema_data.name|enum_name_syntax(prop)}}:rep[{{prop|string_constant}}]];
{% else %}
        self.{{prop|variable_syntax(True)}} = {{unpack_objc_type(prop_data, prop)}};
{% endif %}
{% endif %}
    }
{% endfor %}
    [self resetChangedFlags];
    return didUnpack; 
}

@end

