package be.iminds.ilabt.jfed.ui.javafx.probe_gui.command_arguments;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;


import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.model.ParameterHistoryModel;
import be.iminds.ilabt.jfed.lowlevel.ApiMethodParameter;
import be.iminds.ilabt.jfed.lowlevel.ApiMethodParameterType;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractFederationApi1;
import be.iminds.ilabt.jfed.lowlevel.api.AbstractFederationApi2;
import be.iminds.ilabt.jfed.lowlevel.resourceid.ResourceId;
import be.iminds.ilabt.jfed.lowlevel.resourceid.ResourceUrn;
import be.iminds.ilabt.jfed.ui.javafx.probe_gui.ProbeController;
import be.iminds.ilabt.jfed.ui.javafx.probe_gui.command_arguments.CommandArgumentChooser.CredentialSubject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import javax.inject.Inject;
import java.util.*;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 12/1/2014
 * Time: 15:40
 */
public class CommandArgumentChooserFactory {

    private final TestbedInfoSource testbedInfoSource;
    private final GeniUserProvider geniUserProvider;
    private final ParameterHistoryModel parameterHistoryModel;
    private final SfaModel sfaModel;

    @Inject
    public CommandArgumentChooserFactory(TestbedInfoSource testbedInfoSource, GeniUserProvider geniUserProvider, ParameterHistoryModel parameterHistoryModel, SfaModel sfaModel) {
        this.testbedInfoSource = testbedInfoSource;
        this.geniUserProvider = geniUserProvider;
        this.parameterHistoryModel = parameterHistoryModel;
        this.sfaModel = sfaModel;
    }


    @SuppressWarnings("unchecked")
    public <T> CommandArgumentChooser<T> getCommandArgumentChooser(
            String parameterName,
            Class<T> parameterClass,
            ApiMethodParameter annotation,
            ProbeController.MethodInfo methodInfo,
            final ObservableList<CommandArgumentChooser> otherCommandArgumentChoosers) {

        Server userServer = null;
        if (geniUserProvider.isUserLoggedIn()) {
            userServer = geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer();
        }

        ApiMethodParameterType paramType = ApiMethodParameterType.NOT_SPECIFIED;

        paramType = annotation.parameterType();

        if (paramType == ApiMethodParameterType.NOT_SPECIFIED)
            paramType = deriveType(parameterName, parameterClass, annotation, methodInfo);

        switch (paramType) {
            case STRING: {
                return (CommandArgumentChooser<T>) new StringArgumentChooser(
                        Objects.equals(annotation.guiDefault(), "") ? null : annotation.guiDefault());
            }
            case STRING_MULTILINE: {
                return (CommandArgumentChooser<T>) new MultiLineStringArgumentChooser(
                        Objects.equals(annotation.guiDefault(), "") ? null : annotation.guiDefault(), false);
            }
            case INTEGER: {
                Integer defaultValue = null;
                if (!Objects.equals(annotation.guiDefault(), ""))
                    defaultValue = Integer.parseInt(annotation.guiDefault());
                return (CommandArgumentChooser<T>) new IntegerArgumentChooser(defaultValue);
            }
            case BOOLEAN: {
                Boolean defaultValue = null;
                if (!Objects.equals(annotation.guiDefault(), "")) {
                    defaultValue = false;
                    if (annotation.guiDefault().equalsIgnoreCase("true"))
                        defaultValue = true;
                    if (annotation.guiDefault().equalsIgnoreCase("yes"))
                        defaultValue = true;
                    if (annotation.guiDefault().equalsIgnoreCase("1"))
                        defaultValue = true;
                }
                return (CommandArgumentChooser<T>) new BooleanArgumentChooser(defaultValue);
            }
            case CREDENTIAL_STRING: {
                return (CommandArgumentChooser<T>) new CredentialArgumentChooser( parameterHistoryModel, CredentialSubject.ANY);
            }
            case USER_CREDENTIAL_STRING: {
                return (CommandArgumentChooser<T>) new CredentialArgumentChooser( parameterHistoryModel, CredentialSubject.USER);
            }
            case SLICE_CREDENTIAL_STRING: {
                return (CommandArgumentChooser<T>) new CredentialArgumentChooser( parameterHistoryModel, CredentialSubject.SLICE);
            }
            case URN: {
                return (CommandArgumentChooser<T>) new UrnArgumentProvidedOptionsChooser(parameterHistoryModel.getAllUrnList(), null, userServer);
            }
            case SLIVER_URN: {
                return (CommandArgumentChooser<T>) new UrnArgumentProvidedOptionsChooser(parameterHistoryModel.getSliverUrns(), "sliver", userServer);
            }
            case AM_URN: {
                return (CommandArgumentChooser<T>) new UrnArgumentProvidedOptionsChooser(
                        FXCollections.observableArrayList(
                            testbedInfoSource.getServers().stream().map(Server::getDefaultComponentManagerUrn).collect(Collectors.toList())
                        )
                        , "authority", userServer);
            }
            case SLICE_URN: {
                return (CommandArgumentChooser<T>) new UrnArgumentProvidedOptionsChooser(parameterHistoryModel.getSliceUrnsList(), "slice", userServer);
            }
            case USER_URN: {
                return (CommandArgumentChooser<T>) new UrnArgumentProvidedOptionsChooser(parameterHistoryModel.getUserUrns(), "user", userServer);
            }
            case RSPEC_STRING: {
                String defaultRspec =
                        "<rspec type=\"request\" generated=\"2013-01-16T14:20:39Z\" xsi:schemaLocation=\"http://www.geni.net/resources/rspec/3 http://www.geni.net/resources/rspec/3/request.xsd \" xmlns:client=\"http://www.protogeni.net/resources/rspec/ext/client/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.geni.net/resources/rspec/3\">\n" +
                                "  <node client_id=\"PC\" component_manager_id=\"urn:publicid:IDN+" +
                                (geniUserProvider.isUserLoggedIn()
                                        ? geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer().getUrnTld()
                                        : "<AM>") +
                                "+authority+cm\" exclusive=\"true\">\n" +
                                "    <sliver_type name=\"raw-pc\"/>\n" +
                                "  </node>\n" +
                                "</rspec>\n";
                return (CommandArgumentChooser<T>) new MultiLineStringArgumentChooser(
                        Objects.equals(annotation.guiDefault(), "") ? defaultRspec : annotation.guiDefault(), true);
            }
            case LIST_OF_CREDENTIAL: {
                return (CommandArgumentChooser<T>) new MultiCredentialArgumentChooser(parameterHistoryModel, CredentialSubject.ANY);
            }
            case LIST_OF_URN_STRING: {
                return (CommandArgumentChooser<T>) new MultiStringArgumentProvidedOptionsChooser(parameterHistoryModel.getAllUrnList());
            }
            case LIST_OF_USERSPEC: {
                return (CommandArgumentChooser<T>) new UserSpecListArgumentChooser(geniUserProvider, sfaModel);
            }
            case LIST_OF_STRING: {
                return (CommandArgumentChooser<T>) new MultiStringArgumentProvidedOptionsChooser(FXCollections.observableArrayList());
            }
            case LIST_OF_SLICE_URN_STRING: {
                return (CommandArgumentChooser<T>) new MultiStringArgumentProvidedOptionsChooser(parameterHistoryModel.getSliceUrnsList());
            }
            case LIST_OF_USER_URN_STRING: {
                return (CommandArgumentChooser<T>) new MultiStringArgumentProvidedOptionsChooser(parameterHistoryModel.getUserUrns());
            }

            //TODO: smart selection of specific API fields
            //TODO: if GetVersion reply cached, use all fields from it
            case CH_API1_FILTER: {
                return (CommandArgumentChooser<T>)
                        new MultiStringArgumentProvidedOptionsChooser(FXCollections.observableArrayList(getChApiFields1(methodInfo)));
            }
            case CH_API1_MATCH: {
                return (CommandArgumentChooser<T>)
                        new ChApi1MatchArgumentChooser(getChApiFieldsMap1(methodInfo));
            } //TODO: allow multiple values per field! And make a more convenient GUI for searching using this.
            case CH_API1_FIELDS: {
                return (CommandArgumentChooser<T>)
                        new MapStringToStringArgumentChooser(getChApiFieldsMap1(methodInfo), "Field", "Value");
            }


            //TODO: implement these for APIv2  (they need a selector for object TYPE, in addition to what they had in v1.
            //                                  Or even better, this selector should be linked to the type parameter for the same method!)
            case CH_API2_OBJECT_TYPE: {
                List<String> objectNames = getChApi2Objects(methodInfo);
                StringArgumentProvidedOptionsChooser res =
                        new StringArgumentProvidedOptionsChooser(FXCollections.observableArrayList(objectNames), getChApi2DefaultObject(methodInfo), false/*limitToOptions*/);

                res.valueProperty().addListener((observableValue, from, to) -> {
                    if (to != null) {
                        otherCommandArgumentChoosers.stream().filter(commandArgumentChooser -> ChApiTypeDependantArgumentChooser.class.isAssignableFrom(commandArgumentChooser.getClass())).forEach(commandArgumentChooser -> ((ChApiTypeDependantArgumentChooser) commandArgumentChooser).setObjectType(to));
                    }
                });

                return (CommandArgumentChooser<T>) res;
            }
            case CH_API2_FILTER: {
                return (CommandArgumentChooser<T>)
                        new ChApi2FilterArgumentChooser(getChApi2(methodInfo), getChApi2ConvenientOrDefaultObject(methodInfo));
            }
            case CH_API2_MATCH: {
                return (CommandArgumentChooser<T>)
                        new ChApi2MatchArgumentChooser(getChApi2(methodInfo), getChApi2ConvenientOrDefaultObject(methodInfo));
            }

            case CH_API2_FIELDS: {
                return (CommandArgumentChooser<T>)
                        new ChApi2FieldsArgumentChooser(getChApi2(methodInfo), getChApi2ConvenientOrDefaultObject(methodInfo));
            }


            case LIST_OF_URN: {
                return (CommandArgumentChooser<T>) new MultiUrnArgumentProvidedOptionsChooser(parameterHistoryModel.getAllUrnList());
            }
            case LIST_OF_SLICE_URN: {
                return (CommandArgumentChooser<T>) new MultiUrnArgumentProvidedOptionsChooser(parameterHistoryModel.getSliceUrnsList());
            }
            case LIST_OF_SLIVER_URN: {
                return (CommandArgumentChooser<T>) new MultiUrnArgumentProvidedOptionsChooser(parameterHistoryModel.getSliverUrns());
            }
            case LIST_OF_USER_URN: {
                return (CommandArgumentChooser<T>) new MultiUrnArgumentProvidedOptionsChooser(parameterHistoryModel.getUserUrns());
            }


            case GENI_EXTRA_OPTIONS: {
                return (CommandArgumentChooser<T>) new ExtraOptionsArgumentChooser(annotation.guiDefault());
            }
            case XML_RPC_STRUCT: {
                return (CommandArgumentChooser<T>) new MapArgumentChooser(annotation.guiDefault(), true);
            }

            case CH_API_LIST_MEMBER_TUPLES: {
                return (CommandArgumentChooser<T>) new ChApiMemberArgumentChooser(null/* todo cached members*/);
            }

            case MAP_OF_STRING_TO_STRING: {
                return (CommandArgumentChooser<T>) new MapStringToStringArgumentChooser(new HashMap<>());
            }
            case MAP_OF_STRING_TO_OBJECT: {
                return new UnsupportedArgumentChooser<>(parameterClass);
            }

            case DATE: {
                return (CommandArgumentChooser<T>) new DateArgumentChooser();
            }
            case STRING_DATE_RFC3339: {
                return (CommandArgumentChooser<T>) new DateArgumentChooser();
            }
            case URL: {
                return (CommandArgumentChooser<T>) new StringArgumentChooser(
                        Objects.equals(annotation.guiDefault(), "") ? "http://example.com" : annotation.guiDefault());
            }
            case EMAIL: {
                return (CommandArgumentChooser<T>) new StringArgumentChooser(
                        Objects.equals(annotation.guiDefault(), "") ? "example@example.com" : annotation.guiDefault());
            }

            case NOT_SPECIFIED:
            default: {
                return new UnsupportedArgumentChooser<>(parameterClass);
            }
        }
    }

    public static Map<String, String> getChApiFieldsMap1(ProbeController.MethodInfo methodInfo) {
        if (methodInfo.api.getAbstractApi() instanceof AbstractFederationApi1) {
            AbstractFederationApi1 gch = (AbstractFederationApi1) methodInfo.api.getAbstractApi();
            String objectName = gch.getMethodObject(methodInfo.method);
            if (objectName == null) {
                System.err.println("Tiny bug: method \"" + methodInfo.name + "\" not registered as object CRUD method.");
                return new HashMap<>();
            }
            return gch.getMinimumFieldsMap(objectName);
        }
        return null;
    }

    public static ApiMethodParameterType deriveType(String parameterName, Class parameterClass, ApiMethodParameter annotation,
                                                    ProbeController.MethodInfo methodInfo) {
        if (Objects.equals(annotation.name(), "credentialList"))
            return ApiMethodParameterType.LIST_OF_CREDENTIAL;

        if (annotation.name().equals("slice") || annotation.name().equals("sliceUrn"))
            return ApiMethodParameterType.SLICE_URN;

        if (Objects.equals(annotation.name(), "urns"))
            return ApiMethodParameterType.LIST_OF_URN_STRING;


        if (Objects.equals(annotation.name(), "userCredential"))
            return ApiMethodParameterType.USER_CREDENTIAL_STRING;

        if (Objects.equals(annotation.name(), "credential"))
            return ApiMethodParameterType.CREDENTIAL_STRING;

        if (Objects.equals(annotation.name(), "sliceCredential"))
            return ApiMethodParameterType.SLICE_CREDENTIAL_STRING;

        if (Objects.equals(annotation.name(), "clearingHouseCredential"))
            return ApiMethodParameterType.CREDENTIAL_STRING;

        if (annotation.name().equals("UserSpecList") || annotation.name().equals("users"))
            return ApiMethodParameterType.LIST_OF_USERSPEC;


        if (annotation.name().equals("user") || annotation.name().equals("userUrn"))
            return ApiMethodParameterType.USER_URN;

        if (Objects.equals(annotation.name(), "rspec"))
            return ApiMethodParameterType.RSPEC_STRING;

        if (Objects.equals(parameterClass, ResourceId.class))
            return ApiMethodParameterType.URN;

        if (Objects.equals(parameterClass, ResourceUrn.class))
            return ApiMethodParameterType.URN;

        if (Objects.equals(parameterClass, String.class))
            if (annotation.multiLineString())
                return ApiMethodParameterType.STRING_MULTILINE;
            else
                return ApiMethodParameterType.STRING;

        if (Objects.equals(parameterClass, Boolean.class))
            return ApiMethodParameterType.BOOLEAN;

        if (Objects.equals(parameterClass, Map.class) && (Objects.equals(annotation.name(), "options") || Objects.equals(annotation.name(), "extraOptions") || Objects.equals(annotation.name(), "extra_options")))
            return ApiMethodParameterType.GENI_EXTRA_OPTIONS;

        if (Objects.equals(parameterClass, Map.class))
            return ApiMethodParameterType.XML_RPC_STRUCT;

        if (Objects.equals(parameterClass, Integer.class))
            return ApiMethodParameterType.INTEGER;

        if (Objects.equals(parameterClass, Date.class))
            return ApiMethodParameterType.DATE;

        return ApiMethodParameterType.NOT_SPECIFIED;
    }


    public static List<String> getChApiFields1(ProbeController.MethodInfo methodInfo) {
        if (methodInfo.api.getAbstractApi() instanceof AbstractFederationApi1) {
            AbstractFederationApi1 gch = (AbstractFederationApi1) methodInfo.api.getAbstractApi();
            String objectName = gch.getMethodObject(methodInfo.method);
            if (objectName == null) {
                System.err.println("Tiny bug: method \"" + methodInfo.name + "\" not registered as object CRUD method.");
                return new ArrayList<>();
            }
            return gch.getMinimumFieldNames(objectName);
        }
        return null;
    }

    public static String getChApi2DefaultObject(ProbeController.MethodInfo methodInfo) {
        if (methodInfo.api.getAbstractApi() instanceof AbstractFederationApi2) {
            AbstractFederationApi2 api = (AbstractFederationApi2) methodInfo.api.getAbstractApi();
            List<String> objs = api.getApiObjects();
            assert objs != null;
            assert !objs.contains(null) : "api.getApiObjects() may not contain null: " + objs;
            assert objs.get(0) != null : "api.getApiObjects() may not contain null: " + objs;
            List<String> reqs = api.getRequiredApiServices();
            objs.retainAll(reqs);
            if (objs.size() > 0) {
                assert objs.get(0) != null : "objs may not contain null: " + objs;
                return objs.get(0);
            }
            return api.getApiObjects().get(0);
        }
        return null;
    }

    public static String getChApi2ConvenientOrDefaultObject(ProbeController.MethodInfo methodInfo) {
        if (!methodInfo.annotation.convenienceMethodFor().isEmpty() &&
                !methodInfo.annotation.convenienceMethodObjectType().isEmpty())
            return methodInfo.annotation.convenienceMethodObjectType();

        return getChApi2DefaultObject(methodInfo);
    }

    public static AbstractFederationApi2 getChApi2(ProbeController.MethodInfo methodInfo) {
        if (methodInfo.api.getAbstractApi() instanceof AbstractFederationApi2) {
            AbstractFederationApi2 api = (AbstractFederationApi2) methodInfo.api.getAbstractApi();
            return api;
        }
        return null;
    }

    public static List<String> getChApi2Objects(ProbeController.MethodInfo methodInfo) {
        if (methodInfo.api.getAbstractApi() instanceof AbstractFederationApi2) {
            AbstractFederationApi2 fedapi = (AbstractFederationApi2) methodInfo.api.getAbstractApi();
            return fedapi.getApiObjects();
        }
        return null;
    }


}
