/*
 * Project Scelight
 * 
 * Copyright (c) 2013 Andras Belicza <[email protected]>
 * 
 * This software is the property of Andras Belicza.
 * Copying, modifying, distributing, refactoring without the author's permission
 * is prohibited and protected by Law.
 */
package hu.sllauncher.bean;

import hu.scelightapibase.bean.HiddenProperty;
import hu.scelightapibase.bean.IBean;
import hu.sllauncher.service.env.LEnv;

import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;

/**
 * Ancestor class for XML beans.
 * 
 * @author Andras Belicza
 * 
 * @see IdedBean
 */
@XmlAccessorType( XmlAccessType.FIELD )
public class Bean implements IBean {
	
	/** Bean instance version. */
	@XmlAttribute( name = "v" )
	protected int beanVer;
	
	/**
	 * Creates a new {@link Bean}.
	 * <p>
	 * No-arg constructor required by some implementation of {@link JAXB#unmarshal(java.io.Reader, Class)}.
	 * </p>
	 */
	protected Bean() {
	}
	
	/**
	 * Creates a new {@link Bean}.
	 * 
	 * @param beanVer bean instance version
	 */
	public Bean( final int beanVer ) {
		this.setBeanVer( beanVer );
	}
	
	@Override
	public int getBeanVer() {
		return beanVer;
	}
	
	/**
	 * Sets the bean instance version.
	 * 
	 * @param beanVer bean instance version to be set
	 */
	public void setBeanVer( int beanVer ) {
		this.beanVer = beanVer;
	}
	
	/**
	 * Clones this bean by concatenating marshaling and unmarshaling operations.
	 * 
	 * <p>
	 * This default bean cloning implementation is "very" slow, it even marshals and unmarshals primitive type values (like <code>int</code> or
	 * <code>boolean</code>) and immutable objects (like {@link String}) which otherwise would not be necessary. But works for all beans without modification,
	 * and does not require modification when a bean is modified or extended with new fields later on (<i>resilient</i> to changes).
	 * </p>
	 * <p>
	 * Beans that are frequently cloned should override this method and implement a custom cloning. A custom cloning (which copies the fields) can be even 1000
	 * times faster than this!
	 * </p>
	 * 
	 * @param <T> dynamic type of the bean to return
	 * @return a clone of this bean
	 */
	@Override
	@SuppressWarnings( "unchecked" )
	public < T extends IBean > T cloneBean() {
		final StringWriter sw = new StringWriter( 256 );
		JAXB.marshal( this, sw );
		
		return (T) JAXB.unmarshal( new StringReader( sw.toString() ), this.getClass() );
	}
	
	/**
	 * Returns a string representation of the bean.
	 * <p>
	 * This implementation uses {@link #buildDevString(StringBuilder)}.
	 * </p>
	 * 
	 * @return a string representation of the bean
	 * @see #buildDevString(StringBuilder)
	 */
	@Override
	public String toString() {
		final StringBuilder b = new StringBuilder();
		
		buildDevString( b );
		
		return b.toString();
	}
	
	@Override
	public void buildDevString( final StringBuilder b ) {
		b.append( getClass().getSimpleName() ).append( '{' );
		
		for ( final Method m : getClass().getMethods() ) {
			if ( ( m.getModifiers() & Modifier.STATIC ) != 0 )
				continue;
			
			if ( m.getParameterTypes().length > 0 )
				continue;
			
			if ( m.getReturnType().equals( Void.TYPE ) )
				continue;
			
			final String name = m.getName();
			if ( name.length() < 3 || !name.startsWith( "get" ) )
				continue;
			
			if ( "getClass".equals( name ) )
				continue;
			
			if ( m.getAnnotation( HiddenProperty.class ) != null )
				continue;
			
			// Property name: cut off get and lowercase first letter
			if ( name.length() > 3 )
				b.append( Character.toLowerCase( name.charAt( 3 ) ) ).append( name, 4, name.length() );
			b.append( '=' );
			try {
				final Object propValue = m.invoke( this );
				// Avoid endless recursion:
				if ( propValue == this )
					b.append( "this" );
				else {
					if ( propValue instanceof Bean )
						( (Bean) propValue ).buildDevString( b );
					else
						b.append( propValue ); // This also handles null values...
				}
			} catch ( final IllegalAccessException | IllegalArgumentException | InvocationTargetException e ) {
				LEnv.LOGGER.error( "Could not call method: " + getClass().getSimpleName() + "." + name, e );
			}
			b.append( "; " );
		}
		
		b.append( '}' );
	}
	
}