package org.apache.helix.zookeeper.datamodel;

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

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Operations to divide a ZNRecord into specified buckets
 */
public class ZNRecordBucketizer {
  private static Logger LOG = LoggerFactory.getLogger(ZNRecordBucketizer.class);
  final int _bucketSize;

  /**
   * Instantiate a bucketizer with the number of buckets
   * @param bucketSize
   */
  public ZNRecordBucketizer(int bucketSize) {
    if (bucketSize <= 0) {
      LOG.debug("bucketSize <= 0 (was " + bucketSize
          + "). Set to 0 to use non-bucketized HelixProperty.");
      bucketSize = 0;
    }

    _bucketSize = bucketSize;
  }

  /**
   * Calculate bucketName in form of "resourceName_p{startPartition}-p{endPartition}
   * @param partitionName
   * @return the bucket name
   */
  public String getBucketName(String key) {
    if (_bucketSize == 0) {
      // no bucketize
      return null;
    }

    int idx = key.lastIndexOf('_');
    if (idx < 0) {
      throw new IllegalArgumentException("Could NOT find partition# in " + key
          + ". partitionName should be in format of resourceName_partition#");
    }

    try {
      int partitionNb = Integer.parseInt(key.substring(idx + 1));
      int bucketNb = partitionNb / _bucketSize;
      int startPartition = bucketNb * _bucketSize;
      int endPartition = bucketNb * _bucketSize + (_bucketSize - 1);
      return key.substring(0, idx) + "_p" + startPartition + "-p" + endPartition;
    } catch (NumberFormatException e) {
      throw new IllegalArgumentException("Could NOT parse partition# (" + key.substring(idx + 1)
          + ") in " + key);
    }
  }

  /**
   * Bucketize a ZNRecord
   * @param record
   * @return A map of bucket names to bucketized records
   */
  public Map<String, ZNRecord> bucketize(ZNRecord record) {
    Map<String, ZNRecord> map = new HashMap<String, ZNRecord>();
    if (_bucketSize == 0) {
      map.put(record.getId(), record);
      return map;
    }

    // bucketize list field
    for (String partitionName : record.getListFields().keySet()) {
      String bucketName = getBucketName(partitionName);
      if (bucketName != null) {
        if (!map.containsKey(bucketName)) {
          map.put(bucketName, new ZNRecord(bucketName));
        }
        ZNRecord bucketizedRecord = map.get(bucketName);
        bucketizedRecord.setListField(partitionName, record.getListField(partitionName));
      } else {
        LOG.error("Can't bucketize " + partitionName + " in list field");
      }
    }

    // bucketize map field
    for (String partitionName : record.getMapFields().keySet()) {
      String bucketName = getBucketName(partitionName);
      if (bucketName != null) {
        if (!map.containsKey(bucketName)) {
          map.put(bucketName, new ZNRecord(bucketName));
        }
        ZNRecord bucketizedRecord = map.get(bucketName);
        bucketizedRecord.setMapField(partitionName, record.getMapField(partitionName));
      } else {
        LOG.error("Can't bucketize " + partitionName + " in map field");
      }
    }

    // copy all simple fields
    for (ZNRecord bucketizedRecord : map.values()) {
      bucketizedRecord.setSimpleFields(record.getSimpleFields());
    }
    return map;
  }
}
