/*---------------------------------------------------------------------------*/
/* XmlIo.m                                                                   */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#import <Foundation/Foundation.h>

#import "platform/platform.h"
#import "platform/IosNamespace.h"
#import "XmlIo.h"

#define XML_TYPE_TAG            0
#define XML_TYPE_ATTRIB         1
#define XML_TYPE_ATTRIB_VALUE   2
#define XML_TYPE_CONTENT        3
#define XML_TYPE_HREF           5
#define XML_TYPE_ANY            0xff

#define XML_ERROR_INCOMPLETE    1
#define XML_ERROR_FATAL         2

#define XML_FLAG_COMPLETE       1

#define XML_MAX_TAG             3072
#define XML_MAX_ID              32

@interface XmlContent : NSObject

@property (nonatomic) int desc;
@property (nonatomic) NSString *info;

@property (copy) int (^get_type)(void);
@property (copy) int (^get_count)(void);
@property (copy) int (^get_href)(void);
@property (copy) int (^get_flags)(void);
@property (copy) int (^get_base)(void);
@property (copy) void (^set_type)(int type);
@property (copy) void (^set_count)(int count);
@property (copy) void (^set_href)(int href);
@property (copy) void (^set_flags)(int flags);
@property (copy) void (^set_base)(int base);
@property (copy) void (^set_desc)(int type, int count, int href, int flags, int base);

+ (id) xmlContent;

- (id) init;

@end
@implementation XmlContent

+ (id) xmlContent
{
    return [[self alloc] init];
}

- (id) init
{
    self = [super init];
    if (self) {
        XmlContent *__weak weakSelf = self;
        _get_type = ^ int ()  { return(weakSelf.desc & 0x00000007); };
        _get_count = ^ int () { return((weakSelf.desc >> 3) & 0x00001fff); };
        _get_href = ^ int ()  { return((weakSelf.desc >> 16) & 0x00000007); };
        _get_flags = ^ int () { return((weakSelf.desc >> 19) & 0x00000001); };
        _get_base = ^ int ()  { return((weakSelf.desc >> 20) & 0x00000fff); };
        _set_type = ^ void (int type)   { weakSelf.desc = (weakSelf.desc & ~0x00000007) | type; };
        _set_count = ^ void (int count) { weakSelf.desc = (weakSelf.desc & ~0x0000fff8) | (count << 3); };
        _set_href = ^ void (int href)   { weakSelf.desc = (weakSelf.desc & ~0x00070000) | (href << 16); };
        _set_flags = ^ void (int flags) { weakSelf.desc = (weakSelf.desc & ~0x00080000) | (flags << 19); };
        _set_base = ^ void (int base)   { weakSelf.desc = (weakSelf.desc & ~0xfff00000) | (base << 20); };
        _set_desc = ^ void (int type, int count, int href, int flags, int base) {
            weakSelf.desc = type | (count << 3) | (href << 16) | (flags << 19) | (base << 20);
		};

    }
    return self;
}

@end
@interface XmlContentArray : NSObject

@property (nonatomic) NSMutableArray *array_;

@property (copy) XmlContent *(^get)(int index);
@property (copy) void (^set)(int index, XmlContent *str);
@property (copy) int (^size)(void);
@property (copy) void (^add)(XmlContent *str);
@property (copy) void (^clear)(void);

+ (id) array;

- (id) init;
- (XmlContent *) objectAtIndexedSubscript:(int)index;
- (void) setObject:(XmlContent *)content atIndexedSubscript:(int)index;

@end
@implementation XmlContentArray

+ (id) array
{
    return [[self alloc] init];
}

- (id) init
{
    self = [super init];
    if (self) {
        XmlContentArray *__weak weakSelf = self;
        _get = ^ XmlContent * (int index) { XmlContent *content = (XmlContent *)(weakSelf.array_[index]); return ((((NSNull *) content) == [NSNull null]) ? nil : content); };
        _set = ^ void (int index, XmlContent *content) { [weakSelf.array_ replaceObjectAtIndex:index withObject:(content == nil) ? [NSNull null] : content]; };
        _size = ^ int () { return (int)(weakSelf.array_.count); };
        _add = ^ void (XmlContent *content) { [weakSelf.array_ addObject:(content == nil) ? [NSNull null] : content]; };
        _clear = ^ void () { return [weakSelf.array_ removeAllObjects]; };

        _array_ = [NSMutableArray array];
    }
    return self;
}

- (XmlContent *) objectAtIndexedSubscript:(int)index
{
    XmlContent *content = self.array_[index];
    return ((((NSNull *) content) == [NSNull null]) ? nil : content);
}

- (void) setObject:(XmlContent *)content atIndexedSubscript:(int)index
{
    self.array_[index] = (content == nil) ? [NSNull null] : content;
}

@end
@interface XmlIo()

@property (nonatomic) int error;
@property (nonatomic) int index;
@property (nonatomic) XmlContentArray *content;

- (void) reset_;
- (int) add_:(int)type base:(int)base s:(NSString *)s;
- (void) set_complete_:(int)base;
- (int) get_base_:(int)base;
- (int) add_tag_:(int)base name:(NSString *)name;
- (void) add_attrib_:(int)base name:(NSString *)name value:(NSString *)value;
- (void) add_attrib_bool_:(int)base name:(NSString *)name b:(bool)b;
- (void) add_attrib_int_:(int)base name:(NSString *)name c:(int)c;
- (void) add_attrib_unsigned_:(int)base name:(NSString *)name c:(int)c;
- (void) add_attrib_string_:(int)base name:(NSString *)name str:(NSString *)str;
- (void) add_content_:(int)base s:(NSString *)s;
- (void) encode_to_string_:(int)current str:(NSMutableString *)str;
- (void) encode_:(NSMutableString *)str;

+ (id) xmlIo;

- (id) init;

@end
@implementation XmlIo

+ (id) xmlIo
{
    return [[self alloc] init];
}

- (id) init
{
    self = [super init];
    if (self) {
        XmlIo *__weak weakSelf = self;
        _reset = ^ void () { [weakSelf reset_]; };
        _add = ^ int (int type, int base, NSString *s) { return [weakSelf add_:type base:base s:s]; };
        _set_complete = ^ void (int base) { [weakSelf set_complete_:base]; };
        _get_base = ^ int (int base) { return [weakSelf get_base_:base]; };
        _add_tag = ^ int (int base, NSString *name) { return [weakSelf add_tag_:base name:name]; };
        _add_attrib = ^ void (int base, NSString *name, NSString *value) { [weakSelf add_attrib_:base name:name value:value]; };
        _add_attrib_bool = ^ void (int base, NSString *name, bool b) { [weakSelf add_attrib_bool_:base name:name b:b]; };
        _add_attrib_int = ^ void (int base, NSString *name, int c) { [weakSelf add_attrib_int_:base name:name c:c]; };
        _add_attrib_unsigned = ^ void (int base, NSString *name, int c) { [weakSelf add_attrib_unsigned_:base name:name c:c]; };
        _add_attrib_string = ^ void (int base, NSString *name, NSString *str) { [weakSelf add_attrib_string_:base name:name str:str]; };
        _add_content = ^ void (int base, NSString *s) { [weakSelf add_content_:base s:s]; };
        _encode_to_string = ^ void (int current, NSMutableString *str) { [weakSelf encode_to_string_:current str:str]; };
        _encode = ^ void (NSMutableString *str) { [weakSelf encode_:str]; };

        self.error = 0;
        self.index = 0;
        self.content = [XmlContentArray array];
    }
    return self;
}

- (void) reset_
{
    self.error = 0;
    self.index = 0;
    self.content = [XmlContentArray array];
}

- (int) add_:(int)type base:(int)base s:(NSString *)s
{
    int current;
    if(base >= self.index) {
        current = 0;
    }
    else {
        current = base + self.content[base].get_count() + 1;
    }
    if(current != self.index) return 0;

    self.content[current] = [XmlContent xmlContent];
    self.content[current].set_desc(type, (type == XML_TYPE_TAG) ? 0 : s.length, 0, 0, base);
    self.content[current].info = s;

    while(base < self.index) {
        self.content[base].set_count(self.content[base].get_count() + 1);
        base = self.content[base].get_base();
    }

    (self.index)++;
    return current;
}

- (void) set_complete_:(int)base
{
    self.content[base].set_flags(XML_FLAG_COMPLETE);
}

- (int) get_base_:(int)base
{
    return self.content[base].get_base();
}

- (int) add_tag_:(int)base name:(NSString *)name
{
    return self.add(XML_TYPE_TAG, base, name);
}

- (void) add_attrib_:(int)base name:(NSString *)name value:(NSString *)value
{
    self.add(XML_TYPE_ATTRIB, base, name);
    self.add(XML_TYPE_ATTRIB_VALUE, base, value);
}

- (void) add_attrib_bool_:(int)base name:(NSString *)name b:(bool)b
{
    self.add(XML_TYPE_ATTRIB, base, name);
    self.add(XML_TYPE_ATTRIB_VALUE, base, b ? @"true" : @"false");
}

- (void) add_attrib_int_:(int)base name:(NSString *)name c:(int)c
{
    self.add(XML_TYPE_ATTRIB, base, name);
    self.add(XML_TYPE_ATTRIB_VALUE, base, [NSString stringWithFormat:@"%d", c]);
}

- (void) add_attrib_unsigned_:(int)base name:(NSString *)name c:(int)c
{
    self.add(XML_TYPE_ATTRIB, base, name);
    self.add(XML_TYPE_ATTRIB_VALUE, base, [NSString stringWithFormat:@"%u", c]);
}

- (void) add_attrib_string_:(int)base name:(NSString *)name str:(NSString *)str
{
    self.add(XML_TYPE_ATTRIB, base, name);
    self.add(XML_TYPE_ATTRIB_VALUE, base, str);
}

- (void) add_content_:(int)base s:(NSString *)s
{
    self.add(XML_TYPE_CONTENT, base, s);
}

- (void) encode_to_string_:(int)current str:(NSMutableString *)str
{
    NSRange r;
    
    r.length = 1;
    switch(self.content[current].get_type()) {
    case XML_TYPE_TAG: {
        int i;
        bool empty = true;

        [str appendString:@"<"];
        [str appendString:self.content[current].info];
        for(i = 0; i < self.content[current].get_count(); ) {
            if(self.content[current+i+1].get_type() == XML_TYPE_ATTRIB) {
                self.encode_to_string(current+i+1, str);
                i++;
            }
            else if(self.content[current+i+1].get_type() == XML_TYPE_CONTENT) {
                i++;
                empty = false;
            }
            else if(self.content[current+i+1].get_type() == XML_TYPE_TAG) {
                i += self.content[current+i+1].get_count() + 1;
                empty = false;
            }
            else {
                i++;
            }
        }
        if(empty) {
            [str appendString:@"/>"];
        }
        else {
            [str appendString:@">"];
            for(i = 0; i < self.content[current].get_count(); ) {
                if(self.content[current+i+1].get_type() == XML_TYPE_TAG) {
                    self.encode_to_string(current+i+1, str);
                    i += self.content[current+i+1].get_count() + 1;
                }
                else if(self.content[current+i+1].get_type() == XML_TYPE_CONTENT) {
                    self.encode_to_string(current+i+1, str);
                    i++;
                }
                else {
                    i++;
                }
            }
            [str appendString:@"</"];
            [str appendString:self.content[current].info];
            [str appendString:@">"];
        }
        break;
        }
    case XML_TYPE_ATTRIB:
        if(self.content[current].get_count() != 0) {
            [str appendString:@" "];
            [str appendString:self.content[current].info];
            [str appendString:@"="];
            self.encode_to_string(current+1, str);
        }
        break;
    case XML_TYPE_ATTRIB_VALUE: {
        [str appendString:@"\""];
        for(int i = 0; i < self.content[current].get_count(); i++) {
            switch([self.content[current].info characterAtIndex:i]) {
            case '<': [str appendString:@"&lt;"]; break;
            case '>': [str appendString:@"&gt;"]; break;
            case '"': [str appendString:@"&quot;"]; break;
            case '\'': [str appendString:@"&apos;"]; break;
            case '&': [str appendString:@"&amp;"]; break;
            case '\r': [str appendString:@"&#xD;"]; break;
            case '\n': [str appendString:@"&#xA;"]; break;
            default: r.location = i; [str appendString:[self.content[current].info substringWithRange:r]]; break;
            }
        }
        [str appendString:@"\""];
        break;
        }
    case XML_TYPE_CONTENT:
        [str appendString:self.content[current].info];
        break;
    }
}

- (void) encode_:(NSMutableString *)str
{
    if (self.index != 0) {
        self.encode_to_string(0, str);
    }
}

+ (unsigned) xml_to_str:(char *)buffer in:(const char *)in in_len:(unsigned)in_len
{
    unsigned length = 0;
    char c='&';
    unsigned start=0;
    for(int i=0; i < in_len && in[i]; ) {
        for( ; i < in_len && in[i] && in[i]!=c; i++) buffer[length++] = in[i];
        if(i < in_len && in[i]) {
            if(c=='&') {
                c=';';
                start = length;
            }
            else {
                c='&';
                buffer[length]=0;
                if(buffer[start+1]=='#') {
                    unsigned x;
                    if(buffer[start+2]=='x') {
                        x = strtoul(&buffer[start+3],0,16);
                    }
                    else {
                        x = strtoul(&buffer[start+2],0,10);
                    }
                    if(x<128) {
                        buffer[start] = (char) x;
                    }
                    else if(x<2048) {
                        buffer[start++] = (char)((x>>6) | 0xc0);
                        buffer[start] = (char)((x & 0x3f) | 0x80);
                    }
                    else if(x<65536) {
                        buffer[start++] = (char)((x>>12) | 0xe0);
                        buffer[start++] = (char)(((x>>6) & 0x3f) | 0x80);
                        buffer[start] = (char)((x & 0x3f) | 0x80);
                    }
                    else {
                        buffer[start] = '.';
                    }
                }
                else if(!strcmp(&buffer[start],"&lt")) {
                    buffer[start] = '<';
                }
                else if(!strcmp(&buffer[start],"&gt")) {
                    buffer[start] = '>';
                }
                else if(!strcmp(&buffer[start],"&amp")) {
                    buffer[start] = '&';
                }
                else if(!strcmp(&buffer[start],"&quot")) {
                    buffer[start] = '"';
                }
                else if(!strcmp(&buffer[start],"&apos")) {
                    buffer[start] = '\'';
                }
                else if(!strcmp(&buffer[start],"&nbsp")) {
                    buffer[start++] = (char)0xc2;
                    buffer[start] = (char)0xa0;
                }
                length = start+1;
                i++;
            }
        }
    }
    buffer[length] = 0;
    return length;
}

@end
