/*
 * 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.phoenix.filter;

import java.io.IOException;

import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Writables;
import org.apache.phoenix.expression.Expression;


/**
 *
 * Filter that evaluates WHERE clause expression, used in the case where there
 * are references to multiple column qualifiers over multiple column families.
 * Also there same qualifier names in different families.
 * 
 */
public class MultiCFCQKeyValueComparisonFilter extends MultiKeyValueComparisonFilter {
    private final ImmutablePairBytesPtr ptr = new ImmutablePairBytesPtr();

    public MultiCFCQKeyValueComparisonFilter() {
    }

    public MultiCFCQKeyValueComparisonFilter(Expression expression, boolean allCFs, byte[] essentialCF) {
        super(expression, allCFs, essentialCF);
    }

    @Override
    protected Object setColumnKey(byte[] cf, int cfOffset, int cfLength,
            byte[] cq, int cqOffset, int cqLength) {
        ptr.set(cf, cfOffset, cfLength, cq, cqOffset, cqLength);
        return ptr;
    }

    @Override
    protected Object newColumnKey(byte[] cf, int cfOffset, int cfLength,
            byte[] cq, int cqOffset, int cqLength) {

        byte[] cfKey;
        if (cfOffset == 0 && cf.length == cfLength) {
            cfKey = cf;
        } else {
            // Copy bytes here, but figure cf names are typically a few bytes at most,
            // so this will be better than creating an ImmutableBytesPtr
            cfKey = new byte[cfLength];
            System.arraycopy(cf, cfOffset, cfKey, 0, cfLength);
        }
        cfSet.add(cfKey);
        return new ImmutablePairBytesPtr(cf, cfOffset, cfLength, cq, cqOffset, cqLength);
    }

    private static class ImmutablePairBytesPtr {
        private byte[] bytes1;
        private int offset1;
        private int length1;
        private byte[] bytes2;
        private int offset2;
        private int length2;
        private int hashCode;

        private ImmutablePairBytesPtr() {
        }

        private ImmutablePairBytesPtr(byte[] bytes1, int offset1, int length1, byte[] bytes2, int offset2, int length2) {
            set(bytes1, offset1, length1, bytes2, offset2, length2);
        }

        @Override
        public int hashCode() {
            return hashCode;
        }

        public void set(byte[] bytes1, int offset1, int length1, byte[] bytes2, int offset2, int length2) {
            this.bytes1 = bytes1;
            this.offset1 = offset1;
            this.length1 = length1;
            this.bytes2 = bytes2;
            this.offset2 = offset2;
            this.length2 = length2;
            int hash = 1;
            for (int i = offset1; i < offset1 + length1; i++)
                hash = (31 * hash) + bytes1[i];
            for (int i = offset2; i < offset2 + length2; i++)
                hash = (31 * hash) + bytes2[i];
            hashCode = hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            ImmutablePairBytesPtr that = (ImmutablePairBytesPtr)obj;
            if (this.hashCode != that.hashCode) return false;
            if (Bytes.compareTo(this.bytes2, this.offset2, this.length2, that.bytes2, that.offset2, that.length2) != 0) return false;
            if (Bytes.compareTo(this.bytes1, this.offset1, this.length1, that.bytes1, that.offset1, that.length1) != 0) return false;
            return true;
        }
    }

    public static MultiCFCQKeyValueComparisonFilter parseFrom(final byte [] pbBytes) throws DeserializationException {
        try {
            return (MultiCFCQKeyValueComparisonFilter)Writables.getWritable(pbBytes, new MultiCFCQKeyValueComparisonFilter());
        } catch (IOException e) {
            throw new DeserializationException(e);
        }
    }
}
