//
//  PicoXMLWriter.m
//  pico
//
//  Created by bulldog on 13-2-26.
//  Copyright (c) 2013 LeanSoft Technology. All rights reserved.
//

#import "PicoXMLWriter.h"
#import "PicoConverter.h"
#import "XMLWriter.h"
#import "PicoConfig.h"
#import "PicoBindingSchema.h"
#import "PicoClassSchema.h"
#import "PicoPropertySchema.h"
#import "PicoConstants.h"
#import "PicoXMLElement.h"
#import "PicoBindable.h"

@implementation PicoXMLWriter

@synthesize config = _config;

- (instancetype)init {
    return [self initWithConfig:[[PicoConfig alloc] init]];
}

- (instancetype)initWithConfig:(PicoConfig *)config {
    self = [super init];
    if (self) {
        self.config = config;
    }
    
    return self;
}

-(NSData *)toData:(id)obj {
    
    NSString *xmlString = [self toString:obj] ;
    
    NSData *data = [xmlString dataUsingEncoding: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)self.config.encoding)) allowLossyConversion:NO];
    
    return data;
    
}

-(NSString *)toString:(id)obj {
    
    
    if (obj == nil) {
		@throw [NSException exceptionWithName:@"WriterException" reason:@"Can't write nil object." userInfo:nil];
	}
    
    XMLWriter *xmlWriter = [[XMLWriter alloc] init];
    _autoPrefixCount = 0;
    
    [xmlWriter writeStartDocumentWithEncodingAndVersion:self.config.encoding version:@"1.0"];
    
    
    PicoBindingSchema *bindingSchema = [PicoBindingSchema fromObject:obj];
    PicoClassSchema *classSchema = [bindingSchema classSchema];
    NSString *xmlName = classSchema.xmlName;
    NSString *namespace = classSchema.nsURI;
    if (!xmlName) { // workaround, in case xml name is not provided in class schema, use class name instead
        xmlName = bindingSchema.className;
    }
    
    if ([namespace length] != 0) {
        [xmlWriter setDefaultNamespace:namespace]; // bind to default namespace
    }
    
    [xmlWriter writeStartElementWithNamespace:namespace localName:xmlName];
    
    [self writeObject:xmlWriter source:obj];
    
    [xmlWriter writeEndElementWithNamespace:namespace localName:xmlName];
    
    [xmlWriter writeEndDocument];
    
    NSString *xmlString = [xmlWriter toString];
    xmlString = [NSMutableString stringWithString:xmlString];

    
    return xmlString;
    
}

-(void)writeObject:(XMLWriter *)xmlWriter source:(id)source {
    PicoBindingSchema *bindingSchema = [PicoBindingSchema fromObject:source];
    NSString *namespace = nil;
    PicoClassSchema *classSchema = [bindingSchema classSchema];
    if (classSchema) {
        namespace = classSchema.nsURI;
    }
    
    [self writeAttributes:xmlWriter source:source schema:bindingSchema];
    
    [self writeValue:xmlWriter source:source schema:bindingSchema];
    
    // automatic prefixing
    if (namespace && ![xmlWriter getPrefix:namespace]) {
        NSString *prefix = [NSString stringWithFormat:@"ns%d", _autoPrefixCount++];
        [xmlWriter setPrefix:prefix namespaceURI:namespace];
    }
    [self writeElements:xmlWriter source:source schema:bindingSchema namespace:namespace];
    
    [self writeAnyElements:xmlWriter source:source schema:bindingSchema];
}

-(void)writeAttributes:(XMLWriter *)xmlWriter source:(id)source schema:(PicoBindingSchema *)bindingSchema {
    NSDictionary *map = bindingSchema.property2AttributeSchemaMapping;
    if (map.count > 0) {
        for(NSString *propertyName in map) {
            PicoPropertySchema *ps = map[propertyName];
            id propertyValue = [source valueForKey:propertyName];
            if (propertyValue) {
                NSString *xmlString = [PicoConverter write:propertyValue withType:ps.propertyType config:self.config];
                if (xmlString.length > 0) {
                    [xmlWriter writeAttribute:ps.xmlName value:xmlString];
                }
            }
        }
    }
}


-(void)writeValue:(XMLWriter *)xmlWriter source:(id)source schema:(PicoBindingSchema *)bindingSchema {
    PicoPropertySchema *valuePs = bindingSchema.valueSchema;
    if (valuePs) {
        id propertyValue = [source valueForKey:valuePs.propertyName];
        NSString *xmlString = [PicoConverter write:propertyValue withType:valuePs.propertyType config:self.config];
        if (xmlString.length > 0) {
            [xmlWriter writeCharacters:xmlString];
        }
    }
}

-(void)writeAnyElements:(XMLWriter *)xmlWriter source:(id)source schema:(PicoBindingSchema *)bindingSchema {
    PicoPropertySchema *anyPs = bindingSchema.anyElementSchema;
    if(anyPs) {
        NSArray *anyArray = [source valueForKey: anyPs.propertyName];
        if (anyArray) {
            for(id entry in anyArray) {
                if (entry) {
                    if ([entry isMemberOfClass:[PicoXMLElement class]]) {
                        [self writePicoXMLElement:xmlWriter xmlElement:entry];
                    } else if ([entry conformsToProtocol:@protocol(PicoBindable)]) {
                        [self writePicoObject:xmlWriter source:entry];
                    }
                }
            }
        }
    }
}

-(void)writePicoXMLElement:(XMLWriter *)xmlWriter xmlElement:(PicoXMLElement *)element {
    if (!element) return; // to be cautious
    if (element.name) {
        NSString *namespace = element.nsUri;
        if (namespace.length == 0) {
            namespace = nil;
        }
        // automatic prefixing
        if (namespace && ![xmlWriter getPrefix:namespace]) {
            NSString *prefix = [NSString stringWithFormat:@"ns%d", _autoPrefixCount++];
            [xmlWriter setPrefix:prefix namespaceURI:namespace];
        }
        [xmlWriter writeStartElementWithNamespace:namespace localName:element.name];
        if (element.attributes) { // write attributes
            for(NSString *key in element.attributes) {
                NSString *value = [element.attributes valueForKey:key];
                [xmlWriter writeAttribute:key value:value];
            }
        }
        if (element.value) { // write element text
            [xmlWriter writeCharacters:element.value];
        }
        if (element.children) { // write element children
            for(PicoXMLElement *child in element.children) {
                [self writePicoXMLElement:xmlWriter xmlElement:child];
            }
        }
        [xmlWriter writeEndElementWithNamespace:namespace localName:element.name];
    }
}

-(void)writePicoObject:(XMLWriter *)xmlWriter source:(id)source {
    if (!source) return; // to be cautious
    PicoBindingSchema *bindingSchema = [PicoBindingSchema fromObject:source];
    PicoClassSchema *classSchema = [bindingSchema classSchema];
    NSString *xmlName = classSchema.xmlName;
    NSString *namespace = classSchema.nsURI;
    if (!xmlName) { // workaround, in case xml name is not provided in class schema, use class name instead
        xmlName = bindingSchema.className;
    }
    
    // automatic prefixing
    if (namespace && ![xmlWriter getPrefix:namespace]) {
        NSString *prefix = [NSString stringWithFormat:@"ns%d", _autoPrefixCount++];
        [xmlWriter setPrefix:prefix namespaceURI:namespace];
    }
    [xmlWriter writeStartElementWithNamespace:namespace localName:xmlName];
    [self writeObject:xmlWriter source:source];
    [xmlWriter writeEndElementWithNamespace:namespace localName:xmlName];
}


-(void)writeElements:(XMLWriter *)xmlWriter source:(id)source schema:(PicoBindingSchema *)bindingSchema namespace:(NSString *)nsURI  {
    NSDictionary *map = bindingSchema.property2ElementSchemaMapping;
    if (map.count > 0) {
        for(NSString *propertyName in map) {
            PicoPropertySchema *ps = map[propertyName];
            id propertyValue = [source valueForKey:propertyName];
            if (propertyValue) {
                if ([ps.propertyKind isEqualToString:PICO_KIND_ELEMENT]) {
                    [self writeElement:xmlWriter source:propertyValue schema:ps namespace:nsURI];
                }
                // array elements
                else if ([ps.propertyKind isEqualToString:PICO_KIND_ELEMENT_ARRAY]) {
                    NSArray *array = propertyValue;
                    for(id entry in array) {
                        if (entry) {
                            [self writeElement:xmlWriter source:entry schema:ps namespace:nsURI];
                        }
                    }
                }
            }
        }
    }
}

-(void)writeElement:(XMLWriter *)xmlWriter source:(id)source schema:(PicoPropertySchema *)propertySchema namespace:(NSString *)nsURI {
    if (!source) return;
    
    if ([PicoConverter isPrimitive:propertySchema.propertyType]) {
        NSString *xmlString = [PicoConverter write:source withType:propertySchema.propertyType config:self.config];
        if (xmlString.length > 0) {
            [xmlWriter writeStartElementWithNamespace:nsURI localName:propertySchema.xmlName];
            [xmlWriter writeCharacters:xmlString];
            [xmlWriter writeEndElementWithNamespace:nsURI localName:propertySchema.xmlName];
        }
        return;
    }
    
    if ([propertySchema.propertyType isEqualToString:PICO_TYPE_OBJECT]) {
        [xmlWriter writeStartElementWithNamespace:nsURI localName:propertySchema.xmlName];
        [self writeObject:xmlWriter source:source];
        [xmlWriter writeEndElementWithNamespace:nsURI localName:propertySchema.xmlName];
    }
    
}




@end
