/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.generic;

import java.io.Serializable;
import net.ranides.assira.reflection.GenericClass;
import net.ranides.assira.text.LexicalCast;

/**
 * Wrapper na referencję obiektu. Jedno z nielicznych zastosowań (zresztą dość
 * nieeleganckie) to przekazywanie wrappera jako argument funkcji, która za jego
 * pomocą zwraca wynik.
 * <p>
 * Sygnalizowanie wyników za pomocą zmian stanu argumentów przekazanych funkcji
 * nie jest dobrą praktyką, ale czasem (rzadko) upraszcza API.
 * </p>
 * @param <T>
 * @author ranides
 */
public abstract class Wrapper<T> {
    
    public static <Q> Wrapper<Q> make() {
        return new CWrapper<>(null);
    }
    
    public static <Q> Wrapper<Q> make(Q value) {
        return new CWrapper<>(value);
    }

    /**
     * Returns value of referenced object
     * @return
     */
    public abstract T get();

    /**
     * Change value of referenced object
     * @param value
     */
    public abstract void set(T value);
    
    /**
     * Changes value, automatically casting passed argument, if necessary
     * @param value 
     */
    public abstract void put(Object value);

    /**
     * Returns type of referenced object. Implementations <i>should</i> do as much as possible
     * to return most informative result. For example, it is allowed to merge 
     * current runtime class (erasure) with generic parameters extracted
     * from declaration.
     * <p>
     * Please note that it isn't guaranteed in any way, that return value will
     * represent most concrete class of stored value. For example, it <i>could</i>
     * be {@code TypeToken&lt;T&gt;} but in extremal cases, it is allowed to return
     * even {@code Object.class}.
     * </p>
     * @return 
     */
    public abstract GenericClass type();

    private static class CWrapper<Q> extends Wrapper<Q> implements Serializable {
        
        private static final long serialVersionUID = 3L;
        
        private Q value;

        public CWrapper(Q value) {
            this.value = value;
        }

        @Override
        public Q get() {
            return value;
        }

        @Override
        public void set(Q value) {
            this.value = value;
        }

        @Override
        public void put(Object value) {
            this.value = LexicalCast.cast(value, type());
        }

        @Override
        public GenericClass type() {
            return new TypeToken<Q>(){};
        }
        
    }
}
