package com.urbancode.air.plugin.scm.changelog

import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.util.TimeZone;

public class ChangeSetXMLHelper {

    //**************************************************************************
    // CLASS
    //**************************************************************************

    static private final TimeZone GMT = TimeZone.getTimeZone("GMT")
    static private final String AIR_DATE_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.S Z";

    //**************************************************************************
    // INSTANCE
    //**************************************************************************

    public def toXML(Collection<ChangeSet> changeSets) {
        final DateFormat airDateFormat = getAirDateFormat();

        def xmlBuilder = new groovy.xml.StreamingMarkupBuilder();
        xmlBuilder.encoding = "UTF-8"
        Writable writable = xmlBuilder.bind{
            mkp.xmlDeclaration()
            'change-log'{
                changeSets.each{ changeSet ->
                    'change-set'(){
                        'id'(stripInvalidXMLChars(changeSet.id))
                        'user'(stripInvalidXMLChars(changeSet.user))
                        'date'(airDateFormat.format(changeSet.date))
                        if (changeSet.message) {
                            'comment'(stripInvalidXMLChars(changeSet.message))
                        }
                        'repository-type'(stripInvalidXMLChars(com.urbancode.air.plugin.scm.SCMStep.REPO_TYPE))
                        'repository-id'(System.getenv()['REPOSITORY_ID'])
                        'source-config-id'(System.getenv()['SOURCE_CONFIG_ID'])
                        'file-set'{
                            changeSet.fileSet.each{change ->
                                'file'(
                                    'change-type':stripInvalidXMLChars(change.type), 
                                    'revision-number':stripInvalidXMLChars(change.revision), 
                                    stripInvalidXMLChars(change.path))
                            }
                        }
    
                        if (changeSet.properties) {
                            'properties'{
                                changeSet.properties.each{ prop ->
                                    'property'{
                                        'name'(stripInvalidXMLChars(prop.key))
                                        if (prop.value) {
                                            'value'(stripInvalidXMLChars(prop.value))
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return writable;
    }
    
    /**
    * Strip any invalid xml characters (such as backspace) from the value.
    * @param value the value to strip (can be null)
    * @return the sanitized value (null if and only if value was null)
    */
   protected String stripInvalidXMLChars(String value) {
       // legal xml chars: #x9 | #xA | #xD | #x20-#xD7FF | #xE000-#xFFFD | #x10000-#x10FFFF
       def legalChars = ['\\u0009', '\\u000A', '\\u000D', '\\u0020-\\uD7FF', '\\uE000-\\uFFFD', '\\u10000-\\u10FFFF']
       def illegalChars = "[^${legalChars.join()}]"
       return !value ? value : value.replaceAll(illegalChars, '?')
   }

    /**
     * Create a new AirDate format instance based upon {@link #AIR_DATE_FORMAT_STRING}
     * @return a new {@link DateFormat}
     */
    protected DateFormat getAirDateFormat() {
        DateFormat airDateFormat = new SimpleDateFormat(AIR_DATE_FORMAT_STRING);
        airDateFormat.timeZone = GMT;
        return airDateFormat;
    }
}
