/**
 * Copyright © 2008-2009 DRIVER PROJECT (ICM UW)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.dnetlib.resultset.impl;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Transient;

import org.apache.log4j.Logger;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.IndexColumn;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;


/**
 * Pull Result Set object.
 * @author Marek Horst
 * @version 0.01
 *
 */
@Entity
public class PullResultSetObject extends ResultSetObject {


	protected static final Logger log = Logger.getLogger(PullResultSetObject.class);
	
	/**
	 * CreatePull Result Set type.
	 */
	private CreatePullRSType createPullRS_type;
	
	/**
	 * Data provider service address.
	 */
	private String dataProviderServiceAddress;
	
	/**
	 * Bulk data identifier.
	 */
	private String bdId;
	
	/**
	 * Result Set initial page size.
	 */
	private int initialPageSize;
	
	/**
	 * Retreved pages array. 
	 * Every page of results retrieved from data provider is set to true.
	 */
	private boolean[] retrievedPages;
	
	/**
	 * Pull Result Set data.
	 */
	private List<String[]> data;
	
	/**
	 * Pull Result Set size.
	 */
	private int pullRsSize;

	/**
	 * Result Set last access time.
	 */
	private long lastAccessTime;
	
	/**
	 * Default construnctor for hibernate3 purposes.
	 */
	public PullResultSetObject() {
		super(null, Integer.MIN_VALUE);
	}
	
	/**
	 * Default constructor.
	 * @param resultSetId
	 * @param expiryTime
	 * @param dataProviderServiceAddress
	 * @param bdId
	 * @param initialPageSize
	 * @param pullRsSize
	 * @param createPullRS_type
	 */
	public PullResultSetObject(String resultSetId, int expiryTime, 
			String dataProviderServiceAddress, String bdId, int initialPageSize,
			int pullRsSize, CreatePullRSType createPullRS_type) {
		super(resultSetId, expiryTime);
//		keepAliveTime support
		if (createPullRS_type!=null && createPullRS_type.getKeepAliveTime()!=null)
			log.debug("got keep alive time [in secs]: "+createPullRS_type.getKeepAliveTime());
		setLastAccessTime(System.currentTimeMillis());
		
		this.dataProviderServiceAddress = dataProviderServiceAddress;
		this.bdId = bdId;
		if (initialPageSize<1)
			throw new IllegalArgumentException("initialPageSize must be >=1");
		this.initialPageSize = initialPageSize;
		this.pullRsSize = pullRsSize;
		this.createPullRS_type = createPullRS_type;
		int pagesCount = calculatePagesCount(
				initialPageSize, pullRsSize);
		this.retrievedPages = new boolean[pagesCount];
		for (int i=0; i<retrievedPages.length; i++)
			retrievedPages[i] = false;
		this.data = new ArrayList<String[]>(pagesCount);
//		added for initial filling the data with Empty arrays to avoid hibernate bug(?) where nulls are ommited
//		TODO if hibernate ObjectContainer is dropped, remove this initialization
		for (int i=0; i<pagesCount; i++)
			this.data.add(new String[0]);
		
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.ResultSetObject#getSize()
	 */
	@Override
	@Transient
	public int getSize() {
		return pullRsSize;
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.resultset.impl.ResultSetObject#getResultSetType()
	 */
	@Override
	@Transient
	public ResultSetType getResultSetType() {
		return ResultSetType.PULL_RS;
	}

	int calculatePagesCount(int initialPageSize, int pullRsSize) {
		if (pullRsSize<initialPageSize)
			return 1;
		int pagesCount = pullRsSize/initialPageSize;
		if (pullRsSize%initialPageSize>0)
			pagesCount++;
		return pagesCount;
	}
	
	/**
	 * Returns true if given results page was already retrieved.
	 * First page number is 1.
	 * Throws InvalidParameterException if pageNumber is outside supported range.
	 * @param pageNumber
	 * @return true if given results page was already retrieved
	 */
	public boolean isPageRetreived(int pageNumber) {
		if (pageNumber<1 || pageNumber>retrievedPages.length)
			throw new InvalidParameterException("pageNumber must be within range " +
					"<"+1+";"+retrievedPages.length+">");
		return retrievedPages[pageNumber-1];
	}
	
	/**
	 * Marks page given as parameter as retrieved. First page number is 1.
	 * Throws InvalidParameterException if pageNumber is outside supported range.
	 * @param pageNumber
	 */
	public void setPageRetrieved(int pageNumber) {
		if (pageNumber<1 || pageNumber>retrievedPages.length)
			throw new InvalidParameterException("pageNumber must be within range " +
					"<"+1+";"+retrievedPages.length+">");
		retrievedPages[pageNumber-1] = true;
	}
	
	/**
	 * Sets data for page number given as parameter.
	 * Throws InvalidParameterException if pageNumber is outside supported range.
	 * @param pageNumber
	 * @param pageData
	 */
	public void setPageData(int pageNumber, String[] pageData) {
		if (pageNumber<1 || pageNumber>this.data.size())
			throw new InvalidParameterException("pageNumber must be within range " +
					"<"+1+";"+retrievedPages.length+">");
		data.set(pageNumber-1, pageData);
	}
	
	/**
	 * Returns data from page number given as parameter.
	 * @param pageNumber
	 * @param fromPosition
	 * @param toPosition
	 * @return String[] data
	 */
	public String[] getPageData(int pageNumber, int fromPosition, int toPosition) {
		return PullResultSetObjectUtils.getPageData(
				pageNumber, fromPosition, toPosition, this.data);
	}
	
	/**
	 * Returns pull result set data for given range.
	 * @param fromPosition
	 * @param toPosition
	 * @return array of data
	 */
	public String[] getData(int fromPosition, int toPosition) {
		return PullResultSetObjectUtils.getData(fromPosition, toPosition, 
				this.initialPageSize, this.pullRsSize, this.data);
	}
	
	/**
	 * Returns Result Set expiration time. 
	 * @return Result Set expiration time
	 */
	@Transient
	public long getExpirationTime() {
		if (createPullRS_type==null ||
				createPullRS_type.getKeepAliveTime()==null ||
				createPullRS_type.getKeepAliveTime()==0 ||
				lastAccessTime==0) {
			return getCreationTime() + (getExpiryTime() * 1000);
		} else {
			log.debug("using keepAliveTime: " + createPullRS_type.getKeepAliveTime() + 
					" to determine expiration time");
			long cExpTime = getCreationTime() + (getExpiryTime() * 1000);
			long katExpTime = getLastAccessTime() + (createPullRS_type.getKeepAliveTime() * 1000);
			return cExpTime>katExpTime?cExpTime:katExpTime;
		}
	}
	
	/**
	 * Returns bulk data identifier.
	 * @return bulk data identifier
	 */
	public String getBdId() {
		return bdId;
	}

	/**
	 * Sets bulk data identifier.
	 * @param bdId
	 */
	public void setBdId(String bdId) {
		this.bdId = bdId;
	}

	/**
	 * Returns CreatePull Result Set type.
	 * @return CreatePull Result Set type
	 */
	@Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "keepAliveTime", column = @Column(name = "typeKeepAliveTime")),
        @AttributeOverride(name = "styleSheet", column = @Column(name = "typeStyleSheet")),
        @AttributeOverride(name = "total", column = @Column(name = "typeTotal"))
                })
	public CreatePullRSType getCreatePullRS_type() {
		return createPullRS_type;
	}

	/**
	 * Sets CreatePull Result Set type.
	 * @param createPullRS_type
	 */
	public void setCreatePullRS_type(CreatePullRSType createPullRS_type) {
		this.createPullRS_type = createPullRS_type;
	}

	/**
	 * Returns data provider service address.
	 * @return data provider service address
	 */
	public String getDataProviderServiceAddress() {
		return dataProviderServiceAddress;
	}

	/**
	 * Sets data provider service address.
	 * @param dataProviderServiceAddress
	 */
	public void setDataProviderServiceAddress(String dataProviderServiceAddress) {
		this.dataProviderServiceAddress = dataProviderServiceAddress;
	}

	/**
	 * Returns Result Set initial page size.
	 * @return Result Set initial page size
	 */
	public int getInitialPageSize() {
		return initialPageSize;
	}

	/**
	 * Sets Result Set initial page size.
	 * @param initialPageSize
	 */
	public void setInitialPageSize(int initialPageSize) {
		this.initialPageSize = initialPageSize;
	}

	/**
	 * retrievedPages getter method for hibernate3 purposes.
	 * @return retrievedPages
	 */
	public boolean[] getRetrievedPages() {
		return retrievedPages;
	}

	/**
	 * retrievedPages setter method for hibernate3 purposes.
	 * @param retrievedPages
	 */
	public void setRetrievedPages(boolean[] retrievedPages) {
		this.retrievedPages = retrievedPages;
	}

	/**
	 * data getter method for hibernate3 purposes.
	 * @return data
	 */
	@CollectionOfElements(fetch = FetchType.LAZY, targetElement = String[].class)
	@LazyCollection(LazyCollectionOption.EXTRA)
	@IndexColumn(name = "data_position", base=1)
	public List<String[]> getData() {
		return data;
	}

	/**
	 * data setter method for hibernate3 purposes.
	 * @param data
	 */
	public void setData(List<String[]> data) {
		this.data = data;
	}

	/**
	 * pullRsSize getter method for hibernate3 purposes.
	 * @return pullRS size
	 */
	public int getPullRsSize() {
		return pullRsSize;
	}

	/**
	 * pullRsSize setter method for hibernate3 purposes.
	 * @param pullRsSize
	 */
	public void setPullRsSize(int pullRsSize) {
		this.pullRsSize = pullRsSize;
	}

	/**
	 * Removes all stored data.
	 */
	public void cleanAllStoredData() {
		if (data!=null) {
			for (int i=0; i<data.size(); i++)
				this.data.set(i, new String[0]);
		}
			
		if (retrievedPages!=null)
			for (int i=0; i<retrievedPages.length; i++)
				retrievedPages[i] = false;
	}

	/**
	 * Returns lastAccessTime.
	 * @return lastAccessTime
	 */
	public synchronized long getLastAccessTime() {
		return lastAccessTime;
	}

	/**
	 * Sets lastAccessTime.
	 * @param lastAccessTime
	 */
	public synchronized void setLastAccessTime(long lastAccessTime) {
		this.lastAccessTime = lastAccessTime;
	}

}
