package com.github.nsnjson.encoding;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.*;

import java.util.*;
import java.util.function.Function;

import static com.fasterxml.jackson.databind.node.JsonNodeType.*;
import static com.github.nsnjson.format.Format.*;

public class Encoder {

    private static final Map<JsonNodeType, Function<JsonNode, Optional<JsonNode>>> resolvers = new HashMap<>();
    static {
        resolvers.put(NULL, (json) -> encodeNull());
        resolvers.put(NUMBER, (json) -> encodeNumber((NumericNode) json));
        resolvers.put(STRING, (json) -> encodeString((TextNode) json));
        resolvers.put(BOOLEAN, (json) -> encodeBoolean((BooleanNode) json));
        resolvers.put(ARRAY, (json) -> encodeArray((ArrayNode) json));
        resolvers.put(OBJECT, (json) -> encodeObject((ObjectNode) json));
    }

    public static Optional<JsonNode> encode(JsonNode value) {
        return Optional.ofNullable(resolvers.get(value.getNodeType())).flatMap(resolver -> resolver.apply(value));
    }

    private static Optional<JsonNode> encodeNull() {
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode presentation = objectMapper.createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_NULL);

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeBoolean(BooleanNode value) {
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode presentation = objectMapper.createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_BOOLEAN);
        presentation.put(FIELD_VALUE, value.booleanValue() ? BOOLEAN_TRUE : BOOLEAN_FALSE);

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeNumber(NumericNode value) {
        if (value.isInt()) {
            return encodeNumberInt(value);
        }
        else if (value.isLong()) {
            return encodeNumberLong(value);
        }
        else if (value.isDouble()) {
            return encodeNumberDouble(value);
        }

        return Optional.empty();
    }

    private static Optional<JsonNode> encodeNumberInt(NumericNode value) {
        ObjectNode presentation = new ObjectMapper().createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_NUMBER);
        presentation.put(FIELD_VALUE, value.asInt());

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeNumberLong(NumericNode value) {
        ObjectNode presentation = new ObjectMapper().createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_NUMBER);
        presentation.put(FIELD_VALUE, value.asLong());

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeNumberDouble(NumericNode value) {
        ObjectNode presentation = new ObjectMapper().createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_NUMBER);
        presentation.put(FIELD_VALUE, value.asDouble());

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeString(TextNode value) {
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode presentation = objectMapper.createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_STRING);
        presentation.put(FIELD_VALUE, value.asText());

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeArray(ArrayNode array) {
        ObjectMapper objectMapper = new ObjectMapper();

        ArrayNode presentationOfArrayItems = objectMapper.createArrayNode();

        for (JsonNode item: array) {
            Optional<JsonNode> itemPresentationOption = encode(item);

            if (itemPresentationOption.isPresent()) {
                JsonNode encodedItem = itemPresentationOption.get();

                presentationOfArrayItems.add(encodedItem);
            }
        }

        ObjectNode presentation = objectMapper.createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_ARRAY);
        presentation.set(FIELD_VALUE, presentationOfArrayItems);

        return Optional.of(presentation);
    }

    private static Optional<JsonNode> encodeObject(ObjectNode object) {
        ObjectMapper objectMapper = new ObjectMapper();

        ArrayNode presentationOfObjectFields = objectMapper.createArrayNode();

        for (Iterator<String> namesIterator = object.fieldNames(); namesIterator.hasNext();) {
            String name = namesIterator.next();

            JsonNode value = object.get(name);

            Optional<JsonNode> valuePresentationOption = encode(value);

            if (valuePresentationOption.isPresent()) {
                JsonNode valuePresentation = valuePresentationOption.get();

                ObjectNode fieldPresentation = objectMapper.createObjectNode();
                fieldPresentation.put(FIELD_NAME, name);
                fieldPresentation.set(FIELD_TYPE, valuePresentation.get(FIELD_TYPE));

                if (valuePresentation.has(FIELD_VALUE)) {
                    fieldPresentation.set(FIELD_VALUE, valuePresentation.get(FIELD_VALUE));
                }

                presentationOfObjectFields.add(fieldPresentation);
            }
        }

        ObjectNode presentation = objectMapper.createObjectNode();
        presentation.put(FIELD_TYPE, TYPE_MARKER_OBJECT);
        presentation.set(FIELD_VALUE, presentationOfObjectFields);

        return Optional.of(presentation);
    }

}
@@ -73,55 +73,16 @@
 ind.
-*;%0Aimport com.fasterxml.jackson.databind.n
+JsonN
 ode
-.*
 ;%0A%0Ai
@@ -101,5359 +101,222 @@
 til.
-*;%0Aimport java.util.function.Function;%0A%0Aimport static com.fasterxml.jackson.databind.node.JsonNodeType.*;%0Aimport static com.github.nsnjson.format.Format.*;%0A%0Apublic class Encoder %7B%0A%0A    private static final Map%3CJsonNodeType, Function%3CJsonNode, Optional%3CJsonNode%3E%3E%3E resolvers = new HashMap%3C%3E();%0A    static %7B%0A        resolvers.put(NULL, (json) -%3E encodeNull());%0A        resolvers.put(NUMBER, (json) -%3E encodeNumber((NumericNode) json));%0A        resolvers.put(STRING, (json) -%3E encodeString((TextNode) json));%0A        resolvers.put(BOOLEAN, (json) -%3E encodeBoolean((BooleanNode) json));%0A        resolvers.put(ARRAY, (json) -%3E encodeArray((ArrayNode) json));%0A        resolvers.put(OBJECT, (json) -%3E encodeObject((ObjectNode) json));%0A    %7D%0A%0A    public static Optional%3CJsonNode%3E encode(JsonNode value) %7B%0A        return Optional.ofNullable(resolvers.get(value.getNodeType())).flatMap(resolver -%3E resolver.apply(value));%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeNull() %7B%0A        ObjectMapper objectMapper = new ObjectMapper();%0A%0A        ObjectNode presentation = objectMapper.createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_NULL);%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeBoolean(BooleanNode value) %7B%0A        ObjectMapper objectMapper = new ObjectMapper();%0A%0A        ObjectNode presentation = objectMapper.createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_BOOLEAN);%0A        presentation.put(FIELD_VALUE, value.booleanValue() ? BOOLEAN_TRUE : BOOLEAN_FALSE);%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeNumber(NumericNode value) %7B%0A        if (value.isInt()) %7B%0A            return encodeNumberInt(value);%0A        %7D%0A        else if (value.isLong()) %7B%0A            return encodeNumberLong(value);%0A        %7D%0A        else if (value.isDouble()) %7B%0A            return encodeNumberDouble(value);%0A        %7D%0A%0A        return Optional.empty();%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeNumberInt(NumericNode value) %7B%0A        ObjectNode presentation = new ObjectMapper().createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_NUMBER);%0A        presentation.put(FIELD_VALUE, value.asInt());%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeNumberLong(NumericNode value) %7B%0A        ObjectNode presentation = new ObjectMapper().createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_NUMBER);%0A        presentation.put(FIELD_VALUE, value.asLong());%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeNumberDouble(NumericNode value) %7B%0A        ObjectNode presentation = new ObjectMapper().createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_NUMBER);%0A        presentation.put(FIELD_VALUE, value.asDouble());%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeString(TextNode value) %7B%0A        ObjectMapper objectMapper = new ObjectMapper();%0A%0A        ObjectNode presentation = objectMapper.createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_STRING);%0A        presentation.put(FIELD_VALUE, value.asText());%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeArray(ArrayNode array) %7B%0A        ObjectMapper objectMapper = new ObjectMapper();%0A%0A        ArrayNode presentationOfArrayItems = objectMapper.createArrayNode();%0A%0A        for (JsonNode item: array) %7B%0A            Optional%3CJsonNode%3E itemPresentationOption = encode(item);%0A%0A            if (itemPresentationOption.isPresent()) %7B%0A                JsonNode encodedItem = itemPresentationOption.get();%0A%0A                presentationOfArrayItems.add(encodedItem);%0A            %7D%0A        %7D%0A%0A        ObjectNode presentation = objectMapper.createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_ARRAY);%0A        presentation.set(FIELD_VALUE, presentationOfArrayItems);%0A%0A        return Optional.of(presentation);%0A    %7D%0A%0A    private static Optional%3CJsonNode%3E encodeObject(ObjectNode object) %7B%0A        ObjectMapper objectMapper = new ObjectMapper();%0A%0A        ArrayNode presentationOfObjectFields = objectMapper.createArrayNode();%0A%0A        for (Iterator%3CString%3E namesIterator = object.fieldNames(); namesIterator.hasNext();) %7B%0A            String name = namesIterator.next();%0A%0A            JsonNode value = object.get(name);%0A%0A            Optional%3CJsonNode%3E valuePresentationOption = encode(value);%0A%0A            if (valuePresentationOption.isPresent()) %7B%0A                JsonNode valuePresentation = valuePresentationOption.get();%0A%0A                ObjectNode fieldPresentation = objectMapper.createObjectNode();%0A                fieldPresentation.put(FIELD_NAME, name);%0A                fieldPresentation.set(FIELD_TYPE, valuePresentation.get(FIELD_TYPE));%0A%0A                if (valuePresentation.has(FIELD_VALUE)) %7B%0A                    fieldPresentation.set(FIELD_VALUE, valuePresentation.get(FIELD_VALUE));%0A                %7D%0A%0A                presentationOfObjectFields.add(fieldPresentation);%0A            %7D%0A        %7D%0A%0A        ObjectNode presentation = objectMapper.createObjectNode();%0A        presentation.put(FIELD_TYPE, TYPE_MARKER_OBJECT);%0A        presentation.set(FIELD_VALUE, presentationOfObjectFields);%0A%0A        return Optional.of(presentati
+Optional;%0A%0Apublic class Encoder %7B%0A%0A    private static final Encoding DEFAULT_ENCODING = new DefaultEncoding();%0A%0A    public static Optional%3CJsonNode%3E encode(JsonNode json) %7B%0A        return DEFAULT_ENCODING.encode(js
 on);

Refactor Encoder