/**
 * Copyright (c) 2013 itemis AG (http://www.itemis.eu) and others.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 * 
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.xtend.lib.annotations;

import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.eclipse.xtend.lib.annotations.AccessorType;
import org.eclipse.xtend.lib.annotations.Accessors;
import org.eclipse.xtend.lib.annotations.AccessorsDeprecationPolicy;
import org.eclipse.xtend.lib.annotations.Data;
import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor;
import org.eclipse.xtend.lib.annotations.FinalFieldsConstructorProcessor;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.TransformationParticipant;
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference;
import org.eclipse.xtend.lib.macro.declaration.AnnotationTarget;
import org.eclipse.xtend.lib.macro.declaration.EnumerationValueDeclaration;
import org.eclipse.xtend.lib.macro.declaration.FieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMemberDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableParameterDeclaration;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend.lib.macro.declaration.Visibility;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
import org.eclipse.xtext.xbase.lib.Conversions;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.IterableExtensions;
import org.eclipse.xtext.xbase.lib.ListExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.lib.StringExtensions;

/**
 * @since 2.7
 * @noextend
 * @noreference
 */
@Beta
@SuppressWarnings("all")
public class AccessorsProcessor implements TransformationParticipant<MutableMemberDeclaration> {
  /**
   * @since 2.7
   * @noextend
   * @noreference
   */
  @Beta
  public static class Util {
    @Extension
    private TransformationContext context;
    
    public Util(final TransformationContext context) {
      this.context = context;
    }
    
    public Visibility toVisibility(final AccessorType type) {
      Visibility _switchResult = null;
      if (type != null) {
        switch (type) {
          case PUBLIC_GETTER:
            _switchResult = Visibility.PUBLIC;
            break;
          case PROTECTED_GETTER:
            _switchResult = Visibility.PROTECTED;
            break;
          case PACKAGE_GETTER:
            _switchResult = Visibility.DEFAULT;
            break;
          case PRIVATE_GETTER:
            _switchResult = Visibility.PRIVATE;
            break;
          case PUBLIC_SETTER:
            _switchResult = Visibility.PUBLIC;
            break;
          case PROTECTED_SETTER:
            _switchResult = Visibility.PROTECTED;
            break;
          case PACKAGE_SETTER:
            _switchResult = Visibility.DEFAULT;
            break;
          case PRIVATE_SETTER:
            _switchResult = Visibility.PRIVATE;
            break;
          default:
            StringConcatenation _builder = new StringConcatenation();
            _builder.append("Cannot convert ");
            _builder.append(type);
            throw new IllegalArgumentException(_builder.toString());
        }
      } else {
        StringConcatenation _builder = new StringConcatenation();
        _builder.append("Cannot convert ");
        _builder.append(type);
        throw new IllegalArgumentException(_builder.toString());
      }
      return _switchResult;
    }
    
    public boolean hasGetter(final FieldDeclaration it) {
      final Function1<String, Boolean> _function = (String name) -> {
        MethodDeclaration _findDeclaredMethod = it.getDeclaringType().findDeclaredMethod(name);
        return Boolean.valueOf((_findDeclaredMethod != null));
      };
      return IterableExtensions.<String>exists(this.getPossibleGetterNames(it), _function);
    }
    
    public boolean shouldAddGetter(final FieldDeclaration it) {
      return ((!this.hasGetter(it)) && (this.getGetterType(it) != AccessorType.NONE));
    }
    
    @SuppressWarnings("unchecked")
    public AccessorType getGetterType(final FieldDeclaration it) {
      AnnotationReference _elvis = null;
      AnnotationReference _accessorsAnnotation = this.getAccessorsAnnotation(it);
      if (_accessorsAnnotation != null) {
        _elvis = _accessorsAnnotation;
      } else {
        AnnotationReference _accessorsAnnotation_1 = this.getAccessorsAnnotation(it.getDeclaringType());
        _elvis = _accessorsAnnotation_1;
      }
      final AnnotationReference annotation = _elvis;
      if ((annotation != null)) {
        final Function1<EnumerationValueDeclaration, AccessorType> _function = (EnumerationValueDeclaration it_1) -> {
          return AccessorType.valueOf(it_1.getSimpleName());
        };
        final List<AccessorType> types = ListExtensions.<EnumerationValueDeclaration, AccessorType>map(((List<EnumerationValueDeclaration>)Conversions.doWrapArray(annotation.getEnumArrayValue("value"))), _function);
        AccessorType _elvis_1 = null;
        final Function1<AccessorType, Boolean> _function_1 = (AccessorType it_1) -> {
          return Boolean.valueOf(it_1.name().endsWith("GETTER"));
        };
        AccessorType _findFirst = IterableExtensions.<AccessorType>findFirst(types, _function_1);
        if (_findFirst != null) {
          _elvis_1 = _findFirst;
        } else {
          _elvis_1 = AccessorType.NONE;
        }
        return _elvis_1;
      }
      return null;
    }
    
    public AnnotationReference getAccessorsAnnotation(final AnnotationTarget it) {
      return it.findAnnotation(this.context.findTypeGlobally(Accessors.class));
    }
    
    public AnnotationReference getDeprecatedAnnotation(final AnnotationTarget it) {
      return it.findAnnotation(this.context.findTypeGlobally(Deprecated.class));
    }
    
    public AccessorsDeprecationPolicy getDeprecationPolicyAsEnum(final AnnotationReference annot) {
      return AccessorsDeprecationPolicy.valueOf(annot.getEnumValue("deprecationPolicy").getSimpleName());
    }
    
    public Object validateGetter(final MutableFieldDeclaration field) {
      return null;
    }
    
    public String getGetterName(final FieldDeclaration it) {
      return IterableExtensions.<String>head(this.getPossibleGetterNames(it));
    }
    
    public List<String> getPossibleGetterNames(final FieldDeclaration it) {
      final ArrayList<String> names = CollectionLiterals.<String>newArrayList();
      if ((((this.isBooleanType(this.orObject(it.getType())) && it.getSimpleName().startsWith("is")) && (it.getSimpleName().length() > 2)) && Character.isUpperCase(it.getSimpleName().charAt(2)))) {
        String _simpleName = it.getSimpleName();
        names.add(_simpleName);
      }
      List<String> _xifexpression = null;
      boolean _isBooleanType = this.isBooleanType(this.orObject(it.getType()));
      if (_isBooleanType) {
        _xifexpression = Collections.<String>unmodifiableList(CollectionLiterals.<String>newArrayList("is", "get"));
      } else {
        _xifexpression = Collections.<String>unmodifiableList(CollectionLiterals.<String>newArrayList("get"));
      }
      final Function1<String, String> _function = (String prefix) -> {
        String _firstUpper = StringExtensions.toFirstUpper(it.getSimpleName());
        return (prefix + _firstUpper);
      };
      names.addAll(ListExtensions.<String, String>map(_xifexpression, _function));
      return names;
    }
    
    public boolean isBooleanType(final TypeReference it) {
      return ((!it.isInferred()) && Objects.equal(it, this.context.getPrimitiveBoolean()));
    }
    
    public void addGetter(final MutableFieldDeclaration field, final Visibility visibility) {
      this.validateGetter(field);
      field.markAsRead();
      final Procedure1<MutableMethodDeclaration> _function = (MutableMethodDeclaration it) -> {
        this.context.setPrimarySourceElement(it, this.context.getPrimarySourceElement(field));
        it.addAnnotation(this.context.newAnnotationReference(Pure.class));
        final Iterable<? extends MethodDeclaration> superGetters = it.getOverriddenOrImplementedMethods();
        boolean _isEmpty = IterableExtensions.isEmpty(superGetters);
        boolean _not = (!_isEmpty);
        if (_not) {
          final Function1<MethodDeclaration, Boolean> _function_1 = (MethodDeclaration it_1) -> {
            return Boolean.valueOf(it_1.isFinal());
          };
          boolean _exists = IterableExtensions.exists(superGetters, _function_1);
          if (_exists) {
            StringConcatenation _builder = new StringConcatenation();
            _builder.append("Adding a getter to the field ");
            String _simpleName = field.getSimpleName();
            _builder.append(_simpleName);
            _builder.append(" would override a final method.");
            this.context.addError(field, _builder.toString());
          } else {
            it.addAnnotation(this.context.newAnnotationReference(Override.class));
          }
        }
        final AnnotationReference annot = this.getAccessorsAnnotation(field);
        if ((annot != null)) {
          AccessorsDeprecationPolicy _deprecationPolicyAsEnum = this.getDeprecationPolicyAsEnum(annot);
          if (_deprecationPolicyAsEnum != null) {
            switch (_deprecationPolicyAsEnum) {
              case ALWAYS:
              case ONLY_GETTER:
                it.addAnnotation(this.context.newAnnotationReference(Deprecated.class));
                break;
              case SAME_AS_FIELD:
                AnnotationReference _deprecatedAnnotation = this.getDeprecatedAnnotation(field);
                boolean _tripleNotEquals = (_deprecatedAnnotation != null);
                if (_tripleNotEquals) {
                  it.addAnnotation(this.context.newAnnotationReference(Deprecated.class));
                }
                break;
              case ONLY_SETTER:
              case NEVER:
                break;
              default:
                StringConcatenation _builder_1 = new StringConcatenation();
                _builder_1.append("Cannot determine deprecation policy for field ");
                String _simpleName_1 = field.getSimpleName();
                _builder_1.append(_simpleName_1);
                throw new IllegalArgumentException(_builder_1.toString());
            }
          } else {
            StringConcatenation _builder_1 = new StringConcatenation();
            _builder_1.append("Cannot determine deprecation policy for field ");
            String _simpleName_1 = field.getSimpleName();
            _builder_1.append(_simpleName_1);
            throw new IllegalArgumentException(_builder_1.toString());
          }
        }
        it.setReturnType(this.orObject(field.getType()));
        StringConcatenationClient _client = new StringConcatenationClient() {
          @Override
          protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
            _builder.append("return ");
            Object _fieldOwner = Util.this.fieldOwner(field);
            _builder.append(_fieldOwner);
            _builder.append(".");
            String _simpleName = field.getSimpleName();
            _builder.append(_simpleName);
            _builder.append(";");
          }
        };
        it.setBody(_client);
        it.setStatic(field.isStatic());
        it.setVisibility(visibility);
      };
      field.getDeclaringType().addMethod(this.getGetterName(field), _function);
    }
    
    @SuppressWarnings("unchecked")
    public AccessorType getSetterType(final FieldDeclaration it) {
      AnnotationReference _elvis = null;
      AnnotationReference _accessorsAnnotation = this.getAccessorsAnnotation(it);
      if (_accessorsAnnotation != null) {
        _elvis = _accessorsAnnotation;
      } else {
        AnnotationReference _accessorsAnnotation_1 = this.getAccessorsAnnotation(it.getDeclaringType());
        _elvis = _accessorsAnnotation_1;
      }
      final AnnotationReference annotation = _elvis;
      if ((annotation != null)) {
        final Function1<EnumerationValueDeclaration, AccessorType> _function = (EnumerationValueDeclaration it_1) -> {
          return AccessorType.valueOf(it_1.getSimpleName());
        };
        final List<AccessorType> types = ListExtensions.<EnumerationValueDeclaration, AccessorType>map(((List<EnumerationValueDeclaration>)Conversions.doWrapArray(annotation.getEnumArrayValue("value"))), _function);
        AccessorType _elvis_1 = null;
        final Function1<AccessorType, Boolean> _function_1 = (AccessorType it_1) -> {
          return Boolean.valueOf(it_1.name().endsWith("SETTER"));
        };
        AccessorType _findFirst = IterableExtensions.<AccessorType>findFirst(types, _function_1);
        if (_findFirst != null) {
          _elvis_1 = _findFirst;
        } else {
          _elvis_1 = AccessorType.NONE;
        }
        return _elvis_1;
      }
      return null;
    }
    
    private Object fieldOwner(final MutableFieldDeclaration it) {
      Object _xifexpression = null;
      boolean _isStatic = it.isStatic();
      if (_isStatic) {
        _xifexpression = this.context.newTypeReference(it.getDeclaringType());
      } else {
        _xifexpression = "this";
      }
      return _xifexpression;
    }
    
    public boolean hasSetter(final FieldDeclaration it) {
      MethodDeclaration _findDeclaredMethod = it.getDeclaringType().findDeclaredMethod(this.getSetterName(it), this.orObject(it.getType()));
      return (_findDeclaredMethod != null);
    }
    
    public String getSetterName(final FieldDeclaration it) {
      String _firstUpper = StringExtensions.toFirstUpper(it.getSimpleName());
      return ("set" + _firstUpper);
    }
    
    public boolean shouldAddSetter(final FieldDeclaration it) {
      return (((!it.isFinal()) && (!this.hasSetter(it))) && (this.getSetterType(it) != AccessorType.NONE));
    }
    
    public void validateSetter(final MutableFieldDeclaration field) {
      boolean _isFinal = field.isFinal();
      if (_isFinal) {
        this.context.addError(field, "Cannot set a final field");
      }
      if (((field.getType() == null) || field.getType().isInferred())) {
        this.context.addError(field, "Type cannot be inferred.");
        return;
      }
    }
    
    public void addSetter(final MutableFieldDeclaration field, final Visibility visibility) {
      this.validateSetter(field);
      final Procedure1<MutableMethodDeclaration> _function = (MutableMethodDeclaration it) -> {
        this.context.setPrimarySourceElement(it, this.context.getPrimarySourceElement(field));
        it.setReturnType(this.context.getPrimitiveVoid());
        final Iterable<? extends MethodDeclaration> superSetters = it.getOverriddenOrImplementedMethods();
        boolean _isEmpty = IterableExtensions.isEmpty(superSetters);
        boolean _not = (!_isEmpty);
        if (_not) {
          final Function1<MethodDeclaration, Boolean> _function_1 = (MethodDeclaration it_1) -> {
            return Boolean.valueOf(it_1.isFinal());
          };
          boolean _exists = IterableExtensions.exists(superSetters, _function_1);
          if (_exists) {
            StringConcatenation _builder = new StringConcatenation();
            _builder.append("Adding a setter to the field ");
            String _simpleName = field.getSimpleName();
            _builder.append(_simpleName);
            _builder.append(" would override a final method.");
            this.context.addError(field, _builder.toString());
          } else {
            it.addAnnotation(this.context.newAnnotationReference(Override.class));
          }
        }
        final AnnotationReference annot = this.getAccessorsAnnotation(field);
        if ((annot != null)) {
          AccessorsDeprecationPolicy _deprecationPolicyAsEnum = this.getDeprecationPolicyAsEnum(annot);
          if (_deprecationPolicyAsEnum != null) {
            switch (_deprecationPolicyAsEnum) {
              case ALWAYS:
              case ONLY_SETTER:
                it.addAnnotation(this.context.newAnnotationReference(Deprecated.class));
                break;
              case SAME_AS_FIELD:
                AnnotationReference _deprecatedAnnotation = this.getDeprecatedAnnotation(field);
                boolean _tripleNotEquals = (_deprecatedAnnotation != null);
                if (_tripleNotEquals) {
                  it.addAnnotation(this.context.newAnnotationReference(Deprecated.class));
                }
                break;
              case ONLY_GETTER:
              case NEVER:
                break;
              default:
                StringConcatenation _builder_1 = new StringConcatenation();
                _builder_1.append("Cannot determine deprecation policy for field ");
                String _simpleName_1 = field.getSimpleName();
                _builder_1.append(_simpleName_1);
                throw new IllegalArgumentException(_builder_1.toString());
            }
          } else {
            StringConcatenation _builder_1 = new StringConcatenation();
            _builder_1.append("Cannot determine deprecation policy for field ");
            String _simpleName_1 = field.getSimpleName();
            _builder_1.append(_simpleName_1);
            throw new IllegalArgumentException(_builder_1.toString());
          }
        }
        final MutableParameterDeclaration param = it.addParameter(field.getSimpleName(), this.orObject(field.getType()));
        StringConcatenationClient _client = new StringConcatenationClient() {
          @Override
          protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
            Object _fieldOwner = Util.this.fieldOwner(field);
            _builder.append(_fieldOwner);
            _builder.append(".");
            String _simpleName = field.getSimpleName();
            _builder.append(_simpleName);
            _builder.append(" = ");
            String _simpleName_1 = param.getSimpleName();
            _builder.append(_simpleName_1);
            _builder.append(";");
          }
        };
        it.setBody(_client);
        it.setStatic(field.isStatic());
        it.setVisibility(visibility);
      };
      field.getDeclaringType().addMethod(this.getSetterName(field), _function);
    }
    
    private TypeReference orObject(final TypeReference ref) {
      TypeReference _xifexpression = null;
      if ((ref == null)) {
        _xifexpression = this.context.getObject();
      } else {
        _xifexpression = ref;
      }
      return _xifexpression;
    }
  }
  
  @Override
  public void doTransform(final List<? extends MutableMemberDeclaration> elements, @Extension final TransformationContext context) {
    final Consumer<MutableMemberDeclaration> _function = (MutableMemberDeclaration it) -> {
      this.transform(it, context);
    };
    elements.forEach(_function);
  }
  
  protected void _transform(final MutableFieldDeclaration it, @Extension final TransformationContext context) {
    @Extension
    final AccessorsProcessor.Util util = new AccessorsProcessor.Util(context);
    final AnnotationReference annot = util.getAccessorsAnnotation(it);
    boolean _shouldAddGetter = util.shouldAddGetter(it);
    if (_shouldAddGetter) {
      util.addGetter(it, util.toVisibility(util.getGetterType(it)));
    } else {
      if (((annot != null) && (util.getDeprecationPolicyAsEnum(annot) == AccessorsDeprecationPolicy.ONLY_GETTER))) {
        StringConcatenation _builder = new StringConcatenation();
        _builder.append("Field ");
        String _simpleName = it.getSimpleName();
        _builder.append(_simpleName);
        _builder.append(" needs no getter, but deprecationPolicy is ONLY_GETTER.");
        _builder.newLineIfNotEmpty();
        _builder.append("Explicitly setting it has no effect, as no getter will be generated.");
        _builder.newLine();
        _builder.append("Use deprecation policy NEVER to disable accessors deprecation and remove this warning.");
        _builder.newLine();
        context.addWarning(it, _builder.toString());
      }
    }
    boolean _shouldAddSetter = util.shouldAddSetter(it);
    if (_shouldAddSetter) {
      util.addSetter(it, util.toVisibility(util.getSetterType(it)));
    } else {
      if (((annot != null) && (util.getDeprecationPolicyAsEnum(annot) == AccessorsDeprecationPolicy.ONLY_SETTER))) {
        StringConcatenation _builder_1 = new StringConcatenation();
        _builder_1.append("Field ");
        String _simpleName_1 = it.getSimpleName();
        _builder_1.append(_simpleName_1);
        _builder_1.append(" needs no setter, but deprecationPolicy is ONLY_SETTER.");
        _builder_1.newLineIfNotEmpty();
        _builder_1.append("Explicitly setting it has no effect, as no setter will be generated.");
        _builder_1.newLine();
        _builder_1.append("Use deprecation policy NEVER to disable accessors deprecation and remove this warning.");
        _builder_1.newLine();
        context.addWarning(it, _builder_1.toString());
      }
    }
  }
  
  protected void _transform(final MutableClassDeclaration it, @Extension final TransformationContext context) {
    AnnotationReference _findAnnotation = it.findAnnotation(context.findTypeGlobally(Data.class));
    boolean _tripleNotEquals = (_findAnnotation != null);
    if (_tripleNotEquals) {
      return;
    }
    @Extension
    final FinalFieldsConstructorProcessor.Util requiredArgsUtil = new FinalFieldsConstructorProcessor.Util(context);
    if ((requiredArgsUtil.needsFinalFieldConstructor(it) || (it.findAnnotation(context.findTypeGlobally(FinalFieldsConstructor.class)) != null))) {
      requiredArgsUtil.addFinalFieldsConstructor(it);
    }
    final Function1<MutableFieldDeclaration, Boolean> _function = (MutableFieldDeclaration it_1) -> {
      return Boolean.valueOf(((!it_1.isStatic()) && context.isThePrimaryGeneratedJavaElement(it_1)));
    };
    final Consumer<MutableFieldDeclaration> _function_1 = (MutableFieldDeclaration it_1) -> {
      this._transform(it_1, context);
    };
    IterableExtensions.filter(it.getDeclaredFields(), _function).forEach(_function_1);
  }
  
  public void transform(final MutableMemberDeclaration it, final TransformationContext context) {
    if (it instanceof MutableClassDeclaration) {
      _transform((MutableClassDeclaration)it, context);
      return;
    } else if (it instanceof MutableFieldDeclaration) {
      _transform((MutableFieldDeclaration)it, context);
      return;
    } else {
      throw new IllegalArgumentException("Unhandled parameter types: " +
        Arrays.<Object>asList(it, context).toString());
    }
  }
}