package com.airlock.iam.plugin.configeditor.data;

import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.annotation.property.TestletProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationVariable;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.testlet.Testlet;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.framework.application.configuration.DefaultPluginConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.vaadin.ui.UI;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.Property.ReadOnlyException;
import com.vaadin.v7.data.util.converter.Converter.ConversionException;
import com.vaadin.v7.ui.Field;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Optional;

public class ConfigurableProperty implements Property<Object> {
   private final PropertyAdapter<? extends Annotation> propertyAdapter;
   private boolean readOnly;

   public ConfigurableProperty(PropertyAdapter<? extends Annotation> propertyAdapter) {
      this.propertyAdapter = propertyAdapter;
      this.readOnly = this.determineReadOnlyState();
   }

   public Object getValue() {
      return this.propertyAdapter.getValue().orElse(this.propertyAdapter.getDefaultValue().orElse(null));
   }

   public boolean hasValue() {
      return this.getValue() != null;
   }

   public boolean isSensitive() {
      return this.getConfigFieldAnnotation() instanceof StringProperty stringProperty ? stringProperty.isSensitive() : false;
   }

   public boolean hasVariable() {
      return this.propertyAdapter.hasVariable();
   }

   public Optional<ConfigurationVariable> getVariable() {
      return this.propertyAdapter.getVariable();
   }

   public void setVariable(ConfigurationVariable variable) {
      this.propertyAdapter.setVariable(variable);
   }

   public void removeVariable() {
      this.propertyAdapter.unsetVariable();
   }

   public Annotation getConfigFieldAnnotation() {
      return this.propertyAdapter.getFieldAnnotation();
   }

   public GroupProperty getGroupPropertyAnnotation() {
      return this.isLicensed() ? this.propertyAdapter.getAnnotation(GroupProperty.class) : new GroupProperty() {
         public Class<? extends Annotation> annotationType() {
            return GroupProperty.class;
         }

         public String groupIdentifier() {
            return "Unlicensed";
         }
      };
   }

   public Groups getGroupsAnnotation() {
      try {
         return this.propertyAdapter.getPluginConfiguration().getPluginClass().getAnnotation(Groups.class);
      } catch (InitializeException e) {
         throw new IllegalStateException("Cannot load class " + this.propertyAdapter.getPluginConfiguration().getPluginClassName(), e);
      }
   }

   public boolean hasTestlet() {
      return this.propertyAdapter.getAnnotation(TestletProperty.class) != null;
   }

   public Testlet getTestlet() throws IllegalStateException {
      if (!this.hasTestlet()) {
         return null;
      }

      TestletProperty testlet = this.propertyAdapter.getAnnotation(TestletProperty.class);
      Class<? extends Testlet> testletClass = testlet.testletClass();

      try {
         return testletClass.getDeclaredConstructor().newInstance();
      } catch (LinkageError | ReflectiveOperationException e) {
         throw new IllegalStateException("Error creating a new instance of " + testletClass.getName() + ": " + e.getMessage(), e);
      }
   }

   public String getTestletToolTip() {
      if (!this.hasTestlet()) {
         return "";
      }

      TestletProperty testlet = this.propertyAdapter.getAnnotation(TestletProperty.class);
      return testlet.buttonToolTip();
   }

   public String getConfigPropertyName() {
      return this.propertyAdapter.getConfigPropertyName();
   }

   public boolean isSameConfigurationProperty(MutablePluginConfiguration pc, ConfigurationContext cc, List<String> propertyNames) {
      return this.propertyAdapter.isSameConfigurationProperty(pc, cc, propertyNames);
   }

   public void setValue(Object newValue) throws ReadOnlyException, ConversionException {
      if (!this.isReadOnly()) {
         this.propertyAdapter.setValue(newValue);
      }
   }

   public void overrideValue() {
      this.propertyAdapter.setValueInConfiguration(this.propertyAdapter.getValue().orElse(null));
      this.removeVariable();
   }

   public void removeProperty() {
      this.propertyAdapter.removeProperty();
   }

   public Class<?> getType() {
      return this.propertyAdapter.getConfigFieldType();
   }

   public boolean isReadOnly() {
      return this.readOnly;
   }

   public String getDisplayName() {
      return this.propertyAdapter.getDisplayName();
   }

   public final boolean isValueDefinedLocally() {
      return this.propertyAdapter.getValueStatus() == ConfigurableProperty.ValueStatus.DEFINED_LOCALLY
         || this.propertyAdapter.getValueStatus() == ConfigurableProperty.ValueStatus.DEFAULT_DEFINED_LOCALLY;
   }

   public ConfigurableProperty.ValueStatus getValueStatus() {
      return this.propertyAdapter.getValueStatus();
   }

   public void setReadOnly(boolean newStatus) {
      this.readOnly = newStatus;
   }

   @Override
   public String toString() {
      Object value = this.getValue();
      return value != null ? value.toString() : "";
   }

   public Field<?> createField() {
      return this.propertyAdapter.createField();
   }

   public MutablePluginConfiguration getPluginConfiguration() {
      return this.propertyAdapter.getPluginConfiguration();
   }

   public ConfigurationContext getConfigurationContext() {
      return this.propertyAdapter.getConfigurationContext();
   }

   public boolean isPropertyDefinedLocallyInEnvironmentAndContext() {
      return this.propertyAdapter.isPropertyDefinedLocallyInEnvironmentAndContext();
   }

   private boolean isLicensed() {
      String pluginClassName = this.propertyAdapter.getPluginConfiguration().getPluginClassName();
      String configPropertyName = this.propertyAdapter.getConfigPropertyName();
      return this.getConfigEditorApplication().getLicenseManager().isPropertyLicensed(pluginClassName, configPropertyName);
   }

   private ConfigEditorApplication getConfigEditorApplication() {
      return (ConfigEditorApplication)UI.getCurrent();
   }

   public final boolean determineReadOnlyState() {
      return this.isInherited();
   }

   public final boolean isInherited() {
      return this.propertyAdapter.isInherited();
   }

   public boolean hasInheritance() {
      return this.propertyAdapter.hasInheritance();
   }

   public boolean isMandatory() {
      return this.propertyAdapter.isMandatory();
   }

   boolean isPluginProperty() {
      return this.propertyAdapter.isPluginProperty();
   }

   void setDefaultValue() {
      if (!this.hasValue()) {
         if (this.isPluginProperty() && this.propertyAdapter.hasDefaultValueOnPluginPropertyAnnotation()) {
            MutablePluginConfiguration newValue = new DefaultPluginConfiguration(null, this.propertyAdapter.getDefaultValueOnAnnotation().getName());
            newValue.setSynthetic(true);
            this.setValue(newValue);
         }
      }
   }

   public boolean isValueFromDefault() {
      return this.propertyAdapter.isValueFromDefault();
   }

   public boolean isValueFromGlobalReference() {
      return this.propertyAdapter.isValueFromGlobalReference();
   }

   public enum ValueStatus {
      INHERITED_FROM_PARENT("Inherited from parent."),
      INHERITED_FROM_COMMON_ENVIRONMENT("Inherited from shared environment."),
      INHERITED_FROM_DEFAULTCONTEXT("Inherited from default context."),
      DEFINED_LOCALLY("Defined locally."),
      DEFAULT("Default value."),
      DEFAULT_DEFINED_LOCALLY("Default value"),
      UNDEFINED("Undefined.");

      private final String description;

      @SuppressFBWarnings(justification = "generated code")
      public String getDescription() {
         return this.description;
      }

      @SuppressFBWarnings(justification = "generated code")
      ValueStatus(final String description) {
         this.description = description;
      }
   }
}
