package ru.yandex.autodoc.common.doc.view.handlers;

import ru.yandex.autodoc.common.doc.types.AnyObjectModel;
import ru.yandex.autodoc.common.doc.types.CollectionType;
import ru.yandex.autodoc.common.doc.types.EnumType;
import ru.yandex.autodoc.common.doc.types.FieldDescription;
import ru.yandex.autodoc.common.doc.types.MapType;
import ru.yandex.autodoc.common.doc.types.ObjectModel;
import ru.yandex.autodoc.common.doc.types.PolyObjectModel;
import ru.yandex.autodoc.common.doc.types.ValueType;
import ru.yandex.autodoc.common.doc.view.Markup;
import ru.yandex.autodoc.common.doc.view.format.JsonValue;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author avhaliullin
 */
public class JsonFormatResolver implements ObjectFormatResolver {
    public static final JsonFormatResolver INSTANCE = new JsonFormatResolver();

    @Override
    public Markup resolve(AnyObjectModel model) {
        return new Markup.JsonObject(resolveValue(model));
    }

    protected JsonValue resolveValue(ValueType tpe) {
        if (tpe instanceof ObjectModel) {
            ObjectModel obj = (ObjectModel) tpe;
            return new JsonValue.Object(
                    resolveFields(obj.getFields()),
                    Markup.textOrEmpty(obj.getDescription()),
                    false
            );
        } else if (tpe instanceof PolyObjectModel) {
            PolyObjectModel poly = (PolyObjectModel) tpe;
            return new JsonValue.PolyObject(
                    poly.getDiscriminator(),
                    poly.getCases().stream()
                            .map(cse -> new JsonValue.PolyCase(
                                    cse.getCaseName(),
                                    resolveFields(cse.getModel().getFields()),
                                    Markup.textOrEmpty(cse.getModel().getDescription())
                            )).collect(Collectors.toList()),
                    Markup.EMPTY
            );
        } else if (tpe instanceof CollectionType) {
            CollectionType collection = (CollectionType) tpe;
            return new JsonValue.Array(
                    resolveValue(collection.getElementType()),
                    Markup.EMPTY
            );
        } else if (tpe instanceof MapType) {
            MapType map = (MapType) tpe;
            return new JsonValue.Object(
                    Collections.singletonList(
                            new JsonValue.JsonField(
                                    resolveStringValue(map.getKeyType()),
                                    resolveValue(map.getValueType()),
                                    Markup.EMPTY
                            )
                    ),
                    Markup.EMPTY,
                    true
            );
        } else {
            return resolvePrimitive(tpe, tpe);
        }
    }

    protected JsonValue resolvePrimitive(ValueType baseType, ValueType tpe) {
        Optional<String> exampleStringOpt = Optional.ofNullable(findExample(tpe));

        Markup description = baseType.equals(tpe)
                ? Markup.EMPTY
                : new Markup.Text(tpe.getName());
        if (baseType == ValueType.BOOLEAN) {
            return new JsonValue.BooleanValue(
                    exampleStringOpt.map(Boolean::parseBoolean).orElse(false),
                    description
            );
        } else if (baseType == ValueType.NUMERIC || baseType == ValueType.INTEGER) {
            return new JsonValue.IntValue(
                    exampleStringOpt.map(Integer::parseInt).orElse(1),
                    description
            );
        } else if (baseType == ValueType.FLOAT) {
            return new JsonValue.FloatValue(
                    exampleStringOpt.map(Float::parseFloat).orElse(0.1f),
                    description
            );
        } else if (baseType == ValueType.STRING) {
            return new JsonValue.StringValue(resolveStringValue(tpe), description);
        } else {
            if (baseType.getBaseType() != null) {
                return resolvePrimitive(baseType.getBaseType(), tpe);
            } else {
                throw new RuntimeException("Unsupported base type " + baseType + " of ancestor " + tpe);
            }
        }
    }

    private String findExample(ValueType vt) {
        if (vt instanceof ValueType.Primitive) {
            return ((ValueType.Primitive) vt).getExample();
        } else if (vt.getBaseType() != null) {
            return findExample(vt.getBaseType());
        } else {
            return null;
        }
    }

    protected Markup resolveStringValue(ValueType vt) {
        if (vt instanceof EnumType) {
            return TypeMarkupUtil.enumMarkup((EnumType) vt, true);
        } else {
            String example = findExample(vt);
            return new Markup.Text(example == null ? "some string" : example);
        }
    }

    protected List<JsonValue.JsonField> resolveFields(List<FieldDescription> fs) {
        return fs.stream().map(this::resolveField).collect(Collectors.toList());
    }

    protected JsonValue.JsonField resolveField(FieldDescription f) {
        return new JsonValue.JsonField(
                new Markup.Text(f.getName()),
                resolveValue(f.getType()),
                Markup.textOrEmpty(f.getDescription())
        );
    }
}

