package liquibase.serializer.core.string;

import liquibase.change.Change;
import liquibase.change.ChangeProperty;
import liquibase.change.ColumnConfig;
import liquibase.change.ConstraintsConfig;
import liquibase.change.custom.CustomChange;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.serializer.ChangeLogSerializer;
import liquibase.sql.visitor.SqlVisitor;
import liquibase.util.StringUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.io.OutputStream;
import java.io.IOException;

public class StringChangeLogSerializer implements ChangeLogSerializer {

    private static final int INDENT_LENGTH = 4;

    public String[] getValidFileExtensions() {
        return new String[] {"txt"};
    }

    public String serialize(DatabaseChangeLog databaseChangeLog) {
        return null; //todo
    }
    
    public String serialize(Change change) {
        return change.getChangeMetaData().getName() + ":" + serializeObject(change, 1);
    }

    public String serialize(SqlVisitor visitor) {
        return visitor.getName() + ":" + serializeObject(visitor, 1);
    }

    private String serializeObject(Object objectToSerialize, int indent) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("[");

            SortedSet<String> values = new TreeSet<String>();
            Class<? extends Object> classToCheck = objectToSerialize.getClass();
            while (!classToCheck.equals(Object.class)) {
                for (Field field : classToCheck.getDeclaredFields()) {
                    field.setAccessible(true);
                    ChangeProperty changePropertyAnnotation = field.getAnnotation(ChangeProperty.class);
                    if (changePropertyAnnotation != null && !changePropertyAnnotation.includeInSerialization()) {
                        continue;
                    }
                    if (field.getName().equals("serialVersionUID")) {
                        continue;
                    }
                    if (field.getName().equals("$VRc")) { //from emma
                        continue;
                    }
                    if (field.getName().equals("serialVersionUID")) {
                        continue;
                    }

                    String propertyName = field.getName();

                    Object value = field.get(objectToSerialize);
                    if (value instanceof ColumnConfig) {
                        values.add(indent(indent) + serializeColumnConfig((ColumnConfig) field.get(objectToSerialize), indent + 1));
                    } else if (value instanceof ConstraintsConfig) {
                        values.add(indent(indent) + serializeConstraintsConfig((ConstraintsConfig) field.get(objectToSerialize), indent + 1));
                    } else if (value instanceof CustomChange) {
                        values.add(indent(indent) + serializeCustomChange((CustomChange) field.get(objectToSerialize), indent + 1));
                    } else {
                        if (value != null) {
                            if (value instanceof Map) {
                                values.add(indent(indent) + propertyName + "=" + serializeObject((Map) value, indent + 1));
                            } else if (value instanceof Collection) {
                                values.add(indent(indent) + propertyName + "=" + serializeObject((Collection) value, indent + 1));
                            } else if (value instanceof Object[]) {
                                values.add(indent(indent) + propertyName + "=" + serializeObject((Object[]) value, indent + 1));
                            } else {
                                values.add(indent(indent) + propertyName + "=\"" + value.toString() + "\"");
                            }
                        }
                    }
                }
                classToCheck = classToCheck.getSuperclass();
            }

            if (values.size() > 0) {
                buffer.append("\n");
                buffer.append(StringUtils.join(values, "\n"));
                buffer.append("\n");
            }
            buffer.append(indent(indent - 1)).append("]");
            return buffer.toString().replace("\r\n", "\n").replace("\r", "\n"); //standardize all newline chars

        } catch (Exception e) {
            throw new UnexpectedLiquibaseException(e);
        }

    }

    private String indent(int indent) {
        return StringUtils.repeat(" ", INDENT_LENGTH * indent);
    }

    private String serializeObject(Object[] collection, int indent) {
        if (collection.length == 0) {
            return "[]";
        }

        String returnString = "[\n";
        for (Object object : collection) {
            if (object instanceof ColumnConfig) {
                returnString += indent(indent) + serializeColumnConfig((ColumnConfig) object, indent + 1) + ",\n";
            } else {
                returnString += indent(indent) + object.toString()+ ",\n";
            }
        }
        returnString = returnString.replaceFirst(",$", "");
        returnString += indent(indent - 1) + "]";

        return returnString;

    }

    private String serializeObject(Collection collection, int indent) {
        if (collection.size() == 0) {
            return "[]";
        }

        String returnString = "[\n";
        for (Object object : collection) {
            if (object instanceof ColumnConfig) {
                returnString += indent(indent) + serializeColumnConfig((ColumnConfig) object, indent + 1) + ",\n";
            } else {
                returnString += indent(indent) + object.toString()+ ",\n";
            }
        }
        returnString = returnString.replaceFirst(",$", "");
        returnString += indent(indent - 1) + "]";

        return returnString;

    }

    private String serializeObject(Map collection, int indent) {
        if (collection.size() == 0) {
            return "[]";
        }

        String returnString = "{\n";
        for (Object key : new TreeSet(collection.keySet())) {
            returnString += indent(indent) +  key.toString() +"=\""+collection.get(key)+ "\",\n";
        }
        returnString = returnString.replaceFirst(",$", "");
        returnString += indent(indent - 1) + "}";

        return returnString;

    }

    public String serialize(ColumnConfig columnConfig) {
        return null;
    }

    private String serializeColumnConfig(ColumnConfig columnConfig, int indent) {
        return "column:" + serializeObject(columnConfig, indent);
    }

    private String serializeConstraintsConfig(ConstraintsConfig constraintsConfig, int indent) {
        return "constraints:" + serializeObject(constraintsConfig, indent);
    }

    private String serializeCustomChange(CustomChange customChange, int indent) {
        return "customChange:" + serializeObject(customChange, indent);
    }

    public String serialize(ChangeSet changeSet) {
        return null;
    }

	public void write(List<ChangeSet> changeSets, OutputStream out)
			throws IOException {
		
	}
}

@@ -677,24 +677,90 @@
 l.TreeSet;%0D%0A
+import java.util.regex.Matcher;%0D%0Aimport java.util.regex.Pattern;%0D%0A
 import java.
@@ -3971,32 +3971,676 @@
  indent + 1));%0D%0A
+                            %7D%0D%0A                            else if (value instanceof Number) %7B%0D%0A                                String valueStr = value.toString();%0D%0A                                final Pattern valueStrPattern = Pattern.compile(%22(%5C%5Cd*%5C%5C.%5C%5Cd*?)0+%22);%0D%0A                                final Matcher valueStrMatcher = valueStrPattern.matcher(valueStr);%0D%0A                                if (valueStrMatcher.matches()) %7B%0D%0A                                    valueStr = valueStrMatcher.group(1);%0D%0A                                %7D%0D%0A                                values.add(indent(indent) + propertyName + %22=%5C%22%22 + valueStr + %22%5C%22%22);%0D%0A
                 

DBV-89 CORE-1255 Remove trailing zero from numeric output of decimal values