package sharin.sql.formatter;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import sharin.util.PropertyUtils;

class BasicFormatCommand {

    private enum Type {
        NOOP, PRINTE, PRINTD, BEGIN, END, PREPEND;

        private static final Map<String, Type> nameMap = new HashMap<String, Type>();

        static {

            for (Type type : values()) {
                nameMap.put(type.name().toLowerCase(), type);
            }
        }

        public static Type find(String name) {
            return nameMap.get(name);
        }
    }

    private static final Pattern linePattern = Pattern
            .compile("^(.*)--\\s*([$&#]?)(.*)$");

    private final Type type;

    private final String body;

    private final String expr;

    private final String escapeChars;

    public BasicFormatCommand(String line, String escapeChars) {
        Type type = Type.NOOP;
        String body = null;
        String expr = null;
        Matcher matcher = linePattern.matcher(line);

        if (matcher.matches()) {
            body = matcher.group(1).trim();
            String group2 = matcher.group(2);
            String group3 = matcher.group(3);

            if (group2 != null && group2.length() > 0) {
                char ch = group2.charAt(0);

                if (ch == '$') {
                    type = Type.PRINTE;
                    expr = group3.trim();

                } else if (ch == '&') {
                    type = Type.PRINTD;
                    expr = group3.trim();

                } else if (ch == '#') {
                    String[] typeExpr = group3.split("\\s", 2);
                    Type t = Type.find(typeExpr[0]);

                    if (t != null) {
                        type = t;

                        if (typeExpr.length > 1) {
                            expr = typeExpr[1];
                        }
                    }
                }
            }

        } else {
            body = line.trim();
        }

        this.type = type;
        this.body = body;
        this.expr = expr;
        this.escapeChars = escapeChars;
    }

    public void execute(Object context, BasicResultBuffer resultBuffer) {
        String line = null;
        Object value = null;

        switch (type) {
        case PRINTE:
            value = PropertyUtils.getNestedPropertyValue(context, expr);

            if (value != null) {
                resultBuffer.flush();
            }

            if (value == null
                    && !PropertyUtils.hasNestedProperty(context, expr)) {
                line = body;

            } else {
                line = convertValue(value);
            }

            break;

        case PRINTD:
            Object token = PropertyUtils.getNestedPropertyValue(context, expr);

            if (token != null) {
                resultBuffer.flush();
            }

            if (token == null
                    && !PropertyUtils.hasNestedProperty(context, expr)) {
                line = body;

            } else {

                if (token instanceof Object[]) {
                    Object[] tokens = (Object[]) token;
                    Object[] values = new Object[tokens.length];

                    for (int i = 0; i < tokens.length; i++) {
                        values[i] = new Direct(tokens[i]);
                    }

                    value = values;

                } else {
                    value = new Direct(token);
                }

                line = convertValue(value);
            }

            break;

        case BEGIN:
            resultBuffer.begin();
            resultBuffer.append(body);
            break;

        case END:
            resultBuffer.append(body);
            resultBuffer.end();
            break;

        case PREPEND:
            resultBuffer.prepend(body);
            break;

        default:
            line = body;
            break;
        }

        if (line != null && line.length() > 0) {
            resultBuffer.append(line);
        }
    }

    private String convertValue(Object value) {

        if (value == null) {
            return "null";

        } else if (value instanceof String) {
            return convertString((String) value);

        } else if (value instanceof Number) {
            return value.toString();

        } else if (value instanceof Direct) {
            return value.toString();

        } else if (value instanceof Object[]) {
            Object[] values = (Object[]) value;
            StringBuilder builder = new StringBuilder();
            int len = values.length;

            if (len > 0) {
                builder.append(convertValue(values[0]));

                for (int i = 1; i < len; i++) {
                    builder.append(", ");
                    builder.append(convertValue(values[i]));
                }
            }

            return builder.toString();
        }

        return convertString(value.toString());
    }

    private String convertString(String s) {
        StringBuilder builder = new StringBuilder();
        builder.append('\'');

        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            if (escapeChars.indexOf(ch) != -1) {
                builder.append(ch);
            }

            builder.append(ch);
        }

        builder.append('\'');
        return builder.toString();
    }
}
