/*
* Copyright 2014 - LAit SpA
*				 Realizzato da Icona Management srl su commessa LAit SpA
* Copyright 2015- LAit SpA
*                                                  Aggiornato da LAit SpA
*
* Concesso in licenza a norma dell'EUPL, versione 1.1 o
* successive dell'EUPL (la "Licenza")– non appena saranno
* approvate dalla Commissione europea;
* Non è possibile utilizzare l'opera salvo nel rispetto della Licenza.
* È possibile ottenere una copia della Licenza al seguente indirizzo:
*
* http://ec.europa.eu/idabc/eupl
*
* Salvo diversamente indicato dalla legge applicabile o
* concordato per iscritto, il software distribuito secondo
* i termini della Licenza è distribuito "TAL QUALE",
* SENZA GARANZIE O CONDIZIONI DI ALCUN TIPO,
* esplicite o implicite.
* Si veda la Licenza per la lingua specifica che disciplina
* le autorizzazioni e le limitazioni secondo i termini della
* Licenza.
*/
package it.laitspa.cpf.faces.utils;

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;

public abstract class ExtendableDataModel<T> extends SerializableDataModel {
  
  protected DataPage<T> dataProvider;
  protected Object currentPk;
  protected Map<Object,Object> wrappedData = new HashMap<Object,Object>();
  protected List<Object> wrappedKeys = null;
  protected int         pageSize;
  protected int         rowIndex;
  
  protected String      sortBy;
  protected boolean     ascending = true;
  protected boolean     ordered = false;
  private int currentPage;

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

  
  public ExtendableDataModel(int pageSize)
  {
    this.pageSize = pageSize;
    this.rowIndex = -1;
    this.currentPage = 1;
  }
  
  /**
   * 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 = 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<Object>();
      wrappedData.clear();   
      
      // Check if need to read from DB
      //
      if (getDataProvider() == null)
        this.dataProvider = fetchPage(firstRow, numberOfRows);
      else
      {
        // Just readed from row count.
        //
        int start_page_index = getDataProvider().getStartRow();
        int last_index = start_page_index + getDataProvider().getData().size();
        
        //if (firstRow < start_page_index || (firstRow + numberOfRows) > last_index)
        if (firstRow < start_page_index || firstRow >= last_index || ordered)
          this.dataProvider = fetchPage(firstRow, numberOfRows);
        
        ordered = false;
      }
      for (Object item: getDataProvider().getData()) {
          Object key = getObjectPk(item);
          wrappedKeys.add(key);
          wrappedData.put(key, item);
          visitor.process(context, key, 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().getDatasetSize());
          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 Object getRowData() {
      if (currentPk==null) {
          return null;
      } else {
          Object ret = wrappedData.get(currentPk);
          /*
          if (ret==null) {
              ret = getDataProvider().getAuctionItemByPk(currentPk);
              wrappedData.put(currentPk, ret);
              return ret;
          } else {
              return ret;
          }
          */
          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);
          */
        return wrappedData.containsKey(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;
      }
  }
  
  protected <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() {

    // Use to sort
    //
    Object sortableProps = getSortableProperty();
    if (sortableProps != null)
    {
      String newSortField = sortableProps.toString(); 
      if (newSortField.equals(sortBy)) 
      { 
          ascending = !ascending; 
      } 
      sortBy = newSortField;
      ordered = true;
    }
  }
  
  protected void resetDataProvider() {
      this.dataProvider = null;
  }

  public DataPage<T> getDataProvider() {
      if (dataProvider == null) {
        int startRow = 0;
        this.rowIndex = startRow;
        dataProvider = fetchPage(startRow, pageSize);
      }
      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;
  }

  /**
   * Method which must be implemented in cooperation with the managed bean class
   * to fetch data on demand.
   */
  public abstract DataPage<T> fetchPage(int startRow, int pageSize);
  
  /**
   * Method which must be implemented in cooperation with the managed bean class
   * to return the primary key of current row.
   */
  public Object getObjectPk(Object row)
  {
    return row.hashCode();
  }

  public int getPageSize()
  {
    return pageSize;
  }
  
  private Object getSortableProperty()
  {
    FacesContext context = FacesContext.getCurrentInstance(); 
    Object sortFieldObject =  context.getExternalContext().getRequestParameterMap().get("sortBy");
    return sortFieldObject;
  }
  
  protected Map<String, Boolean> getOrderCriteria()
  {
    if(sortBy == null)
      return null;

    Map<String, Boolean> orderCriteria = new HashMap<String, Boolean>();
    orderCriteria.put(sortBy, ascending);
    return orderCriteria;
  }
  
  public List<T> getListItems(){
      if(dataProvider == null)
        return null;
     
      return dataProvider.getData();
    
  }

  public int getCurrentPage()
  {
    return currentPage;
  }

  public void setCurrentPage(int currentPage)
  {
    this.currentPage = currentPage;
  }

  
  
  
}
