/*
 * Copyright (c) 2017. Hans-Peter Grahsl ([email protected])
 *
 * Licensed 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 at.grahsl.kafka.connect.mongodb.processor.field.projection;

import at.grahsl.kafka.connect.mongodb.MongoDbSinkConnectorConfig;
import com.mongodb.DBCollection;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonValue;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public abstract class WhitelistProjector extends FieldProjector {

    public WhitelistProjector(MongoDbSinkConnectorConfig config,String collection) {
        this(config, config.getValueProjectionList(collection), collection);
    }

    public WhitelistProjector(MongoDbSinkConnectorConfig config,
                              Set<String> fields, String collection) {
        super(config,collection);
        this.fields = fields;
    }

    @Override
    protected void doProjection(String field, BsonDocument doc) {

        //special case short circuit check for '**' pattern
        //this is essentially the same as not using
        //whitelisting at all but instead take the full record
        if(fields.contains(FieldProjector.DOUBLE_WILDCARD)) {
            return;
        }

        Iterator<Map.Entry<String, BsonValue>> iter = doc.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry<String, BsonValue> entry = iter.next();

            String key = field.isEmpty() ? entry.getKey()
                    : field + FieldProjector.SUB_FIELD_DOT_SEPARATOR + entry.getKey();
            BsonValue value = entry.getValue();

            if(!fields.contains(key)
                    //NOTE: always keep the _id field
                    && !key.equals(DBCollection.ID_FIELD_NAME)) {

                if(!checkForWildcardMatch(key))
                    iter.remove();

            }

            if(value != null) {
                if(value.isDocument()) {
                    //short circuit check to avoid recursion
                    //if 'key.**' pattern exists
                    String matchDoubleWildCard = key
                            + FieldProjector.SUB_FIELD_DOT_SEPARATOR
                            + FieldProjector.DOUBLE_WILDCARD;
                    if(!fields.contains(matchDoubleWildCard)) {
                        doProjection(key, (BsonDocument)value);
                    }
                }
                if(value.isArray()) {
                    BsonArray values = (BsonArray)value;
                    for(BsonValue v : values.getValues()) {
                        if(v != null && v.isDocument()) {
                            doProjection(key,(BsonDocument)v);
                        }
                    }
                }
            }

        }
    }

    private boolean checkForWildcardMatch(String key) {

        String[] keyParts = key.split("\\"+FieldProjector.SUB_FIELD_DOT_SEPARATOR);
        String[] pattern = new String[keyParts.length];
        Arrays.fill(pattern,FieldProjector.SINGLE_WILDCARD);

        for(int c=(int)Math.pow(2, keyParts.length)-1;c >= 0;c--) {

            int mask = 0x1;
            for(int d = keyParts.length-1;d >= 0;d--) {
                if((c & mask) != 0x0) {
                    pattern[d] = keyParts[d];
                }
                mask <<= 1;
            }

            if(fields.contains(String.join(FieldProjector.SUB_FIELD_DOT_SEPARATOR,pattern)))
                return true;

            Arrays.fill(pattern,FieldProjector.SINGLE_WILDCARD);
        }

        return false;
    }
}