package org.hypergraphql.datafetching;

import graphql.language.Definition;
import graphql.language.Document;
import graphql.language.Field;
import graphql.language.FragmentDefinition;
import graphql.language.OperationDefinition;
import graphql.language.SelectionSet;
import org.hypergraphql.datamodel.HGQLSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

public class ExecutionForestFactory {

    private final static Logger LOGGER = LoggerFactory.getLogger(ExecutionForestFactory.class);

    public ExecutionForest getExecutionForest(Document queryDocument , HGQLSchema schema) {

        ExecutionForest forest = new ExecutionForest();

        SelectionSet queryFields = selectionSet(queryDocument);

        final AtomicInteger counter = new AtomicInteger(0);
        queryFields.getSelections().forEach(child -> { // query fields - why no args?

            if (child.getClass().getSimpleName().equals("Field")) {

                String nodeId = "x_" + counter.incrementAndGet();
                forest.getForest().add(new ExecutionTreeNode((Field) child, nodeId , schema));

            }
        });
        return forest;
    }

    private SelectionSet selectionSet(final Document queryDocument) {

        final Definition definition = queryDocument.getDefinitions().get(0);

        if(definition.getClass().isAssignableFrom(FragmentDefinition.class)) {

            return getFragmentSelectionSet(queryDocument);

        } else if(definition.getClass().isAssignableFrom(OperationDefinition.class)) {
            final OperationDefinition operationDefinition = (OperationDefinition)definition;
            return operationDefinition.getSelectionSet();
        }
        throw new IllegalArgumentException(queryDocument.getClass().getName() + " is not supported");
    }

    private SelectionSet getFragmentSelectionSet(final Document queryDocument) {

        // NPE
        final FragmentDefinition fragmentDefinition = (FragmentDefinition)queryDocument.getDefinitions().get(0);
        final SelectionSet originalSelectionSet = fragmentDefinition.getSelectionSet();

        final Optional<Definition> optionalDefinition = queryDocument.getDefinitions()
                .stream()
                .filter(def -> def.getClass().isAssignableFrom(OperationDefinition.class))
                .findFirst();

        final OperationDefinition operationDefinition;
        if(optionalDefinition.isPresent()) {
            operationDefinition = (OperationDefinition)optionalDefinition.get();
        } else {
            // bail
            throw new IllegalArgumentException("No OperationDefinition is available within the query");
        }

        // NPE?
        final Field operationSelection = (Field)operationDefinition.getSelectionSet().getSelections().get(0);

        final String typeFieldName = operationSelection.getName();

        final Field newSelection = Field.newField()
                .name(typeFieldName)
                .arguments(operationSelection.getArguments())
                .selectionSet(originalSelectionSet)
                .build();

        return new SelectionSet(Collections.singletonList(newSelection));
    }
}