/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-2168
// https://issues.jboss.org/browse/TEIID-2086
package org.teiid.dqp.internal.process;

import static org.junit.jupiter.api.Assertions.*;

import java.util.ArrayList;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.teiid.api.exception.query.QueryParserException;
import org.teiid.cache.CacheConfiguration;
import org.teiid.cache.DefaultCacheFactory;
import org.teiid.dqp.internal.process.SessionAwareCache.CacheID;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.parser.ParseInfo;
import org.teiid.query.parser.QueryParser;
import org.teiid.query.processor.relational.ProjectNode;
import org.teiid.query.processor.relational.RelationalPlan;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.symbol.Reference;
import org.teiid.query.util.CommandContext;

@SuppressWarnings("nls")
public class TestPreparedPlanCache {
    private static final String EXAMPLE_QUERY = "SELECT * FROM table"; //$NON-NLS-1$
	private final static DQPWorkContext token = new DQPWorkContext();
	private final static  DQPWorkContext token2 = new DQPWorkContext();
	
	private final static ParseInfo pi = new ParseInfo();
	
	@BeforeAll public static void setUpOnce() {
		token.getSession().setVDBName("foo"); //$NON-NLS-1$
		token.getSession().setVDBVersion(1); 
		token2.getSession().setVDBName("foo"); //$NON-NLS-1$
		token2.getSession().setVDBVersion(2); 
	}
    
    //====Tests====//
    @Test public void testPutPreparedPlan(){
	    SessionAwareCache<PreparedPlan> cache = new SessionAwareCache<PreparedPlan>("preparedplan", DefaultCacheFactory.INSTANCE, SessionAwareCache.Type.PREPAREDPLAN, 0);
    	
    	CacheID id = new CacheID(token, pi, EXAMPLE_QUERY + 1);
    	
    	//No PreparedPlan at the begining
    	assertNull(cache.get(id));
    	//create one
    	cache.put(id, Determinism.SESSION_DETERMINISTIC, new PreparedPlan(), null);
    	//should have one now
    	assertNotNull(cache.get(id), "Unable to get prepared plan from cache"); //$NON-NLS-1$
    }
    
    @Test public void testGet(){
	    SessionAwareCache<PreparedPlan> cache = new SessionAwareCache<PreparedPlan>("preparedplan", DefaultCacheFactory.INSTANCE, SessionAwareCache.Type.PREPAREDPLAN, 0);
    	helpPutPreparedPlans(cache, token, 0, 10);
    	helpPutPreparedPlans(cache, token2, 0, 15);
    	
    	//read an entry for session2 (token2)
    	PreparedPlan pPlan = cache.get(new CacheID(token2, pi, EXAMPLE_QUERY + 12));
    	assertNotNull(pPlan, "Unable to get prepared plan from cache"); //$NON-NLS-1$
    	assertEquals(new RelationalPlan(new ProjectNode(12)).toString(), pPlan.getPlan().toString(), "Error getting plan from cache"); //$NON-NLS-1$
    	assertEquals(EXAMPLE_QUERY + 12, pPlan.getCommand().toString(), "Error getting command from cache"); //$NON-NLS-1$
    	assertNotNull(pPlan.getAnalysisRecord(), "Error getting plan description from cache"); //$NON-NLS-1$
    	assertEquals(new Reference(1), pPlan.getReferences().get(0), "Error gettting reference from cache"); //$NON-NLS-1$
    }
    
    @Test public void testClearAll(){
	    SessionAwareCache<PreparedPlan> cache = new SessionAwareCache<PreparedPlan>("preparedplan", DefaultCacheFactory.INSTANCE, SessionAwareCache.Type.PREPAREDPLAN, 0);
    	
    	//create one for each session token
    	helpPutPreparedPlans(cache, token, 1, 1);
    	helpPutPreparedPlans(cache, token2, 1, 1);
    	//should have one
    	assertNotNull(cache.get(new CacheID(token, pi, EXAMPLE_QUERY + 1)), "Unable to get prepared plan from cache for token"); //$NON-NLS-1$
    	cache.clearAll();
    	//should not exist for token
    	assertNull(cache.get(new CacheID(token, pi, EXAMPLE_QUERY + 1)), "Failed remove from cache"); //$NON-NLS-1$
    	//should not exist for token2
    	assertNull(cache.get(new CacheID(token2, pi, EXAMPLE_QUERY + 1)), "Unable to get prepared plan from cache for token2"); //$NON-NLS-1$
    }

	// set init size to negative number, which should default to max
    @Test public void testNegativeSizeCacheUsesDefault() {
	    CacheConfiguration config = new CacheConfiguration();
	    config.setMaxEntries(-1);
	    new SessionAwareCache<PreparedPlan>("preparedplan", new DefaultCacheFactory(config), SessionAwareCache.Type.PREPAREDPLAN, 0);
	    // -1 means unlimited in the infinispan
    }
    
    //====Help methods====//
    private void helpPutPreparedPlans(SessionAwareCache<PreparedPlan> cache, DQPWorkContext session, int start, int count){
    	for(int i=0; i<count; i++){
    		Command dummy;
			try {
				dummy = QueryParser.getQueryParser().parseCommand(EXAMPLE_QUERY + (start + i)); 
			} catch (QueryParserException e) {
				throw new RuntimeException(e);
			}
	    	CacheID id = new CacheID(session, pi, dummy.toString());

	    	PreparedPlan pPlan = new PreparedPlan();
    		cache.put(id, Determinism.SESSION_DETERMINISTIC, pPlan, null);
    		pPlan.setCommand(dummy); 
    		pPlan.setPlan(new RelationalPlan(new ProjectNode(i)), new CommandContext());
            AnalysisRecord analysisRecord = new AnalysisRecord(true, false);
    		pPlan.setAnalysisRecord(analysisRecord);
    		ArrayList<Reference> refs = new ArrayList<Reference>();
    		refs.add(new Reference(1));
    		pPlan.setReferences(refs);
    	}
    }
    
}
