package com.atlassian.bitbucket.rest;

import com.atlassian.bitbucket.rest.util.StatefulJsonWriter;
import com.atlassian.bitbucket.ServiceException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.codehaus.jackson.map.annotate.JsonSerialize;

import java.io.IOException;
import java.util.*;

import static java.util.stream.Collectors.toList;

@JsonSerialize
public class RestErrors extends RestMapEntity {

    public static final String ERRORS = "errors";

    public RestErrors() {
        put(ERRORS, new ArrayList<RestErrorMessage>());
    }

    public RestErrors(Collection<RestErrorMessage> errors) {
        put(ERRORS, new ArrayList<>(errors));
    }

    public RestErrors(String error) {
        this(new RestErrorMessage(error));
    }

    /**
     * Construct a {@code RestErrors} instance with a single error containing {@code context} and {@code message}.
     *
     * @param context the error context
     * @param error error message
     */
    public RestErrors(String context, String error) {
        this(new RestErrorMessage(context, error, null));
    }

    public RestErrors(ServiceException e) {
        this(new RestErrorMessage(e));
    }

    public RestErrors(RestErrorMessage message) {
        this(Collections.singletonList(message));
    }

    /**
     * Attempts to end the JSON scope of the supplied writer to a point where either
     * an errors property can be added to the top-level object (where one exists) or a top
     * level object can be added to the root with the errors property set (where no object, array or other value
     * has already been added to the root yet).
     *
     * If this is not possible due the current JSON object context and element count,
     * false is returned.
     *
     * Regardless of the return result, after calling this method the JSON writer's context
     * will be set to the root.
     *
     * @param writer the JSON writer to end scope and attempt to write the error to
     * @return true if the error property could be written, false otherwise
     * @throws IOException if there is an IO exception from the underlying JSON writer
     */
    public boolean endScopeAndTryWriteTo(StatefulJsonWriter writer) throws IOException {
        StatefulJsonWriter.WritingState writingState = writer.endToDocument().getWritingState();

        //If this is the root and there are no other objects, add our error object
        if (writingState.getContext() == StatefulJsonWriter.WritingContext.ROOT && writingState.getEntryCount() == 0) {
            writer.beginObject();
            writeErrors(writer);
            writer.endObject();
            return true;
        } else if (writingState.getContext() == StatefulJsonWriter.WritingContext.OBJECT) {
            writeErrors(writer);
            writer.endObject();
            return true;
        }
        return false;
    }

    public List<RestErrorMessage> getErrors() {
        return getRawErrors().stream()
                .map(RestErrorMessage::valueOf)
                .filter(Objects::nonNull)
                .collect(toList());
    }

    private List<?> getRawErrors() {
        return (List<?>) get(ERRORS);
    }

    private void writeErrors(StatefulJsonWriter writer) throws IOException {
        writer.name(ERRORS);
        writer.beginArray();
        for (RestErrorMessage message : getErrors()) {
            writer.value(message);
        }
        writer.endArray();
    }

    public static class Builder {

        private final ImmutableList.Builder<RestErrorMessage> errorsBuilder = ImmutableList.builder();

        public Builder add(String value) {
            errorsBuilder.add(new RestErrorMessage(value));
            return this;
        }

        public Builder add(RestErrorMessage value) {
            errorsBuilder.add(value);
            return this;
        }

        public Builder addErrors(Iterable<String> values) {
            errorsBuilder.addAll(Iterables.transform(values, RestErrorMessage::new));
            return this;
        }

        public Builder addErrorMessages(Iterable<RestErrorMessage> values) {
            errorsBuilder.addAll(values);
            return this;
        }

        public RestErrors build() {
            return new RestErrors(errorsBuilder.build());
        }
    }

    // REST Documentation Examples
    public static final RestErrors EXAMPLE = new RestErrors("A detailed error message.");

    public static final RestErrors VALIDATION_EXAMPLE = new RestErrors(ImmutableList.of(
            new RestErrorMessage("field_a", "A detailed validation error message for field_a."),
            new RestErrorMessage("A detailed error message.")));
}