package ru.yandex.autodoc.wmtools.params.fetch.impl.common;

import ru.yandex.autodoc.common.doc.ExtraInfoItem;
import ru.yandex.autodoc.common.doc.abstracts.ParamsDocRegistrar;
import ru.yandex.autodoc.wmtools.errors.CommonUserException;
import ru.yandex.autodoc.common.doc.params.ParamType;
import ru.yandex.autodoc.wmtools.params.fetch.MultiParamView;
import ru.yandex.autodoc.wmtools.params.fetch.ParamHolder;
import ru.yandex.common.util.Su;
import ru.yandex.autodoc.common.doc.params.ParamDescriptor;
import ru.yandex.autodoc.wmtools.params.QueryContext;
import ru.yandex.autodoc.wmtools.params.fetch.ParamUtils;

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

/**
 * @author avhaliullin
 */
public abstract class AbstractMultiParamView<T> implements MultiParamView<T> {
    protected final String name;
    protected final ParamType type;
    protected final Integer minValues;
    protected final Integer maxValues;
    protected final boolean skipNull;
    protected final boolean required;

    protected AbstractMultiParamView(ParamsDocRegistrar registrar, String name, ParamType type, boolean required, List<ExtraInfoItem> extraInfos,
                                     Integer minValues, Integer maxValues, boolean skipNull, String desc) {
        this.name = name;
        this.type = type;
        this.required = required;
        this.skipNull = skipNull;
        this.minValues = minValues;
        this.maxValues = maxValues;
        registrar.register(new ParamDescriptor(name, required, ParamType.multi(type), null,
                desc, extraInfos(extraInfos, minValues, maxValues)));
    }

    public List<T> fetchValues(QueryContext context) {
        return ParamUtils.unwrap(fetch(context));
    }

    public ParamHolder<List<ParamHolder<T>>> fetch(QueryContext context) {
        List<String> values = context.getRequest().getMultiParams(name);
        boolean hasErrorOnCollection = false;

        if (required && values.isEmpty()) {
            hasErrorOnCollection = true;
            context.addError(CommonUserException.createRequiredParamMissed(name));
        } else if (minValues != null && values.size() < minValues) {
            hasErrorOnCollection = true;
            context.addError(CommonUserException.createTooFewValues(name, minValues, values.size()));
        } else if (maxValues != null && values.size() > maxValues) {
            hasErrorOnCollection = true;
            context.addError(CommonUserException.createTooManyValues(name, maxValues, values.size()));
        }

        List<ParamHolder<T>> result = new ArrayList<>();
        for (String value : values) {
            ErrorListener errorListener = new ErrorListener();
            T data = doFetch(value, errorListener);
            if (errorListener.hasError()) {
                context.addError(errorListener.getError());
            }
            // skipNull говорит, что не нужно добавлять null'ы. Правда, их нужно добавлять, если была ошибка
            if (data != null || errorListener.hasError() || !skipNull) {
                result.add(new ParamHolder<>(name, data, context, errorListener.hasError(), value));
            }
        }
        return new ParamHolder<>(name, result, context, hasErrorOnCollection, Su.join(values, ","));
    }

    protected abstract T doFetch(String value, ErrorListener errorListener);

    private static List<ExtraInfoItem> extraInfos(List<ExtraInfoItem> extraInfos, Integer minValues, Integer maxValues) {
        if (minValues != null) {
            extraInfos.add(ExtraInfoItem.createSimple("Min values count: " + minValues));
        }
        if (maxValues != null) {
            extraInfos.add(ExtraInfoItem.createSimple("Max values count: " + maxValues));
        }
        return extraInfos;
    }
}
