/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.amber.query;

import com.caucho.amber.manager.AmberConnection;
import com.caucho.amber.type.EntityType;
import com.caucho.amber.type.AmberType;
import com.caucho.util.Alarm;
import com.caucho.util.CurrentTime;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Represents the application's view of the query.
 */
public class CachedQuery {
  private AmberSelectQuery _query;
  private CachedQueryKey _key;
  private ResultSetImpl _rs;

  private AmberType []_argTypes;
  private Object []_argValues;
  private int _argLength = 0;

  private long _loadTime;

  private ArrayList<Object> _values = new ArrayList<Object>();

  private volatile boolean _isLoading;
  private volatile boolean _isValidLoad;

  CachedQuery(UserQuery query)
  {
    _query = (AmberSelectQuery) query.getQuery();

    AmberType []argTypes = query.getArgTypes();
    Object []argValues = query.getArgValues();
    
    _argLength = query.getArgLength();

    if (_argLength > 0) {
      _argTypes = new AmberType[_argLength];
      _argValues = new Object[_argLength];

      for (int i = _argLength - 1; i >= 0; i--) {
        _argTypes[i] = argTypes[i];
        _argValues[i] = argValues[i];
      }
    }

    _key = new CachedQueryKey();
    _key.init(_query.getQueryString(), _argValues, _argLength);

    _query.registerUpdates(this);
  }

  /**
   * returns the key.
   */
  public CachedQueryKey getKey()
  {
    return _key;
  }

  /**
   * Updates the query.
   */
  public void update()
  {
    synchronized (this) {
      _loadTime = 0;
      _isValidLoad = false;
    }
  }

  /**
   * Executes the query, filling the list.
   */
  public void list(List<Object> list, AmberConnection aConn, long maxAge)
    throws SQLException
  {
    AmberType type = _query.getResultType(0);
    EntityType entityType = (EntityType) type;
    Class cl = entityType.getBeanClass();
    
    synchronized (this) {
      long now = CurrentTime.getCurrentTime();

      if (now < _loadTime + maxAge || _isLoading && _loadTime > 0) {
        int length = _values.size();

        for (int i = 0; i < length; i++) {
          Object key = _values.get(i);

          list.add(aConn.loadLazy(cl.getName(), entityType.getName(), (java.io.Serializable) key));
        }
        return;
      }

      _isLoading = true;
      _isValidLoad = true;
    }

    try {
      ArrayList<Object> values = new ArrayList<Object>();

      ResultSetImpl rs = executeQuery(aConn);

      while (rs.next()) {
        values.add(rs.getKey(1));

        list.add(rs.getObject(1));
      }

      rs.close();
      
      synchronized (this) {
        if (_isValidLoad) {
          _values = values;

          _loadTime = CurrentTime.getCurrentTime();
        }
      }
    } finally {
      _isLoading = false;
    }
  }

  /**
   * Executes the query returning a result set.
   */
  private ResultSetImpl executeQuery(AmberConnection aConn)
    throws SQLException
  {
    if (_rs == null)
      _rs = new ResultSetImpl();

    PreparedStatement pstmt;
    pstmt = aConn.prepareStatement(_query.getSQL());

    pstmt.clearParameters();
    
    for (int i = 0; i < _argLength; i++) {
      if (_argValues[i] != null)
        _argTypes[i].setParameter(pstmt, i + 1, _argValues[i]);
    }

    ResultSet rs = pstmt.executeQuery();

    _rs.setResultSet(rs);
    _rs.setQuery((AmberSelectQuery) _query);
    _rs.setSession(aConn);
    _rs.init();
    
    return _rs;
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _query.getQueryString() + "]";
  }
}
