/**
 * Copyright (c) 2018 TypeFox and others.
 * 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * 
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */
package org.eclipse.lsp4j.generator;

import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import org.eclipse.lsp4j.generator.TypeAdapterImpl;
import org.eclipse.xtend.lib.macro.AbstractClassProcessor;
import org.eclipse.xtend.lib.macro.RegisterGlobalsContext;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference;
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.FieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableConstructorDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableTypeParameterDeclaration;
import org.eclipse.xtend.lib.macro.declaration.Type;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend.lib.macro.declaration.Visibility;
import org.eclipse.xtend2.lib.StringConcatenationClient;
import org.eclipse.xtext.xbase.lib.CollectionLiterals;
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.Procedures.Procedure1;
import org.eclipse.xtext.xbase.lib.StringExtensions;

@SuppressWarnings("all")
public class TypeAdapterImplProcessor extends AbstractClassProcessor {
  @Override
  public void doRegisterGlobals(final ClassDeclaration annotatedClass, @Extension final RegisterGlobalsContext context) {
    String _qualifiedName = annotatedClass.getQualifiedName();
    String _plus = (_qualifiedName + ".Factory");
    context.registerClass(_plus);
  }
  
  @Override
  public void doTransform(final MutableClassDeclaration annotatedClass, @Extension final TransformationContext context) {
    final AnnotationReference typeAdapterImplAnnotation = annotatedClass.findAnnotation(context.findTypeGlobally(TypeAdapterImpl.class));
    final TypeReference targetType = typeAdapterImplAnnotation.getClassValue("value");
    this.generateImpl(annotatedClass, targetType, context);
    String _qualifiedName = annotatedClass.getQualifiedName();
    String _plus = (_qualifiedName + ".Factory");
    this.generateFactory(context.findClass(_plus), annotatedClass, targetType, context);
  }
  
  protected MutableClassDeclaration generateImpl(final MutableClassDeclaration impl, final TypeReference targetType, @Extension final TransformationContext context) {
    final ArrayList<FieldDeclaration> targetFields = this.getTargetFields(targetType, context);
    final Function1<FieldDeclaration, Boolean> _function = (FieldDeclaration it) -> {
      return Boolean.valueOf(((!it.getType().isPrimitive()) && (!it.getType().getActualTypeArguments().isEmpty())));
    };
    Iterable<FieldDeclaration> _filter = IterableExtensions.<FieldDeclaration>filter(targetFields, _function);
    for (final FieldDeclaration field : _filter) {
      String _upperCase = field.getSimpleName().toUpperCase();
      String _plus = (_upperCase + "_TYPE_TOKEN");
      final Procedure1<MutableFieldDeclaration> _function_1 = (MutableFieldDeclaration it) -> {
        it.setFinal(true);
        it.setStatic(true);
        it.setType(context.newTypeReference("com.google.gson.reflect.TypeToken", field.getType()));
        StringConcatenationClient _client = new StringConcatenationClient() {
          @Override
          protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
            _builder.append("new TypeToken<");
            TypeReference _type = field.getType();
            _builder.append(_type);
            _builder.append(">() {}");
          }
        };
        it.setInitializer(_client);
      };
      impl.addField(_plus, _function_1);
    }
    impl.setExtendedClass(context.newTypeReference("com.google.gson.TypeAdapter", targetType));
    final Procedure1<MutableFieldDeclaration> _function_2 = (MutableFieldDeclaration it) -> {
      it.setType(context.newTypeReference("com.google.gson.Gson"));
      it.setFinal(true);
    };
    impl.addField("gson", _function_2);
    final Procedure1<MutableConstructorDeclaration> _function_3 = (MutableConstructorDeclaration it) -> {
      it.addParameter("gson", context.newTypeReference("com.google.gson.Gson"));
      StringConcatenationClient _client = new StringConcatenationClient() {
        @Override
        protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
          _builder.append("this.gson = gson;");
          _builder.newLine();
        }
      };
      it.setBody(_client);
    };
    impl.addConstructor(_function_3);
    final Procedure1<MutableMethodDeclaration> _function_4 = (MutableMethodDeclaration method) -> {
      method.addParameter("in", context.newTypeReference("com.google.gson.stream.JsonReader"));
      method.setExceptions(context.newTypeReference(IOException.class));
      method.setReturnType(targetType);
      StringConcatenationClient _client = new StringConcatenationClient() {
        @Override
        protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
          TypeReference _newTypeReference = context.newTypeReference("com.google.gson.stream.JsonToken");
          _builder.append(_newTypeReference);
          _builder.append(" nextToken = in.peek();");
          _builder.newLineIfNotEmpty();
          _builder.append("if (nextToken == JsonToken.NULL) {");
          _builder.newLine();
          _builder.append("\t");
          _builder.append("return null;");
          _builder.newLine();
          _builder.append("}");
          _builder.newLine();
          _builder.newLine();
          _builder.append(targetType);
          _builder.append(" result = new ");
          _builder.append(targetType);
          _builder.append("();");
          _builder.newLineIfNotEmpty();
          _builder.append("in.beginObject();");
          _builder.newLine();
          _builder.append("while (in.hasNext()) {");
          _builder.newLine();
          _builder.append("\t");
          _builder.append("String name = in.nextName();");
          _builder.newLine();
          _builder.append("\t");
          _builder.append("switch (name) {");
          _builder.newLine();
          {
            for(final FieldDeclaration field : targetFields) {
              _builder.append("\t");
              _builder.append("case \"");
              String _simpleName = field.getSimpleName();
              _builder.append(_simpleName, "\t");
              _builder.append("\":");
              _builder.newLineIfNotEmpty();
              _builder.append("\t");
              _builder.append("\t");
              _builder.append("result.set");
              String _firstUpper = StringExtensions.toFirstUpper(field.getSimpleName());
              _builder.append(_firstUpper, "\t\t");
              _builder.append("(read");
              String _firstUpper_1 = StringExtensions.toFirstUpper(field.getSimpleName());
              _builder.append(_firstUpper_1, "\t\t");
              _builder.append("(in));");
              _builder.newLineIfNotEmpty();
              _builder.append("\t");
              _builder.append("\t");
              _builder.append("break;");
              _builder.newLine();
            }
          }
          _builder.append("\t");
          _builder.append("default:");
          _builder.newLine();
          _builder.append("\t\t");
          _builder.append("in.skipValue();");
          _builder.newLine();
          _builder.append("\t");
          _builder.append("}");
          _builder.newLine();
          _builder.append("}");
          _builder.newLine();
          _builder.append("in.endObject();");
          _builder.newLine();
          _builder.append("return result;");
          _builder.newLine();
        }
      };
      method.setBody(_client);
    };
    impl.addMethod("read", _function_4);
    for (final FieldDeclaration field_1 : targetFields) {
      {
        String _firstUpper = StringExtensions.toFirstUpper(field_1.getSimpleName());
        String _plus_1 = ("read" + _firstUpper);
        final MutableMethodDeclaration existingMethod = impl.findDeclaredMethod(_plus_1, 
          context.newTypeReference("com.google.gson.stream.JsonReader"));
        if ((existingMethod == null)) {
          String _firstUpper_1 = StringExtensions.toFirstUpper(field_1.getSimpleName());
          String _plus_2 = ("read" + _firstUpper_1);
          final Procedure1<MutableMethodDeclaration> _function_5 = (MutableMethodDeclaration it) -> {
            it.setVisibility(Visibility.PROTECTED);
            it.addParameter("in", context.newTypeReference("com.google.gson.stream.JsonReader"));
            it.setExceptions(context.newTypeReference(IOException.class));
            it.setReturnType(field_1.getType());
            boolean _isPrimitive = field_1.getType().isPrimitive();
            if (_isPrimitive) {
              String _simpleName = field_1.getType().getSimpleName();
              if (_simpleName != null) {
                switch (_simpleName) {
                  case "boolean":
                    StringConcatenationClient _client = new StringConcatenationClient() {
                      @Override
                      protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                        _builder.append("return in.nextBoolean();");
                      }
                    };
                    it.setBody(_client);
                    break;
                  case "double":
                    StringConcatenationClient _client_1 = new StringConcatenationClient() {
                      @Override
                      protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                        _builder.append("return in.nextDouble();");
                      }
                    };
                    it.setBody(_client_1);
                    break;
                  case "float":
                    StringConcatenationClient _client_2 = new StringConcatenationClient() {
                      @Override
                      protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                        _builder.append("return (float) in.nextDouble();");
                      }
                    };
                    it.setBody(_client_2);
                    break;
                  case "long":
                    StringConcatenationClient _client_3 = new StringConcatenationClient() {
                      @Override
                      protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                        _builder.append("return in.nextLong();");
                      }
                    };
                    it.setBody(_client_3);
                    break;
                  case "int":
                    StringConcatenationClient _client_4 = new StringConcatenationClient() {
                      @Override
                      protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                        _builder.append("return in.nextInt();");
                      }
                    };
                    it.setBody(_client_4);
                    break;
                  case "short":
                  case "byte":
                  case "char":
                    StringConcatenationClient _client_5 = new StringConcatenationClient() {
                      @Override
                      protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                        _builder.append("return (");
                        TypeReference _type = field_1.getType();
                        _builder.append(_type);
                        _builder.append(") in.nextInt();");
                      }
                    };
                    it.setBody(_client_5);
                    break;
                }
              }
            } else {
              boolean _isEmpty = field_1.getType().getActualTypeArguments().isEmpty();
              boolean _not = (!_isEmpty);
              if (_not) {
                StringConcatenationClient _client_6 = new StringConcatenationClient() {
                  @Override
                  protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                    _builder.append("return gson.fromJson(in, ");
                    String _upperCase = field_1.getSimpleName().toUpperCase();
                    _builder.append(_upperCase);
                    _builder.append("_TYPE_TOKEN.getType());");
                  }
                };
                it.setBody(_client_6);
              } else {
                StringConcatenationClient _client_7 = new StringConcatenationClient() {
                  @Override
                  protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                    _builder.append("return gson.fromJson(in, ");
                    TypeReference _type = field_1.getType();
                    _builder.append(_type);
                    _builder.append(".class);");
                  }
                };
                it.setBody(_client_7);
              }
            }
          };
          impl.addMethod(_plus_2, _function_5);
        }
      }
    }
    final Procedure1<MutableMethodDeclaration> _function_5 = (MutableMethodDeclaration method) -> {
      method.addParameter("out", context.newTypeReference("com.google.gson.stream.JsonWriter"));
      method.addParameter("value", targetType);
      method.setExceptions(context.newTypeReference(IOException.class));
      StringConcatenationClient _client = new StringConcatenationClient() {
        @Override
        protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
          _builder.append("if (value == null) {");
          _builder.newLine();
          _builder.append("\t");
          _builder.append("out.nullValue();");
          _builder.newLine();
          _builder.append("\t");
          _builder.append("return;");
          _builder.newLine();
          _builder.append("}");
          _builder.newLine();
          _builder.newLine();
          _builder.append("out.beginObject();");
          _builder.newLine();
          {
            for(final FieldDeclaration field : targetFields) {
              _builder.append("out.name(\"");
              String _simpleName = field.getSimpleName();
              _builder.append(_simpleName);
              _builder.append("\");");
              _builder.newLineIfNotEmpty();
              _builder.append("write");
              String _firstUpper = StringExtensions.toFirstUpper(field.getSimpleName());
              _builder.append(_firstUpper);
              _builder.append("(out, value.get");
              String _firstUpper_1 = StringExtensions.toFirstUpper(field.getSimpleName());
              _builder.append(_firstUpper_1);
              _builder.append("());");
              _builder.newLineIfNotEmpty();
            }
          }
          _builder.append("out.endObject();");
          _builder.newLine();
        }
      };
      method.setBody(_client);
    };
    impl.addMethod("write", _function_5);
    final Type booleanType = context.findTypeGlobally(Boolean.class);
    final Type numberType = context.findTypeGlobally(Number.class);
    final Type stringType = context.findTypeGlobally(String.class);
    for (final FieldDeclaration field_2 : targetFields) {
      {
        String _firstUpper = StringExtensions.toFirstUpper(field_2.getSimpleName());
        String _plus_1 = ("write" + _firstUpper);
        final MutableMethodDeclaration existingMethod = impl.findDeclaredMethod(_plus_1, 
          context.newTypeReference("com.google.gson.stream.JsonWriter"), field_2.getType());
        if ((existingMethod == null)) {
          String _firstUpper_1 = StringExtensions.toFirstUpper(field_2.getSimpleName());
          String _plus_2 = ("write" + _firstUpper_1);
          final Procedure1<MutableMethodDeclaration> _function_6 = (MutableMethodDeclaration it) -> {
            it.setVisibility(Visibility.PROTECTED);
            it.addParameter("out", context.newTypeReference("com.google.gson.stream.JsonWriter"));
            it.addParameter("value", field_2.getType());
            it.setExceptions(context.newTypeReference(IOException.class));
            if ((((field_2.getType().isPrimitive() || booleanType.isAssignableFrom(field_2.getType().getType())) || numberType.isAssignableFrom(field_2.getType().getType())) || stringType.isAssignableFrom(field_2.getType().getType()))) {
              StringConcatenationClient _client = new StringConcatenationClient() {
                @Override
                protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                  _builder.append("out.value(value);");
                  _builder.newLine();
                }
              };
              it.setBody(_client);
            } else {
              boolean _isEmpty = field_2.getType().getActualTypeArguments().isEmpty();
              boolean _not = (!_isEmpty);
              if (_not) {
                StringConcatenationClient _client_1 = new StringConcatenationClient() {
                  @Override
                  protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                    _builder.append("gson.toJson(value, ");
                    String _upperCase = field_2.getSimpleName().toUpperCase();
                    _builder.append(_upperCase);
                    _builder.append("_TYPE_TOKEN.getType(), out);");
                    _builder.newLineIfNotEmpty();
                  }
                };
                it.setBody(_client_1);
              } else {
                TypeReference _type = field_2.getType();
                TypeReference _newTypeReference = context.newTypeReference(Object.class);
                boolean _equals = Objects.equal(_type, _newTypeReference);
                if (_equals) {
                  StringConcatenationClient _client_2 = new StringConcatenationClient() {
                    @Override
                    protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                      _builder.append("if (value == null)");
                      _builder.newLine();
                      _builder.append("\t");
                      _builder.append("out.nullValue();");
                      _builder.newLine();
                      _builder.append("else");
                      _builder.newLine();
                      _builder.append("\t");
                      _builder.append("gson.toJson(value, value.getClass(), out);");
                      _builder.newLine();
                    }
                  };
                  it.setBody(_client_2);
                } else {
                  StringConcatenationClient _client_3 = new StringConcatenationClient() {
                    @Override
                    protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
                      _builder.append("gson.toJson(value, ");
                      TypeReference _type = field_2.getType();
                      _builder.append(_type);
                      _builder.append(".class, out);");
                      _builder.newLineIfNotEmpty();
                    }
                  };
                  it.setBody(_client_3);
                }
              }
            }
          };
          impl.addMethod(_plus_2, _function_6);
        }
      }
    }
    return impl;
  }
  
  protected MutableMethodDeclaration generateFactory(final MutableClassDeclaration factory, final MutableClassDeclaration impl, final TypeReference targetType, @Extension final TransformationContext context) {
    MutableMethodDeclaration _xblockexpression = null;
    {
      TypeReference _newTypeReference = context.newTypeReference("com.google.gson.TypeAdapterFactory");
      factory.setImplementedInterfaces(Collections.<TypeReference>unmodifiableList(CollectionLiterals.<TypeReference>newArrayList(_newTypeReference)));
      final Procedure1<MutableMethodDeclaration> _function = (MutableMethodDeclaration it) -> {
        final MutableTypeParameterDeclaration t = it.addTypeParameter("T");
        it.addParameter("gson", context.newTypeReference("com.google.gson.Gson"));
        it.addParameter("typeToken", context.newTypeReference("com.google.gson.reflect.TypeToken", context.newTypeReference(t)));
        it.setReturnType(context.newTypeReference("com.google.gson.TypeAdapter", context.newTypeReference(t)));
        StringConcatenationClient _client = new StringConcatenationClient() {
          @Override
          protected void appendTo(StringConcatenationClient.TargetStringConcatenation _builder) {
            _builder.append("if (!");
            _builder.append(targetType);
            _builder.append(".class.isAssignableFrom(typeToken.getRawType())) {");
            _builder.newLineIfNotEmpty();
            _builder.append("\t");
            _builder.append("return null;");
            _builder.newLine();
            _builder.append("}");
            _builder.newLine();
            _builder.append("return (TypeAdapter<T>) new ");
            _builder.append(impl);
            _builder.append("(gson);");
            _builder.newLineIfNotEmpty();
          }
        };
        it.setBody(_client);
      };
      _xblockexpression = factory.addMethod("create", _function);
    }
    return _xblockexpression;
  }
  
  private ArrayList<FieldDeclaration> getTargetFields(final TypeReference targetType, @Extension final TransformationContext context) {
    final Type objectType = context.newTypeReference(Object.class).getType();
    final ArrayList<FieldDeclaration> targetFields = CollectionLiterals.<FieldDeclaration>newArrayList();
    TypeReference typeRef = targetType;
    while ((!Objects.equal(typeRef.getType(), objectType))) {
      {
        Type _type = typeRef.getType();
        final ClassDeclaration clazz = ((ClassDeclaration) _type);
        final Function1<FieldDeclaration, Boolean> _function = (FieldDeclaration it) -> {
          boolean _isStatic = it.isStatic();
          return Boolean.valueOf((!_isStatic));
        };
        Iterable<? extends FieldDeclaration> _filter = IterableExtensions.filter(clazz.getDeclaredFields(), _function);
        Iterables.<FieldDeclaration>addAll(targetFields, _filter);
        typeRef = clazz.getExtendedClass();
      }
    }
    return targetFields;
  }
}