/*
 * Copyright 2013. Guidewire Software, Inc.
 */

package gw.internal.gosu.parser.java.classinfo;

import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.java.IJavaAnnotatedElement;
import gw.lang.reflect.java.IJavaClassInfo;
import gw.lang.reflect.java.IJavaClassMethod;
import gw.lang.reflect.java.IJavaClassType;
import gw.lang.reflect.java.asm.AsmAnnotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AsmClassAnnotationInfo implements IAnnotationInfo {
  private AsmAnnotation _annotation;
  private IJavaAnnotatedElement _owner;

  public AsmClassAnnotationInfo( AsmAnnotation annotation, IJavaAnnotatedElement owner ) {
    _annotation = annotation;
    _owner = owner;
  }

  public String getName() {
    return _annotation.getType().getName();
  }

  public IType getOwnersType() {
    return _owner.getEnclosingClass().getJavaType();
  }

  public String getDisplayName() {
    return getName();
  }

  public String getDescription() {
    return getName();
  }

  public Annotation getInstance() {
    throw new RuntimeException( "Not supported for source types" );
  }

  @Override
  public Object getFieldValue( String fieldName ) {
    return normalizeValue( fieldName, _annotation.getFieldValues().get( fieldName ) );
  }

  private Object normalizeValue( String fieldName, Object value ) {
    if( value instanceof List ) {
      value = makeArray( fieldName, (List)value );
    }
    else if( value instanceof AsmAnnotation ) {
      value = new AsmClassAnnotationInfo( (AsmAnnotation)value, _owner );
    }
    return value;
  }

  private Object makeArray( String fieldName, List l ) {
    IJavaClassInfo classInfo = TypeSystem.getJavaClassInfo( _annotation.getType().getName(), ((IJavaClassType)_owner).getModule() );
    try {
      IJavaClassMethod method = classInfo.getDeclaredMethod( fieldName );
      IJavaClassInfo ci = method.getReturnClassInfo();
      return makeArray( ci, l, _owner );
    }
    catch( Exception e ) {
      throw new RuntimeException( e );
    }
  }

  public IType getType() {
    return TypeSystem.getByFullName( getName() );
  }

  public String toString() {
    return getName();
  }

  private static final Map<String, Class> SUPPORTED_TYPES = new HashMap<String, Class>();
  static {
    SUPPORTED_TYPES.put( byte.class.getName(), byte.class );
    SUPPORTED_TYPES.put( boolean.class.getName(), boolean.class );
    SUPPORTED_TYPES.put( char.class.getName(), char.class );
    SUPPORTED_TYPES.put( short.class.getName(), short.class );
    SUPPORTED_TYPES.put( int.class.getName(), int.class );
    SUPPORTED_TYPES.put( long.class.getName(), long.class );
    SUPPORTED_TYPES.put( float.class.getName(), float.class );
    SUPPORTED_TYPES.put( double.class.getName(), double.class );
    SUPPORTED_TYPES.put( void.class.getName(), void.class );
    SUPPORTED_TYPES.put( String.class.getName(), String.class );
    SUPPORTED_TYPES.put( AsmAnnotation.class.getName(), IAnnotationInfo.class );
  }
  public static Object makeArray( IJavaClassInfo ci, List l, IJavaAnnotatedElement owner ) throws ClassNotFoundException {
    Object array;
    IJavaClassInfo componentType = ci.getComponentType();
    if( !componentType.isArray() ) {
      Class compClass;
      if( componentType.isEnum() ) {
        compClass = String.class;
      }
      else {
        compClass = getClass( componentType );
      }
      if( compClass == null ) {
        throw new IllegalStateException();
      }
      array = Array.newInstance( compClass, l.size() );
      for( int i = 0; i < l.size(); i++ ) {
        Object elem = l.get( i );
        if( compClass == IAnnotationInfo.class ) {
          elem = new AsmClassAnnotationInfo( (AsmAnnotation)elem, owner );
        }
        try {
          Array.set( array, i, elem );
        }
        catch( RuntimeException e ) {
          throw e;
        }
      }
    }
    else {
      array = Array.newInstance( getClass( componentType ), l.size() );
      for( int i = 0; i < l.size(); i++ ) {
        Object elem = makeArray( componentType, (List)l.get( i ), owner );
        Array.set( array, i, elem );
      }
    }
    return array;
  }

  private static Class<?> getClass( IJavaClassInfo ci ) {
    if( ci.isArray() ) {
      return Array.newInstance( getClass( ci.getComponentType() ), 0 ).getClass();
    }
    if( ci.isAnnotation() ) {
      return IAnnotationInfo.class;
    }
    else if( ci.isEnum() ) {
      return String.class;
    }
    else {
      String componentName = ci.getName();
      Class cls = SUPPORTED_TYPES.get( componentName );
      return cls == null ? String.class : cls;
    }
  }

}
