package be.iminds.ilabt.jfed.experimenter_gui.config;

import be.iminds.ilabt.jfed.experimenter_gui.editor.properties.map.ImageMap;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.lowlevel.fedmon.TestResult;
import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.rspec_source.AdvertisementRspecSource;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;

/**
 * JFedGuiConfig
 *
 * replaces JFedConfiguration with config that comes from server instead of file
 */
public interface JFedGuiConfig {
    String FLAG_SHOW_TUTORIAL_BUTTON = "showTutorialButton";
    String FLAG_SLA_AVAILABLE = "slaAvailable";
    String FLAG_QOE_REPORTING = "reportQOE";
    String FLAG_SHOW_REPUTATION = "showReputation";

    GtsConfig getGtsConfig();

    boolean hasFlag(String flagName);
    Boolean getBooleanFlag(String flagName);
    String getStringFlag(String flagName);
    Integer getIntegerFlag(String flagName);
    Long getLongFlag(String flagName);

    default boolean isShowTutorialButtonEnabled() {
        return Boolean.TRUE == getBooleanFlag(FLAG_SHOW_TUTORIAL_BUTTON);
    }
    default boolean isSlaAvailable() {
        return Boolean.TRUE == getBooleanFlag(FLAG_SLA_AVAILABLE);
    }
    default boolean isQOEReportingEnabled() {
        return Boolean.TRUE == getBooleanFlag(FLAG_QOE_REPORTING);
    }
    default boolean isShowReputationEnabled() {
        return Boolean.TRUE == getBooleanFlag(FLAG_SHOW_REPUTATION);
    }

    /**
     * guarantees:
     * The ResourceClass point to actual Resources with getResources and getDefaultResource
     * These Resources point to the same Server objects that TestbedInfoSource points to.
     * (So all server info is filled int, and they point to complete testbed and services objects)
     * */
    ResourceClass getResourceClass(String id);
    /**
     * guarantees:
     * The ResourceClass point to actual Resources with getResources and getDefaultResource
     * These Resources point to the same Server objects that TestbedInfoSource points to.
     * (So all server info is filled int, and they point to complete testbed and services objects)
     * */
    Collection<ResourceClass> getResourceClasses();

    /** return the list of Triggered messages. */
    @Nonnull List<TriggeredMessage> getTriggeredMessages();


    enum ResourceType {
        RAW_PC("Bare Metal"),
        VM("VMs"),
        OTHER("Other");

        ResourceType(String friendlyName) {
            this.friendlyName = friendlyName;
        }

        private final String friendlyName;

        public String getFriendlyName() {
            return friendlyName;
        }
    };
    /*
     * detection of specific resource types contained in a resourceClass
     * if the resourceClass is not a "leaf", this is the type of the default.
     *
     * (don't figure this out manually using the ResourceClass etc, let this class determine it.)
     * */
    ResourceType getResourceType(ResourceClass resourceClass);

    /** @return never null, but can return empty list. */
    Collection<ResourceClass> getResourceClassesByType(ResourceType resourceType);

    ResourceClass getDefaultResourceClass();



    /**
     * exactly the same Server objects that TestbedInfoSource has.
     * (So all server info is filled int, and they point to complete testbed and services objects)
     * */
    List<Server> getBindableServers();
    List<Server> getAllServersInVisibleResources();
    List<Server> getHiddenServers();


    ResourceClass findBestResourceClass(RspecNode node);
    List<ResourceClass> findAllPossibleResourceClasses(RspecNode node);


    Resource findBestResource(@Nullable ResourceClass resourceClass, RspecNode node, @Nullable Server overrideComponentManager);
    List<Resource> findAllPossibleResources(@Nullable ResourceClass resourceClass, RspecNode node, @Nullable Server overrideComponentManager);

    //same but with null parameters dropped
    default Resource findBestResource(@Nullable ResourceClass resourceClass, RspecNode node) {
        return findBestResource(resourceClass, node, null);
    }
    default List<Resource> findAllPossibleResource(@Nullable ResourceClass resourceClass, RspecNode node) {
        return findAllPossibleResources(resourceClass, node, null);
    }
    default Resource findBestResource(RspecNode node, @Nullable Server overrideComponentManager) {
        return findBestResource(null, node, overrideComponentManager);
    }
    default List<Resource> findAllPossibleResource(RspecNode node, @Nullable Server overrideComponentManager) {
        return findAllPossibleResources(null, node, overrideComponentManager);
    }
    default Resource findBestResource(RspecNode node) {
        return findBestResource(null, node, null);
    }
    default List<Resource> findAllPossibleResource(RspecNode node) {
        return findAllPossibleResources(null, node, null);
    }

    /**
     * pick a random default Resource for the resourceClass.
     * For each jFed session the same is always chosen.
     * */
    Resource getDefaultResource(ResourceClass resourceClass);


    //TestbedAdvertisementCache functionality:
    /**
     * Find out if a node for this sliver type should be exclusive or not.
     * Ideally this info should come from a cache of advertisement RSpecs.
     * */
    Boolean getNodeExclusivity(GeniUrn componentManagerUrn, String sliverTypeName);

    /*
       callers don't need to care where this info comes from.
       Note: These are JavaFX ListProperty, so you can listen to changes.
       These are ReadOnlyListProperty, so you can't change them using these accessors.
     */
    enum Scope {
        ALL,
        AVAILABLE_ONLY;

        public static Scope getScopeForAvailable(boolean available) {
            return available ? AVAILABLE_ONLY : ALL;
        }
        public static Scope getScopeForAll(boolean all) {
            return !all ? AVAILABLE_ONLY : ALL;
        }
    };
    boolean hasUpToDateComponentInfo(Server server, Scope scope);
    ReadOnlyListProperty<JFedGuiConfigImpl.ComponentInfo> getComponentInfo(Server server, Scope scope);
    ReadOnlyListProperty<String> getSliverTypeNames(Server server);
    ReadOnlyListProperty<HardwareTypeInfo> getHardwareTypes(Server server, Scope scope);
    ReadOnlyListProperty<DiskImage> getDiskImages(Server server, @Nullable String sliverTypeName);
    default ReadOnlyListProperty<DiskImage> getDiskImages(Server server) {
        return getDiskImages(server, null);
    }

    //TODO: register new advertisement info?  very probably does not need to be in this interface:
    //        AdvertisementFetchManager should make sure the actual implementation gets the info somehow

    /** Get advertisement. Note: it is preferable to get the info from it using the getters above, but this is available for cases that is not enough.
     * use AdvertisementFetchManager to request advertisement refresh/initial fetch
     * you can add listeners to this property, it will get updated.
     *
     * There is no need tpo manually set this ever. The AdvertisementFetchManager is responsible for this,
     * and will do this BEFORE completing it's fetch task and calling any listeners of that Task.
     *
     * @param availableOnly if set, specifies which advertisement (available or all) is needed. If not set, any will be returned,
     *                      with a preference for the "all" advertisement.
     *
     * @return never null, but the value of the property can be null
     */
    ReadOnlyObjectProperty<AdvertisementRspecSource> getAdvertisement(Server server, @Nullable Boolean availableOnly);


    //NodesVisualization functionality:
    //TODO fedmon-v3 refactor: for now this will work using the old system underneath
    enum NodesVisualization {
        MAP,  /* nodes are visualized on a world map */
        IMAGE /* nodes are visualized on an image, on which they all have defined coordinates */
    }
    NodesVisualization getNodesVisualization(Server server);
    ImageMap<GeniUrn> getImageMap(Server server);


    //HealthStatus functionality
    ServerGlimpse getServerGlimpse(Server server);




    //various helpers/converters
    Color getColor(Server server);
    Color getColor(Testbed testbed);
    Color getColor(String colorName);

    public static final Comparator<ComponentInfo> COMPONENT_INFO_BY_ID_COMPARATOR = new Comparator<ComponentInfo>() {
        @Override
        public int compare(ComponentInfo o1, ComponentInfo o2) {
            String s1 = o1.getName() == null ? o1.getUrn().toString() : o1.getName();
            String s2 = o2.getName() == null ? o2.getUrn().toString() : o2.getName();

            if (s1 == null || s2 == null) {
                throw new NullPointerException("ComponentInfo must always have some ID");
            }
            return s1.compareTo(s2);
        }
    };

    public static class HardwareTypeInfo implements Comparable {
        private final String hardwareTypeName;
        private final Boolean available;

        public HardwareTypeInfo(String hardwareTypeName) {
            this(hardwareTypeName, null);
        }

        public HardwareTypeInfo(String hardwareTypeName, Boolean available) {
            assert hardwareTypeName != null;
            this.hardwareTypeName = hardwareTypeName;
            this.available = available;
        }

        public String getHardwareTypeName() {
            return hardwareTypeName;
        }

        public Boolean isAvailable() {
            return available;
        }

        @Override
        public int compareTo(Object o) {
            return hardwareTypeName.compareTo(((HardwareTypeInfo) o).hardwareTypeName);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            HardwareTypeInfo that = (HardwareTypeInfo) o;

            if (!hardwareTypeName.equals(that.hardwareTypeName)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return hardwareTypeName.hashCode();
        }

        @Override
        public String toString() {
            return hardwareTypeName;
        }
    }

    public static class ComponentInfo implements Comparable {
        private static final Logger LOG = LoggerFactory.getLogger(ComponentInfo.class);

        private final String name;
        private final GeniUrn urn;
        private final NodeLocation nodeLocation;
        private final boolean available;
        private final Boolean nodeLoginTestSuccess;
        private final List<String> hardwareTypeNames;

        public ComponentInfo(String name, GeniUrn urn, boolean available, Boolean nodeLoginTestSuccess, NodeLocation nodeLocation, List<String> hardwareTypeNames) {
            //we need at least one identifier
            assert name != null || urn != null;

            this.name = name;
            this.urn = urn;
            this.available = available;
            this.nodeLoginTestSuccess = nodeLoginTestSuccess;
            this.nodeLocation = nodeLocation;
            this.hardwareTypeNames = hardwareTypeNames;
        }

        public String getName() {
            return name;
        }

        public GeniUrn getUrn() {
            return urn;
        }

        public boolean isAvailableInRspec() {
            return available;
        }

        public boolean isAvailable() {
            if (nodeLoginTestSuccess == Boolean.FALSE)
                return false;
            return available;
        }

        public Boolean isNodeLoginTestSuccess() {
            return nodeLoginTestSuccess;
        }

        public List<String> getHardwareTypeNames() {
            return this.hardwareTypeNames;
        }

        public boolean hasHardwareType(String hardwareTypeName) {
            boolean res;
            if (hardwareTypeNames == null)
                res = false;
            else
                res = hardwareTypeNames.contains(hardwareTypeName);
//            LOG.debug("hasHardwareType() node="+name+" res="+res+" hardwareTypeNames="+hardwareTypeNames);
            return res;
        }

        @Override
        public int compareTo(Object o) {
            if (!(o instanceof ComponentInfo))
                throw new RuntimeException("Can't compare ComponentInfo to "+o.getClass().getName());

            ComponentInfo ci = (ComponentInfo) o;
            String s1 = this.getName() == null ? this.getUrn().toString() : this.getName();
            String s2 = ci.getName() == null ? ci.getUrn().toString() : ci.getName();

            if (s1 == null || s2 == null) {
                throw new NullPointerException("ComponentInfo must always have some ID");
            }
            return s1.compareTo(s2);
        }


        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ComponentInfo that = (ComponentInfo) o;

            if (name != null ? !name.equals(that.name) : that.name != null) return false;
            if (urn != null ? !urn.equals(that.urn) : that.urn != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + (urn != null ? urn.hashCode() : 0);
            return result;
        }

        @Override
        public String toString() {
            if (name != null)
                return name;
            return urn.getValue();
        }

        public NodeLocation getLocation() {
            return nodeLocation;
        }

        public boolean hasLocation() {
            return nodeLocation != null
                    && nodeLocation.getLatitude() != null
                    && nodeLocation.getLongitude() != null;
        }
    }
    /* something like:
    String colorName = server.getTestbed() == null ? null : server.getTestbed().getColor();

                if (colorName == null) colorName = "transparent";
                try {
                    color = Color.valueOf(colorName.toUpperCase());
                } catch (IllegalArgumentException ex) {
                    LOG.warn("Cannot convert color {}", testbedDesc.getString(TESTBED_COLOR, "transparent"), ex);
                }
    * */


    //provide info from which advertisement info such as sliver types is then derived
    public void setAdvertisementRspec(Integer serverId, boolean availableAdvertisementRspec, List<? extends RspecNode> nodes, Date fetchDate);
    public void setServerGlimpse(Integer serverId, ServerGlimpse serverGlimpse);
    public void setFedmonNodeTestInfo(Integer serverId, Map<GeniUrn, TestResult> fedmonResults);
    public void initResourceClassAdvertisementInfo();

    public ComponentInfo addUserComponentInfo(Integer serverId, GeniUrn componentId);
    public HardwareTypeInfo addUserHardwareTypeInfo(Integer serverId, String hardwareTypeName, Boolean available);
    public void clearUserInfo(Integer serverId);
}

