/*

   Derby - Class org.apache.derby.impl.sql.catalog.SYSCOLPERMSRowFactory

   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.derby.impl.sql.catalog;

import org.apache.derby.iapi.sql.dictionary.ColPermsDescriptor;
import org.apache.derby.iapi.sql.dictionary.DataDescriptorGenerator;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.dictionary.StatisticsDescriptor;
import org.apache.derby.iapi.sql.dictionary.SystemColumn;
import org.apache.derby.iapi.sql.dictionary.TupleDescriptor;
import org.apache.derby.iapi.sql.dictionary.PermissionsDescriptor;

import org.apache.derby.iapi.error.StandardException;

import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.execute.ExecRow;
import org.apache.derby.iapi.sql.execute.ExecIndexRow;
import org.apache.derby.iapi.sql.execute.ExecutionFactory;
import org.apache.derby.iapi.types.SQLChar;
import org.apache.derby.iapi.types.TypeId;
import org.apache.derby.iapi.types.UserType;
import org.apache.derby.iapi.types.DataValueFactory;
import org.apache.derby.iapi.types.RowLocation;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.types.NumberDataValue;
import org.apache.derby.iapi.types.StringDataValue;
import org.apache.derby.iapi.services.uuid.UUIDFactory;
import org.apache.derby.catalog.UUID;
import org.apache.derby.catalog.Statistics;
import org.apache.derby.iapi.services.io.FormatableBitSet;

import java.sql.Timestamp;

/**
 * Factory for creating a SYSCOLPERMS row.
 *
 */

class SYSCOLPERMSRowFactory extends PermissionsCatalogRowFactory
{
	static final String TABLENAME_STRING = "SYSCOLPERMS";

    // Column numbers for the SYSCOLPERMS table. 1 based
	private static final int COLPERMSID_COL_NUM = 1;
    private static final int GRANTEE_COL_NUM = 2;
    private static final int GRANTOR_COL_NUM = 3;
    private static final int TABLEID_COL_NUM = 4;
    private static final int TYPE_COL_NUM = 5;
    protected static final int COLUMNS_COL_NUM = 6;
    private static final int COLUMN_COUNT = 6;

    static final int GRANTEE_TABLE_TYPE_GRANTOR_INDEX_NUM = 0;
    static final int COLPERMSID_INDEX_NUM = 1;
    static final int TABLEID_INDEX_NUM = 2;
    protected static final int TOTAL_NUM_OF_INDEXES = 3;
	private static final int[][] indexColumnPositions = 
	{ 
		{ GRANTEE_COL_NUM, TABLEID_COL_NUM, TYPE_COL_NUM, GRANTOR_COL_NUM},
		{ COLPERMSID_COL_NUM },
		{ TABLEID_COL_NUM }
	};

    public static final int
        GRANTEE_COL_NUM_IN_GRANTEE_TABLE_TYPE_GRANTOR_INDEX = 1;

    private static final boolean[] indexUniqueness = { true, true, false};

    private	static final String[] uuids =
    {
        "286cc01e-0103-0e39-b8e7-00000010f010" // catalog UUID
		,"6074401f-0103-0e39-b8e7-00000010f010"	// heap UUID
		,"787c0020-0103-0e39-b8e7-00000010f010"	// index1
		,"c9a3808d-010c-42a2-ae15-0000000f67f8" //index2
		,"80220011-010c-bc85-060d-000000109ab8" //index3
    };

    SYSCOLPERMSRowFactory(UUIDFactory uuidf, ExecutionFactory ef, DataValueFactory dvf)
	{
		super(uuidf,ef,dvf);
		initInfo(COLUMN_COUNT, TABLENAME_STRING, indexColumnPositions, indexUniqueness, uuids);
	}

	public ExecRow makeRow(TupleDescriptor td, TupleDescriptor parent) throws StandardException
	{
        UUID						oid;
        String colPermID = null;
        DataValueDescriptor grantee = null;
        DataValueDescriptor grantor = null;
        String tableID = null;
        String type = null;
        FormatableBitSet columns = null;

        if( td == null)
        {
            grantee = getNullAuthorizationID();
            grantor = getNullAuthorizationID();
        }
        else
        {
            ColPermsDescriptor cpd = (ColPermsDescriptor) td;
            oid = cpd.getUUID();
            if ( oid == null )
            {
            	oid = getUUIDFactory().createUUID();
            	cpd.setUUID(oid);           
            }
            colPermID = oid.toString();
            grantee = getAuthorizationID( cpd.getGrantee());
            grantor = getAuthorizationID( cpd.getGrantor());
            tableID = cpd.getTableUUID().toString();
            type = cpd.getType();
            columns = cpd.getColumns();
        }
        ExecRow row = getExecutionFactory().getValueRow( COLUMN_COUNT);
        row.setColumn( COLPERMSID_COL_NUM, new SQLChar(colPermID));
        row.setColumn( GRANTEE_COL_NUM, grantee);
        row.setColumn( GRANTOR_COL_NUM, grantor);
        row.setColumn( TABLEID_COL_NUM, new SQLChar(tableID));
        row.setColumn( TYPE_COL_NUM, new SQLChar(type));
        row.setColumn( COLUMNS_COL_NUM, new UserType( (Object) columns));
        return row;
    } // end of makeRow

	/** builds a tuple descriptor from a row */
	public TupleDescriptor buildDescriptor(ExecRow row,
                                           TupleDescriptor parentTuple,
                                           DataDictionary	dataDictionary)
		throws StandardException
    {
        if( SanityManager.DEBUG)
            SanityManager.ASSERT( row.nColumns() == COLUMN_COUNT,
                                  "Wrong size row passed to SYSCOLPERMSRowFactory.buildDescriptor");

        String colPermsUUIDString = row.getColumn( COLPERMSID_COL_NUM).getString();
        UUID colPermsUUID = getUUIDFactory().recreateUUID(colPermsUUIDString);
        String tableUUIDString = row.getColumn( TABLEID_COL_NUM).getString();
        UUID tableUUID = getUUIDFactory().recreateUUID(tableUUIDString);
        String type = row.getColumn( TYPE_COL_NUM).getString();
        FormatableBitSet columns = (FormatableBitSet) row.getColumn( COLUMNS_COL_NUM).getObject();
        if( SanityManager.DEBUG)
            SanityManager.ASSERT( "s".equals( type) || "S".equals( type) ||
                                  "u".equals( type) || "U".equals( type) ||
                                  "r".equals( type) || "R".equals( type),
                                  "Invalid type passed to SYSCOLPERMSRowFactory.buildDescriptor");

        ColPermsDescriptor colPermsDesc =
	        new ColPermsDescriptor( dataDictionary, 
                    getAuthorizationID( row, GRANTEE_COL_NUM),
                    getAuthorizationID( row, GRANTOR_COL_NUM),
                    tableUUID, type, columns);
        colPermsDesc.setUUID(colPermsUUID);
        return colPermsDesc;
    } // end of buildDescriptor

	/** builds a column list for the catalog */
    public SystemColumn[] buildColumnList()
        throws StandardException
    {
        return new SystemColumn[] {
           SystemColumnImpl.getUUIDColumn("COLPERMSID", false),
           SystemColumnImpl.getIdentifierColumn("GRANTEE", false),
           SystemColumnImpl.getIdentifierColumn("GRANTOR", false),
           SystemColumnImpl.getUUIDColumn("TABLEID", false),
           SystemColumnImpl.getIndicatorColumn("TYPE"),
           SystemColumnImpl.getJavaColumn("COLUMNS",
                   "org.apache.derby.iapi.services.io.FormatableBitSet", false)    
        };
    }

	/**
	 * builds an index key row for a given index number.
	 */
  	public ExecIndexRow buildIndexKeyRow( int indexNumber,
                                          PermissionsDescriptor perm) 
  		throws StandardException
    {
        ExecIndexRow row = null;
        
        switch( indexNumber)
        {
        case GRANTEE_TABLE_TYPE_GRANTOR_INDEX_NUM:
            // RESOLVE We do not support the FOR GRANT OPTION, so column permission rows are unique on the
            // grantee, table UUID, and type columns. The grantor column will always have the name of the owner of the
            // table. So the index key, used for searching the index, only has grantee, table UUID, and type columns.
            // It does not have a grantor column.
            //
            // If we support FOR GRANT OPTION then there may be multiple table permissions rows for a
            // (grantee, tableID, type) combination. We must either handle the multiple rows, which is necessary for
            // checking permissions, or add a grantor column to the key, which is necessary for granting or revoking
            // permissions.
            row = getExecutionFactory().getIndexableRow( 3);
            row.setColumn(1, getAuthorizationID( perm.getGrantee()));
            ColPermsDescriptor colPerms = (ColPermsDescriptor) perm;
            String tableUUIDStr = colPerms.getTableUUID().toString();
            row.setColumn(2, new SQLChar(tableUUIDStr));
            row.setColumn(3, new SQLChar(colPerms.getType()));
            break;
        case COLPERMSID_INDEX_NUM:
            row = getExecutionFactory().getIndexableRow( 1);
            String colPermsUUIDStr = perm.getObjectID().toString();
            row.setColumn(1, new SQLChar(colPermsUUIDStr));
            break;
        case TABLEID_INDEX_NUM:
            row = getExecutionFactory().getIndexableRow( 1);
            colPerms = (ColPermsDescriptor) perm;
            tableUUIDStr = colPerms.getTableUUID().toString();
            row.setColumn(1, new SQLChar(tableUUIDStr));
            break;
        }
        return row;
    } // end of buildIndexKeyRow
    
    public int getPrimaryKeyIndexNumber()
    {
        return GRANTEE_TABLE_TYPE_GRANTOR_INDEX_NUM;
    }

    /**
     * Or a set of permissions in with a row from this catalog table
     *
     * @param row an existing row
     * @param perm a permission descriptor of the appropriate class for this PermissionsCatalogRowFactory class.
     * @param colsChanged An array with one element for each column in row. It is updated to
     *                    indicate which columns in row were changed
     *
     * @return The number of columns that were changed.
     *
     * @exception StandardException standard error policy
     */
    public int orPermissions( ExecRow row, PermissionsDescriptor perm, boolean[] colsChanged)
        throws StandardException
    {
        ColPermsDescriptor colPerms = (ColPermsDescriptor) perm;
        FormatableBitSet existingColSet = (FormatableBitSet) row.getColumn( COLUMNS_COL_NUM).getObject();
        FormatableBitSet newColSet = colPerms.getColumns();

        boolean changed = false;
        for( int i = newColSet.anySetBit(); i >= 0; i = newColSet.anySetBit(i))
        {
            if( ! existingColSet.get(i))
            {
                existingColSet.set( i);
                changed = true;
            }
        }
        if( changed)
        {
            colsChanged[ COLUMNS_COL_NUM - 1] = true;
            return 1;
        }
        return 0;
    } // end of orPermissions

    /**
     * Remove a set of permissions from a row from this catalog table
     *
     * @param row an existing row
     * @param perm a permission descriptor of the appropriate class for this PermissionsCatalogRowFactory class.
     * @param colsChanged An array with one element for each column in row. It is updated to
     *                    indicate which columns in row were changed
     *
     * @return -1 if there are no permissions left in the row, otherwise the number of columns that were changed.
     *
     * @exception StandardException standard error policy
     */
    public int removePermissions( ExecRow row, PermissionsDescriptor perm, boolean[] colsChanged)
        throws StandardException
    {
        ColPermsDescriptor colPerms = (ColPermsDescriptor) perm;
        FormatableBitSet removeColSet = colPerms.getColumns();
        if( removeColSet == null)
            // remove all of them
            return -1;
        
        FormatableBitSet existingColSet = (FormatableBitSet) row.getColumn( COLUMNS_COL_NUM).getObject();

        boolean changed = false;
        for( int i = removeColSet.anySetBit(); i >= 0; i = removeColSet.anySetBit(i))
        {
            if( existingColSet.get(i))
            {
                existingColSet.clear( i);
                changed = true;
            }
        }
        if( changed)
        {
            colsChanged[ COLUMNS_COL_NUM - 1] = true;
            if( existingColSet.anySetBit() < 0)
                return -1; // No column privileges left
            return 1; // A change, but there are some privileges left
        }
        return 0; // no change
    } // end of removePermissions
    
	/** 
	 * @see PermissionsCatalogRowFactory#setUUIDOfThePassedDescriptor
	 */
    public void setUUIDOfThePassedDescriptor(ExecRow row, PermissionsDescriptor perm)
    throws StandardException
    {
        DataValueDescriptor existingPermDVD = row.getColumn(COLPERMSID_COL_NUM);
        perm.setUUID(getUUIDFactory().recreateUUID(existingPermDVD.getString()));
    }
}
