/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.empire.db;

import java.util.ArrayList;
import java.util.Iterator;

import org.apache.empire.data.DataType;
import org.apache.empire.exceptions.NotSupportedException;
import org.apache.empire.exceptions.InvalidOperationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * DBCmdParamList
 * This class handles command parameters for Prepared Statements
 */
public class DBCmdParamList implements DBCmdParams
{
    protected static final Logger log = LoggerFactory.getLogger(DBCmdParamList.class);

    protected ArrayList<DBCmdParam> cmdParams;
    
    private int paramUsageCount = 0;

    public DBCmdParamList()
    {
        cmdParams = null;
    }

    public DBCmdParamList(DBCmdParams initial)
    {
        if (initial.isEmpty())
        {   // empty
            cmdParams = null;
        }
        else
        {   // copy from initial
            cmdParams = new ArrayList<>(initial.size());
            for (DBCmdParam p : initial)
                cmdParams.add(p);
        }
    }

    public DBCmdParamList(int size)
    {
        cmdParams = (size>0 ? new ArrayList<>(size) : null);
    }
    
    @Override
    public boolean isEmpty()
    {
        return (cmdParams==null || cmdParams.isEmpty());
    }
    
    @Override
    public int size()
    {
        return (cmdParams==null ? 0 : cmdParams.size());
    }

    @Override
    public Iterator<DBCmdParam> iterator()
    {
        if (cmdParams==null)
            throw new NotSupportedException(this, "iterator");
        return cmdParams.iterator();
    }
    
    public void clear(int capacity)
    {
        paramUsageCount = 0;
        if (capacity>0)
            cmdParams= new ArrayList<DBCmdParam>(capacity);
        else
            cmdParams = null;
    }
    
    public void add(DBCmdParam param)
    {
        if (cmdParams==null)
            cmdParams= new ArrayList<DBCmdParam>();
        // Create and add the parameter to the parameter list 
        cmdParams.add(param);
    }
    
    public void add(DBCommand cmd, DataType type, Object value)
    {
        add(new DBCmdParam(cmd, type, value));
    }
    
    public void remove(DBCmdParam param)
    {
        if (cmdParams==null || !cmdParams.remove(param))
            log.warn("Unable to remove DBCmdParam: Param not found");
    }
    
    public Object[] getParamValues()
    {
        if (cmdParams==null || paramUsageCount==0)
            return null;
        // Check whether all parameters have been used
        if (paramUsageCount!=cmdParams.size())
            log.info("DBCommand parameter count ("+String.valueOf(cmdParams.size())
                   + ") does not match parameter use count ("+String.valueOf(paramUsageCount)+")");
        // Create result array
        Object[] values = new Object[paramUsageCount];
        for (int i=0; i<values.length; i++)
            values[i]=cmdParams.get(i).getValue();
        // values
        return values;
    }
    
    /**
     * internally used to reset the command param usage count.
     * Note: Only one thread my generate an SQL statement 
     * @param cmd the command object
     */
    public void resetParamUsage(DBCommand cmd)
    {
        paramUsageCount = 0;
        if (cmdParams==null)
            return;
        // clear subquery params
        for (int i=cmdParams.size()-1; i>=0 ;i--)
            if (cmdParams.get(i).getCmd()!=cmd)
                cmdParams.remove(i);
    }
    
    /**
     * internally used to remove unused Command Params from list
     * Note: Only one thread my generate an SQL statement 
     * @param cmd the command object
     */
    public void completeParamUsage(DBCommand cmd)
    {
        if (cmdParams==null)
            return;
        // check whether all params have been used
        if (paramUsageCount < cmdParams.size())
        {   // Remove unused parameters
            log.warn("DBCommand has {} unused Command params", cmdParams.size()-paramUsageCount);
            for (int i=cmdParams.size()-1; i>=paramUsageCount; i--)
            {   // Remove temporary params
                if (cmdParams.get(i).getCmd()!=cmd)
                    cmdParams.remove(i);
            }
        }
    }
    
    /**
     * Used to merge CmdParams from a subquery with the current command params list
     * @param subQueryParams the subquery params
     */
    public void mergeSubqueryParams(DBCmdParams subQueryParams)
    {
        if (subQueryParams==null || subQueryParams.isEmpty())
            return;
        // Subquery has parameters
        if (cmdParams==null)
            cmdParams= new ArrayList<DBCmdParam>(subQueryParams.size());
        for (DBCmdParam p : subQueryParams)
            cmdParams.add(paramUsageCount++, new DBCmdParam(null, DataType.UNKNOWN, p.getValue()));
    }
    
    /**
     * internally used to reorder the command params to match their order of occurance
     * @param param the param being used
     */
    protected void notifyParamUsage(DBCmdParam param)
    {
        int index = cmdParams.indexOf(param);
        if (index<0) 
        {   // Error: parameter probably used twice in statement!
            throw new InvalidOperationException("The CmdParam has not been found on this Command.");
        }
        if (index < paramUsageCount)
        {   // Warn: parameter used twice in statement!
            log.debug("The DBCmdParam already been used. Adding a temporary copy");
            cmdParams.add(paramUsageCount, new DBCmdParam(null, param.getDataType(), param.getValue()));
        }
        else if (index > paramUsageCount)
        {   // Correct parameter order
            cmdParams.remove(index);
            cmdParams.add(paramUsageCount, param);
        }
        paramUsageCount++;
    }
}
