package ru.yandex.autodoc.wmtools.params.fetch.json;

import ru.yandex.autodoc.common.out.json.builder.JsonValueBuilder;
import ru.yandex.autodoc.wmtools.params.fetch.json.primitive.IntFieldView;
import ru.yandex.common.util.Su;
import ru.yandex.common.util.collections.Cf;
import ru.yandex.autodoc.common.doc.DocUtils;
import ru.yandex.autodoc.wmtools.params.fetch.ParamHolder;
import ru.yandex.autodoc.wmtools.params.fetch.json.primitive.BooleanFieldView;
import ru.yandex.autodoc.wmtools.params.fetch.json.primitive.LongFieldView;
import ru.yandex.autodoc.common.out.json.JsonValueWriter;
import ru.yandex.autodoc.common.out.json.builder.ContainerBuilder;
import ru.yandex.autodoc.common.out.json.builder.JsonObjectBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @author avhaliullin
 */
public abstract class JsonObjectView<T, I> extends AbstractJsonValueView<T, I> {
    private final List<FieldDescriptor> fields = new ArrayList<>();

    private final String className = className(this, null);

    public ParamHolder<T> fetch(JsonValueContext context, I injected) {
        JsonObjectContext objContext = context.asObject();
        if (objContext.isNull()) {
            return null;
        } else {
            T res = doFetch(objContext, injected);
            return new ParamHolder<>(objContext.jsonParamName(), res, objContext.queryContext, objContext.hasError(), String.valueOf(objContext.node));
        }
    }

    private String className(Object o, String dflt) {
        String result = DocUtils.getNameForObject(o);
        if (result == null) {
            return dflt;
        } else {
            return result;
        }
    }

    @Override
    public JsonValueWriter getExample(final Set<JsonValueView> views) {
        return new JsonValueWriter() {
            @Override
            public <C extends ContainerBuilder> C writeValue(JsonValueBuilder<C> builder) {
                JsonObjectBuilder<C> objBuilder = builder.valueObject();
                if (className != null) {
                    objBuilder.comment(className);
                }
                for (FieldDescriptor descriptor : fields) {
                    JsonValueView example = descriptor.getView();
                    List<String> extraInfos = example.getExtraInfo();
                    String desc = (descriptor.getDesc() == null ? "" : descriptor.getDesc()) +
                            (extraInfos.isEmpty() ? "" : "; " + Su.join(example.getExtraInfo(), "; ")) +
                            (!descriptor.isRequired() ? "; Nullable" : "");
                    if (example instanceof JsonObjectView && views.contains(example)) {
                        String typeName = className(example, "Types, used recursively, should have names!");
                        objBuilder = objBuilder
                                .key(descriptor.getName(), desc)
                                .valueObjectName(typeName);
                    } else {
                        objBuilder = objBuilder
                                .key(descriptor.getName(), desc)
                                .writeValue(descriptor.getView().getExample(views));
                    }
                }
                return objBuilder.endObject();
            }
        };
    }

    @Override
    public List<String> getExtraInfo() {
        return Cf.list();
    }

    protected abstract T doFetch(JsonObjectContext context, I injected);

    private <T, I, V extends JsonFieldView<T, I>> V internalBind(String fieldName, JsonValueView<T, I> view, boolean required, String desc, V fieldView) {
        fields.add(new FieldDescriptor(fieldName, view, required, desc));
        return fieldView;
    }

    protected <T, I> JsonFieldView<T, I> bindInjectable(final String fieldName, final JsonValueView<T, I> view, final boolean required, String desc) {
        fields.add(new FieldDescriptor(fieldName, view, required, desc));
        return new AbstractJsonFieldView<T, I>() {
            @Override
            public ParamHolder<T> fetch(JsonObjectContext context, I injected) {
                return view.fetch(context.getField(fieldName, required), injected);
            }
        };
    }

    protected <T> SimpleFieldView<T> bind(final String fieldName, final JsonValueView<T, ?> view, final boolean required, String desc) {
        fields.add(new FieldDescriptor(fieldName, view, required, desc));
        return new SimpleFieldView<T>() {
            @Override
            public ParamHolder<T> fetch(JsonObjectContext context, Void ignored) {
                return view.fetch(context.getField(fieldName, required), null);
            }
        };
    }

    protected IntFieldView bindInt(String fieldName, int example, String desc) {
        return new IntFieldView(bind(fieldName, JsonValueViews.intView(example), true, desc));
    }

    protected LongFieldView bindLong(String fieldName, long example, String desc) {
        return new LongFieldView(bind(fieldName, JsonValueViews.longView(example), true, desc));
    }

    protected BooleanFieldView bindBoolean(String fieldName, boolean example, String desc) {
        return new BooleanFieldView(bind(fieldName, JsonValueViews.booleanView(example), true, desc));
    }

    protected <T, I> JsonFieldView<List<ParamHolder<T>>, I> bindArrayInjectable(final String fieldName, final JsonValueView<T, I> view, final boolean required, String desc) {
        return bindInjectable(fieldName, new JsonArrayView<>(view), required, desc);
    }

    protected <T, I> JsonFieldView<List<ParamHolder<T>>, I> bindArrayInjectableWithIndex(final String fieldName, final JsonValueView<T, InjectableWithIndex<I>> view, final boolean required, String desc) {
        JsonArrayView.IndexedJsonArrayView<T, I> arrayView = new JsonArrayView.IndexedJsonArrayView<>(view);
        return bindInjectable(fieldName, arrayView, required, desc);
    }

    protected <T> SimpleArrayFieldView<T> bindArrayWithIndex(final String fieldName, final JsonValueView<T, InjectableWithIndex<Object>> view, final boolean required, String desc) {
        JsonArrayView.IndexedJsonArrayView<T, Object> arrayView = new JsonArrayView.IndexedJsonArrayView<>(view);
        fields.add(new FieldDescriptor(fieldName, arrayView, required, desc));
        return new SimpleArrayFieldView<>(arrayView, fieldName, required);
    }

    protected <T> SimpleArrayFieldView<T> bindArray(final String fieldName, final JsonValueView<T, ?> view, final boolean required, String desc) {
        JsonArrayView<T, ?> arrayView = new JsonArrayView<>(view);
        fields.add(new FieldDescriptor(fieldName, arrayView, required, desc));
        return new SimpleArrayFieldView<>(arrayView, fieldName, required);
    }

    private class FieldDescriptor {
        private final String name;
        private final JsonValueView view;
        private final boolean required;
        private final String desc;

        private FieldDescriptor(String name, JsonValueView view, boolean required, String desc) {
            this.name = name;
            this.view = view;
            this.required = required;
            this.desc = desc;
        }

        private String getName() {
            return name;
        }

        private JsonValueView getView() {
            return view;
        }

        private boolean isRequired() {
            return required;
        }

        private String getDesc() {
            return desc;
        }
    }
}
