/*
 * 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$
 *  
 */

package org.teiid.query.processor.xml;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.teiid.api.exception.query.QueryParserException;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.common.buffer.BufferManagerFactory;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.metadata.MetadataStore;
import org.teiid.metadata.Schema;
import org.teiid.metadata.Table;
import org.teiid.query.mapping.relational.QueryNode;
import org.teiid.query.mapping.xml.MappingNodeConstants;
import org.teiid.query.mapping.xml.ResultSetInfo;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.parser.QueryParser;
import org.teiid.query.processor.FakeDataManager;
import org.teiid.query.processor.TestProcessor;
import org.teiid.query.resolver.QueryResolver;
import org.teiid.query.resolver.util.ResolverVisitor;
import org.teiid.query.rewriter.QueryRewriter;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.QueryCommand;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Symbol;
import org.teiid.query.unittest.RealMetadataFactory;
import org.teiid.query.util.CommandContext;


/**
 */
@SuppressWarnings("nls")
public class TestInstructions {

    /**
     * Construct some fake metadata.  Basic conceptual tree is:
     * 
     * stock (physical model)
     *   items (physical group)
     *     itemNum (string)
     *     itemName (string)
     *     itemQuantity (integer)
     * xmltest (virtual model)
     *   rs (virtual group / result set definition)
     *     itemNum (string)
     *     itemName (string)
     *     itemQuantity (integer)
     */
    public QueryMetadataInterface exampleMetadata() { 
    	MetadataStore metadataStore = new MetadataStore();
        // Create models
        Schema stock = RealMetadataFactory.createPhysicalModel("stock", metadataStore); //$NON-NLS-1$
        Schema xmltest = RealMetadataFactory.createVirtualModel("xmltest", metadataStore);     //$NON-NLS-1$

        // Create physical groups
        Table items = RealMetadataFactory.createPhysicalGroup("items", stock); //$NON-NLS-1$
                
        // Create physical elements
        RealMetadataFactory.createElements(items, 
            new String[] { "itemNum", "itemName", "itemQuantity" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            new String[] { DataTypeManager.DefaultDataTypes.STRING, DataTypeManager.DefaultDataTypes.STRING, DataTypeManager.DefaultDataTypes.INTEGER });

        // Create virtual groups
        QueryNode rsQuery = new QueryNode("SELECT itemNum, itemName, itemQuantity FROM stock.items"); //$NON-NLS-1$ //$NON-NLS-2$
        Table rs = RealMetadataFactory.createVirtualGroup("rs", xmltest, rsQuery); //$NON-NLS-1$

        // Create virtual elements
        RealMetadataFactory.createElements(rs, 
            new String[] { "itemNum", "itemName", "itemQuantity" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            new String[] { DataTypeManager.DefaultDataTypes.STRING, DataTypeManager.DefaultDataTypes.STRING, DataTypeManager.DefaultDataTypes.INTEGER });        
            
        // Create the facade from the store
        return RealMetadataFactory.createTransformationMetadata(metadataStore, "example");
    }
    
    private Command helpGetCommand(String sql, QueryMetadataInterface metadata) throws TeiidComponentException, TeiidProcessingException {
        // parse
        QueryParser parser = new QueryParser();
        Command command = parser.parseCommand(sql);
        
        QueryResolver.resolveCommand(command, metadata);
        command = QueryRewriter.rewrite(command, metadata, null);
        
        return command;        
    }

    private Criteria helpGetCriteria(String sql, QueryMetadataInterface metadata) throws QueryParserException, QueryResolverException, TeiidComponentException {
        QueryParser parser = new QueryParser();
        Criteria crit = parser.parseCriteria(sql);
   
        ResolverVisitor.resolveLanguageObject(crit, null, metadata);

        return crit;        
    }

    public void helpProcessInstructions(Program prog, XMLProcessorEnvironment env, String expected) throws Exception {
        env.pushProgram(prog);
    	XMLPlan plan = new XMLPlan(env);
    	TestProcessor.doProcess(plan, new FakeDataManager(), new List[] {Arrays.asList(expected)}, new CommandContext());
    }
    
    public Program exampleProgram() throws Exception{

        ProcessorInstruction i0 = new InitializeDocumentInstruction("UTF-8", true);         //$NON-NLS-1$
        NodeDescriptor descriptor = NodeDescriptor.createNodeDescriptor("Catalogs", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i1 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i2 = new MoveDocInstruction(MoveDocInstruction.DOWN);
        descriptor = NodeDescriptor.createNodeDescriptor("Catalog", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i3 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i4 = new MoveDocInstruction(MoveDocInstruction.DOWN);
        descriptor = NodeDescriptor.createNodeDescriptor("Items", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i5 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i6 = new MoveDocInstruction(MoveDocInstruction.DOWN);        
        
        ResultSetInfo info = new ResultSetInfo("xmltest.rs"); //$NON-NLS-1$
        ProcessorInstruction i7 = new ExecSqlInstruction("xmltest.rs", info);                 //$NON-NLS-1$
        ProcessorInstruction i8 = new MoveCursorInstruction("xmltest.rs"); //$NON-NLS-1$

        WhileInstruction i9 = new WhileInstruction("xmltest.rs");         //$NON-NLS-1$
        descriptor = NodeDescriptor.createNodeDescriptor("Item", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i10 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i11 = new MoveDocInstruction(MoveDocInstruction.DOWN);
        descriptor = NodeDescriptor.createNodeDescriptor("ItemID", null, AddNodeInstruction.ATTRIBUTE, null, null, null, false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i12 = new AddNodeInstruction(descriptor, new ElementSymbol("xmltest.rs.itemNum")); //$NON-NLS-1$
        descriptor = NodeDescriptor.createNodeDescriptor("Name", null, AddNodeInstruction.ELEMENT, null, null, null, false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i13 = new AddNodeInstruction(descriptor, new ElementSymbol("xmltest.rs.itemName"));//$NON-NLS-1$
        descriptor = NodeDescriptor.createNodeDescriptor("Quantity", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i14 = new AddNodeInstruction(descriptor, new ElementSymbol("xmltest.rs.itemQuantity"));//$NON-NLS-1$
        ProcessorInstruction i15 = new MoveDocInstruction(MoveDocInstruction.UP);
        ProcessorInstruction i16 = new MoveCursorInstruction("xmltest.rs"); //$NON-NLS-1$

        ProcessorInstruction i17 = new MoveDocInstruction(MoveDocInstruction.UP);
        ProcessorInstruction i18 = new MoveDocInstruction(MoveDocInstruction.UP);
        ProcessorInstruction i19 = new MoveDocInstruction(MoveDocInstruction.UP);
        
        ProcessorInstruction i20 = new EndDocumentInstruction();
        
        // Stitch them together
        
        Program program = new Program();
        program.addInstruction(i0);
        program.addInstruction(i1);
        program.addInstruction(i2);
        program.addInstruction(i3);
        program.addInstruction(i4);
        program.addInstruction(i5);
        program.addInstruction(i6);
        program.addInstruction(i7);
        program.addInstruction(i8);
        program.addInstruction(i9);

        Program subProgram = new Program();
        i9.setBlockProgram(subProgram);
        subProgram.addInstruction(i10);
        subProgram.addInstruction(i11);
        subProgram.addInstruction(i12);
        subProgram.addInstruction(i13);
        subProgram.addInstruction(i14);
        subProgram.addInstruction(i15);
        subProgram.addInstruction(i16);

        program.addInstruction(i17);
        program.addInstruction(i18);
        program.addInstruction(i19);
        program.addInstruction(i20);

        //ProgramUtil.printProgram(program);
        return program;        
    }

    public Program exampleProgram2(Criteria crit) throws Exception{
        ProcessorInstruction i0 = new InitializeDocumentInstruction("UTF-8", true);         //$NON-NLS-1$
        NodeDescriptor descriptor = NodeDescriptor.createNodeDescriptor("Catalogs", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i1 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i2 = new MoveDocInstruction(MoveDocInstruction.DOWN);
        descriptor = NodeDescriptor.createNodeDescriptor("Catalog", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i3 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i4 = new MoveDocInstruction(MoveDocInstruction.DOWN);
        descriptor = NodeDescriptor.createNodeDescriptor("Items", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i5 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i6 = new MoveDocInstruction(MoveDocInstruction.DOWN);        

        ResultSetInfo info = new ResultSetInfo("xmltest.rs"); //$NON-NLS-1$
        ProcessorInstruction i7 = new ExecSqlInstruction("xmltest.rs", info);                 //$NON-NLS-1$
        ProcessorInstruction i8 = new MoveCursorInstruction("xmltest.rs"); //$NON-NLS-1$

        WhileInstruction i9 = new WhileInstruction("xmltest.rs");         //$NON-NLS-1$

        //need to move this up here so it can be referenced by "If" instruction
        ProcessorInstruction i17 = new MoveCursorInstruction("xmltest.rs"); //$NON-NLS-1$

        IfInstruction i10 = new IfInstruction( );
        descriptor = NodeDescriptor.createNodeDescriptor("Item", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i11 = new AddNodeInstruction(descriptor);
        ProcessorInstruction i12 = new MoveDocInstruction(MoveDocInstruction.DOWN);
        descriptor = NodeDescriptor.createNodeDescriptor("ItemID", null, AddNodeInstruction.ATTRIBUTE, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i13 = new AddNodeInstruction(descriptor, new ElementSymbol("xmltest.rs.itemNum"));//$NON-NLS-1$
        descriptor = NodeDescriptor.createNodeDescriptor("Name", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i14 = new AddNodeInstruction(descriptor, new ElementSymbol("xmltest.rs.itemName"));//$NON-NLS-1$
        descriptor = NodeDescriptor.createNodeDescriptor("Quantity", null, AddNodeInstruction.ELEMENT, null, null, null,false, null, MappingNodeConstants.NORMALIZE_TEXT_PRESERVE);//$NON-NLS-1$
        ProcessorInstruction i15 = new AddNodeInstruction(descriptor, new ElementSymbol("xmltest.rs.itemQuantity")); //$NON-NLS-1$       
        ProcessorInstruction i16 = new MoveDocInstruction(MoveDocInstruction.UP);

        ProcessorInstruction i18 = new MoveDocInstruction(MoveDocInstruction.UP);
        ProcessorInstruction i19 = new MoveDocInstruction(MoveDocInstruction.UP);
        ProcessorInstruction i20 = new MoveDocInstruction(MoveDocInstruction.UP);
        ProcessorInstruction i21 = new EndDocumentInstruction();

        
        // Stitch them together

        Program program = new Program();
        program.addInstruction(i0);
        program.addInstruction(i1);
        program.addInstruction(i2);
        program.addInstruction(i3);
        program.addInstruction(i4);
        program.addInstruction(i5);
        program.addInstruction(i6);
        program.addInstruction(i7);
        program.addInstruction(i8);
        program.addInstruction(i9);

        Program whileProgram = new Program();
        i9.setBlockProgram(whileProgram);
        whileProgram.addInstruction(i10);

        Program thenProgram = new Program();
        Condition cond = new CriteriaCondition(crit, thenProgram); 
        i10.addCondition(cond);

        thenProgram.addInstruction(i11);
        thenProgram.addInstruction(i12);
        thenProgram.addInstruction(i13);
        thenProgram.addInstruction(i14);
        thenProgram.addInstruction(i15);
        thenProgram.addInstruction(i16);

        whileProgram.addInstruction(i17);

        program.addInstruction(i18);
        program.addInstruction(i19);
        program.addInstruction(i20);
        program.addInstruction(i21);

        //ProgramUtil.printProgram(program);
        return program;        
    }
    
    @Test
    public void testProcess1() throws Exception {
        QueryMetadataInterface metadata = exampleMetadata();
        String resultSetName = "xmltest.rs"; //$NON-NLS-1$
        
        FakeXMLProcessorEnvironment env = new FakeXMLProcessorEnvironment();
        Program program = exampleProgram();
        
        BufferManager bufferMgr = BufferManagerFactory.getStandaloneBufferManager();
        XMLPlan temp = new XMLPlan(env);
        CommandContext context = new CommandContext("pid", null, null, null, 1); //$NON-NLS-1$
        temp.initialize(context,null,bufferMgr);

        List<ElementSymbol> schema = new ArrayList<ElementSymbol>();
        schema.add(new ElementSymbol(resultSetName + Symbol.SEPARATOR + "itemNum")); //$NON-NLS-1$
        schema.add(new ElementSymbol(resultSetName + Symbol.SEPARATOR + "itemName")); //$NON-NLS-1$
        schema.add(new ElementSymbol(resultSetName + Symbol.SEPARATOR + "itemQuantity")); //$NON-NLS-1$
        
        env.addData(resultSetName, schema, new List[] { 
                    Arrays.asList( new Object[] { "001", "Lamp", new Integer(5) } ),         //$NON-NLS-1$ //$NON-NLS-2$
                    Arrays.asList( new Object[] { "002", "Screwdriver", new Integer(100) } ),         //$NON-NLS-1$ //$NON-NLS-2$
                    Arrays.asList( new Object[] { "003", "Goat", new Integer(4) } )         //$NON-NLS-1$ //$NON-NLS-2$
                    } );            
        helpProcessInstructions(program, env,  
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +  //$NON-NLS-1$
            "<Catalogs>\n" + //$NON-NLS-1$
            "   <Catalog>\n" +  //$NON-NLS-1$
            "      <Items>\n" +  //$NON-NLS-1$
            "         <Item ItemID=\"001\">\n" +  //$NON-NLS-1$
            "            <Name>Lamp</Name>\n" +  //$NON-NLS-1$
            "            <Quantity>5</Quantity>\n" +  //$NON-NLS-1$
            "         </Item>\n" +  //$NON-NLS-1$
            "         <Item ItemID=\"002\">\n" +  //$NON-NLS-1$
            "            <Name>Screwdriver</Name>\n" +  //$NON-NLS-1$
            "            <Quantity>100</Quantity>\n" +  //$NON-NLS-1$
            "         </Item>\n" +  //$NON-NLS-1$
            "         <Item ItemID=\"003\">\n" +  //$NON-NLS-1$
            "            <Name>Goat</Name>\n" +  //$NON-NLS-1$
            "            <Quantity>4</Quantity>\n" +  //$NON-NLS-1$
            "         </Item>\n" +  //$NON-NLS-1$
            "      </Items>\n" +  //$NON-NLS-1$
            "   </Catalog>\n" +  //$NON-NLS-1$
            "</Catalogs>"); //$NON-NLS-1$
    }

    @Test
    public void testProcess2() throws Exception {
        QueryMetadataInterface metadata = exampleMetadata();

        String resultSetName = "xmltest.rs"; //$NON-NLS-1$
        
        String sql = "SELECT itemNum, itemName, itemQuantity FROM xmltest.rs";         //$NON-NLS-1$
        QueryCommand command = (QueryCommand) helpGetCommand(sql, metadata);               
        
        Criteria crit = helpGetCriteria("xmltest.rs.itemName = 'Screwdriver'", metadata);  //$NON-NLS-1$ 
        FakeXMLProcessorEnvironment env = new FakeXMLProcessorEnvironment();
        Program program = exampleProgram2(crit);
                
        BufferManager bufferMgr = BufferManagerFactory.getStandaloneBufferManager();
        XMLPlan temp = new XMLPlan(env);
        CommandContext context = new CommandContext("pid", null, null, null, 1); //$NON-NLS-1$
        temp.initialize(context,null,bufferMgr);
        env.addData(resultSetName, command.getProjectedSymbols(), new List[] { 
                    Arrays.asList( new Object[] { "001", "Lamp", new Integer(5) } ),         //$NON-NLS-1$ //$NON-NLS-2$
                    Arrays.asList( new Object[] { "002", "Screwdriver", new Integer(100) } ),         //$NON-NLS-1$ //$NON-NLS-2$
                    Arrays.asList( new Object[] { "003", "Goat", new Integer(4) } )         //$NON-NLS-1$ //$NON-NLS-2$
                    } );            

        helpProcessInstructions(program, env, 
        	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +  //$NON-NLS-1$
            "<Catalogs>\n" + //$NON-NLS-1$
            "   <Catalog>\n" +  //$NON-NLS-1$
            "      <Items>\n" +  //$NON-NLS-1$
            "         <Item ItemID=\"002\">\n" +  //$NON-NLS-1$
            "            <Name>Screwdriver</Name>\n" +  //$NON-NLS-1$
            "            <Quantity>100</Quantity>\n" +  //$NON-NLS-1$
            "         </Item>\n" +  //$NON-NLS-1$
            "      </Items>\n" +  //$NON-NLS-1$
            "   </Catalog>\n" +  //$NON-NLS-1$
            "</Catalogs>"); //$NON-NLS-1$
    }
}
