package org.richfaces.demo.extendeddatamodel;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.ajax4jsf.model.SerializableDataModel;
/**
 * 
 * @author ias
 * This is example class that intended to demonstrate use of ExtendedDataModel and SerializableDataModel.
 * This implementation intended to be used as a request scope bean. However, it actually provides serialized
 * state, so on a post-back we do not load data from the data provider. Instead we use data that was used 
 * during rendering.
 * This data model must be used together with Data Provider, which is responsible for actual data load 
 * from the database using specific filtering and sorting. Normally Data Provider must be in either session, or conversation
 * scope.
 */
public class AuctionDataModel extends SerializableDataModel {
	
	private AuctionDataProvider dataProvider;
	private Integer currentPk;
	private Map<Integer,AuctionItem> wrappedData = new HashMap<Integer,AuctionItem>();
	private List<Integer> wrappedKeys = null;

	/**
	 * 
	 */
	private static final long serialVersionUID = -1956179896877538628L;

	/**
	 * This method never called from framework.
	 * (non-Javadoc)
	 * @see org.ajax4jsf.model.ExtendedDataModel#getRowKey()
	 */
	@Override
	public Object getRowKey() {
		return currentPk;
	}
	/**
	 * This method normally called by Visitor before request Data Row.
	 */
	@Override
	public void setRowKey(Object key) {
		this.currentPk = (Integer) key;
		
	}
	/**
	 * This is main part of Visitor pattern. Method called by framework many times during request processing. 
	 */
	@Override
	public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
		int firstRow = ((SequenceRange)range).getFirstRow();
		int numberOfRows = ((SequenceRange)range).getRows();
		wrappedKeys = new ArrayList<Integer>();
		for (AuctionItem item:getDataProvider().getItemsByrange(new Integer(firstRow), numberOfRows, null, true)) {
			wrappedKeys.add(item.getPk());
			wrappedData.put(item.getPk(), item);
			visitor.process(context, item.getPk(), argument);
		}
	}
	/**
	 * This method must return actual data rows count from the Data Provider. It is used by pagination control
	 * to determine total number of data items.
	 */
	private Integer rowCount; // better to buffer row count locally
	@Override
	public int getRowCount() {
		if (rowCount==null) {
			rowCount = new Integer(getDataProvider().getRowCount());
			return rowCount.intValue();
		} else {
			return rowCount.intValue();
		}
	}
	/**
	 * This is main way to obtain data row. It is intensively used by framework. 
	 * We strongly recommend use of local cache in that method. 
	 */
	@Override
	public AuctionItem getRowData() {
		if (currentPk==null) {
			return null;
		} else {
			AuctionItem ret = wrappedData.get(currentPk);
			if (ret==null) {
				ret = getDataProvider().getAuctionItemByPk(currentPk);
				wrappedData.put(currentPk, ret);
				return ret;
			} else {
				return ret;
			}
		}
	}

	/**
	 * Unused rudiment from old JSF staff.
	 */
	@Override
	public int getRowIndex() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Unused rudiment from old JSF staff.
	 */
	@Override
	public Object getWrappedData() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Never called by framework.
	 */
	@Override
	public boolean isRowAvailable() {
		if (currentPk==null) {
			return false;
		} else {
			return getDataProvider().hasAuctionItemByPk(currentPk);
		}
	}

	/**
	 * Unused rudiment from old JSF staff.
	 */
	@Override
	public void setRowIndex(int rowIndex) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Unused rudiment from old JSF staff.
	 */
	@Override
	public void setWrappedData(Object data) {
		throw new UnsupportedOperationException();
	}

	/**
	 * This method suppose to produce SerializableDataModel that will be serialized into View State and used on a post-back.
	 * In current implementation we just mark current model as serialized. In more complicated cases we may need to 
	 * transform data to actually serialized form.
	 */
	public  SerializableDataModel getSerializableModel(Range range) {
		if (wrappedKeys!=null) {
			return this; 
		} else {
			return null;
		}
	}
	
	private <V> V lookupInContext(String expression, Class<? extends V> c) {
		FacesContext facesContext = FacesContext.getCurrentInstance();
		Application application = facesContext.getApplication();
		return c.cast(application.evaluateExpressionGet(facesContext, MessageFormat.format("#'{'{0}'}'", expression), c));
	}
	
	private String auctionDataModelExpressionString;

	private String auctionDataProviderExpressionString;

	
	/**
	 * This is helper method that is called by framework after model update. In must delegate actual database update to 
	 * Data Provider.
	 */
	@Override
	public void update() {
		AuctionDataModel auctionDataModel = lookupInContext(auctionDataModelExpressionString, AuctionDataModel.class);
		Object savedKey = getRowKey();
		for (Integer key : wrappedKeys) {
			auctionDataModel.setRowKey(key);
			auctionDataModel.getRowData().setBid(wrappedData.get(key).getBid());
		}
		setRowKey(savedKey);
		//getDataProvider().update();
		
		this.wrappedData.clear();
		this.wrappedKeys.clear();
		resetDataProvider();
	}
	
	protected void resetDataProvider() {
		this.dataProvider = null;
	}

	public AuctionDataProvider getDataProvider() {
		if (dataProvider == null) {
			dataProvider = lookupInContext(auctionDataProviderExpressionString, AuctionDataProvider.class);
		}
		return dataProvider;
	}
	public String getAuctionDataModelExpressionString() {
		return auctionDataModelExpressionString;
	}
	public void setAuctionDataModelExpressionString(
			String auctionDataModelExpressionString) {
		this.auctionDataModelExpressionString = auctionDataModelExpressionString;
	}
	public String getAuctionDataProviderExpressionString() {
		return auctionDataProviderExpressionString;
	}
	public void setAuctionDataProviderExpressionString(
			String auctionDataProviderExpressionString) {
		this.auctionDataProviderExpressionString = auctionDataProviderExpressionString;
	}

}
