package org.realityforge.arez.processor;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Set;
import javax.annotation.Generated;
import javax.annotation.Nonnull;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import org.realityforge.arez.annotations.Container;

/**
 * Annotation processor that analyzes Arez annotated source and generates Observable models.
 */
@AutoService( Processor.class )
@SupportedAnnotationTypes( { "org.realityforge.arez.annotations.Action",
                             "org.realityforge.arez.annotations.Computed",
                             "org.realityforge.arez.annotations.Container",
                             "org.realityforge.arez.annotations.ContainerId",
                             "org.realityforge.arez.annotations.Observable" } )
@SupportedSourceVersion( SourceVersion.RELEASE_8 )
public final class ArezProcessor
  extends AbstractJavaPoetProcessor
{
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean process( final Set<? extends TypeElement> annotations, final RoundEnvironment env )
  {
    final Set<? extends Element> elements = env.getElementsAnnotatedWith( Container.class );
    processElements( elements );
    return false;
  }

  @Override
  protected void process( @Nonnull final Element element )
    throws IOException, ArezProcessorException
  {
    final ContainerDescriptor descriptor =
      ContainerDescriptorParser.parse( element, processingEnv.getElementUtils(), processingEnv.getTypeUtils() );
    emitTypeSpec( descriptor.getPackageElement().getQualifiedName().toString(), builder( descriptor ) );
  }

  @Nonnull
  private TypeSpec builder( @Nonnull final ContainerDescriptor descriptor )
    throws ArezProcessorException
  {
    final TypeElement element = descriptor.getElement();

    final AnnotationSpec generatedAnnotation =
      AnnotationSpec.builder( Generated.class ).addMember( "value", "$S", getClass().getName() ).build();

    final TypeSpec.Builder builder = TypeSpec.classBuilder( "Arez_" + element.getSimpleName() ).
      addTypeVariables( ProcessorUtil.getTypeArgumentsAsNames( descriptor.asDeclaredType() ) ).
      addModifiers( Modifier.FINAL ).
      addAnnotation( generatedAnnotation );

    if ( descriptor.getElement().getModifiers().contains( Modifier.PUBLIC ) )
    {
      builder.addModifiers( Modifier.PUBLIC );
    }
    else if ( descriptor.getElement().getModifiers().contains( Modifier.PROTECTED ) )
    {
      builder.addModifiers( Modifier.PROTECTED );
    }
    else if ( descriptor.getElement().getModifiers().contains( Modifier.PRIVATE ) )
    {
      builder.addModifiers( Modifier.PRIVATE );
    }

    return builder.build();
  }
}

@@ -120,24 +120,148 @@
 tationSpec;%0A
+import com.squareup.javapoet.MethodSpec;%0Aimport com.squareup.javapoet.ParameterSpec;%0Aimport com.squareup.javapoet.TypeName;%0A
 import com.s
@@ -311,24 +311,52 @@
 OException;%0A
+import java.util.ArrayList;%0A
 import java.
@@ -728,24 +728,75 @@
 nt.Element;%0A
+import javax.lang.model.element.ExecutableElement;%0A
 import javax
@@ -866,24 +866,73 @@
 ypeElement;%0A
+import javax.lang.model.element.VariableElement;%0A
 import org.r
@@ -2724,24 +2724,78 @@
 leName() ).%0A
+      superclass( TypeName.get( element.asType() ) ).%0A
       addTyp
@@ -2964,136 +2964,221 @@
  );%0A
-%0A
     
-if ( descriptor.getElement().getModifiers().contains( Modifier.PUBLIC ) )%0A    %7B%0A      builder.addModifiers( Modifier.PUBLIC
+ProcessorUtil.copyAccessModifiers( element, builder );%0A%0A    for ( final ExecutableElement constructor : ProcessorUtil.getConstructors( element ) )%0A    %7B%0A      builder.addMethod( buildConstructor( constructor )
  );%0A
@@ -3187,299 +3187,1121 @@
   %7D%0A
+%0A
     
-else if ( descriptor.getElement().getModifiers().contains( Modifier.PROTECTED ) )%0A    %7B%0A      builder.addModifiers( Modifier.PROTECTED
+return builder.build();%0A  %7D%0A%0A  @Nonnull%0A  private MethodSpec buildConstructor( @Nonnull final ExecutableElement constructor )%0A  %7B%0A    final MethodSpec.Builder builder = MethodSpec.constructorBuilder();%0A    ProcessorUtil.copyAccessModifiers( constructor, builder );%0A%0A    final StringBuilder superCall = new StringBuilder();%0A    superCall.append( %22super(%22 );%0A    final ArrayList%3CString%3E parameterNames = new ArrayList%3C%3E();%0A    boolean firstParam = true;%0A    for ( final VariableElement element : constructor.getParameters()
  )
-;
 %0A    
-%7D
+%7B
 %0A    
-else if ( descriptor
+  final ParameterSpec.Builder param =%0A        ParameterSpec.builder( TypeName
 .get
-E
+( e
 lement
-().getModifiers().contains( Modifier.PRIVATE ) )%0A    %7B%0A      builder.addModifiers( Modifier.PRIVATE );%0A    %7D
+.asType() ), element.getSimpleName().toString(), Modifier.FINAL );%0A      ProcessorUtil.copyDocumentedAnnotations( element, param );%0A      builder.addParameter( param.build() );%0A      parameterNames.add( element.getSimpleName().toString() );%0A      if ( !firstParam )%0A      %7B%0A        superCall.append( %22,%22 );%0A      %7D%0A      firstParam = false;%0A      superCall.append( %22$N%22 );%0A    %7D%0A%0A    superCall.append( %22)%22 );%0A    builder.addStatement( superCall.toString(), parameterNames.toArray() );
 %0A%0A  

Update generated class to extend base class and define appropriate constructors