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

package net.ranides.assira.reflection.impl.bean;

import java.util.List;
import net.ranides.assira.collection.lists.VirtualList;
import net.ranides.assira.functional.VarFunction;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IMethod;

/**
 * Represents indexed bean property as List. "value" getter & setter should return List.
 * Optional "index" getter & setter should return/take type identical to List generic parameter.
 * 
 * #set just use "value setter"
 * 
 * #get returns VirtualList:
 *      list contains "cached" data obtained by "value getter"
 *      #get(index) uses "index getter" if declared, or cached value otherwise
 *      #set(index) changes content of virtual list
 *      #set(index) use "index setter" if declared to modify bean's content instantly
 *      #size() returns size of cached data
 * 
 * Please note that if "index setter" isn't declared, you have to manually return back modified list:
 *      List<Object> list = property.get(bean)
 *      list.set ...
 *      property.set(list)
 * 
 * Please note that structural changes have to be manually returned back even if you declared "index setter":
 *      List<Object> list = property.get(bean)
 *      list.remove ...
 *      list.add ...
 *      property.set(list)
 * 
 * 
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class RPIndexed extends ABeanProperty {

    private final IMethod vgetter;
    private final IMethod vsetter;
    private final IMethod igetter;
    private final IMethod isetter;

    public RPIndexed(RBeanPropertyInfo info) {
        super(itype(info), info.name());
        this.vgetter = info.vgetter();
        this.vsetter = info.vsetter();
        
        this.igetter = info.igetter();
        this.isetter = info.isetter();
    }
    
    private static IClass<?> itype(RBeanPropertyInfo info) {
        return IClass.generic(List.class, info.item());
    }
        
    @Override
    public void set(Object that, Object value) {
        checkWA();
        vsetter.apply(that, value);
    }

    @Override
    public Object get(Object that) {
        checkRA();
        Object list = vgetter.apply(that);
        return list!=null ? new ListWrapper(this, that, list) : null;
    }

    @Override
    public boolean isReadable() {
        return vgetter != null;
    }

    @Override
    public boolean isWritable() {
        return vsetter != null;
    }
    
    private void checkRA() {
        if(!isReadable()) {
            throw new UnsupportedOperationException("property " + name() + " is read-only");
        }
    }
    
    private void checkWA() {
        if(!isWritable()) {
            throw new UnsupportedOperationException("property " + name() + " is write-only");
        }
    }
    
    private static final class ListWrapper extends VirtualList<Object> {
        
        private static final long serialVersionUID = 1L;

        private final List<Object> data;
        private final VarFunction mset;
        private final VarFunction mget;

        @SuppressWarnings("unchecked")
        public ListWrapper(RPIndexed rpi, Object that, Object list) {
            this.data = (List)list;
            this.mget = null != rpi.igetter ? rpi.igetter.bind(that) : null;
            this.mset = null != rpi.isetter ? rpi.isetter.bind(that) : null;
        }

        @Override
        public int size() {
            return data.size();
        }

        @Override
        public Object get(int index) {
            if(null != mget) {
                return mget.call(index);
            } else {
                return data.get(index);
            }
        }

        @Override
        public Object set(int index, Object element) {
            Object prv = data.set(index, element);
            if(null != mset) {
                prv = mset.call(index, element);
            }
            return prv;
        }

    }
    
}
