public Result process(
      long startMilli,
      boolean processDelayed,
      ProcessorConfig config,
      String[] items,
      Map<Product, ReadWriteData> usageDataByProduct,
      Map<Product, ReadWriteData> costDataByProduct,
      Map<String, Double> ondemandRate) {
    if (StringUtils.isEmpty(items[accountIdIndex])
        || StringUtils.isEmpty(items[productIndex])
        || StringUtils.isEmpty(items[usageTypeIndex])
        || StringUtils.isEmpty(items[operationIndex])
        || StringUtils.isEmpty(items[usageQuantityIndex])
        || StringUtils.isEmpty(items[costIndex])) return Result.ignore;

    Account account = config.accountService.getAccountById(items[accountIdIndex]);
    if (account == null) return Result.ignore;

    double usageValue = Double.parseDouble(items[usageQuantityIndex]);
    double costValue = Double.parseDouble(items[costIndex]);

    long millisStart;
    long millisEnd;
    try {
      millisStart = amazonBillingDateFormat.parseMillis(items[startTimeIndex]);
      millisEnd = amazonBillingDateFormat.parseMillis(items[endTimeIndex]);
    } catch (IllegalArgumentException e) {
      millisStart = amazonBillingDateFormat2.parseMillis(items[startTimeIndex]);
      millisEnd = amazonBillingDateFormat2.parseMillis(items[endTimeIndex]);
    }

    Product product = config.productService.getProductByAwsName(items[productIndex]);
    boolean reservationUsage = "Y".equals(items[reservedIndex]);
    ReformedMetaData reformedMetaData =
        reform(
            millisStart,
            config,
            product,
            reservationUsage,
            items[operationIndex],
            items[usageTypeIndex],
            items[descriptionIndex],
            costValue);
    product = reformedMetaData.product;
    Operation operation = reformedMetaData.operation;
    UsageType usageType = reformedMetaData.usageType;
    Zone zone = Zone.getZone(items[zoneIndex], reformedMetaData.region);

    int startIndex = (int) ((millisStart - startMilli) / AwsUtils.hourMillis);
    int endIndex = (int) ((millisEnd + 1000 - startMilli) / AwsUtils.hourMillis);

    Result result = Result.hourly;
    if (product == Product.ec2_instance) {
      result = processEc2Instance(processDelayed, reservationUsage, operation, zone);
    } else if (product == Product.redshift) {
      result = processRedshift(processDelayed, reservationUsage, operation, costValue);
    } else if (product == Product.data_transfer) {
      result = processDataTranfer(processDelayed, usageType);
    } else if (product == Product.cloudhsm) {
      result = processCloudhsm(processDelayed, usageType);
    } else if (product == Product.ebs) {
      result = processEbs(usageType);
    } else if (product == Product.rds) {
      result = processRds(usageType);
    }

    if (result == Result.ignore || result == Result.delay) return result;

    if (usageType.name.startsWith("TimedStorage-ByteHrs")) result = Result.daily;

    boolean monthlyCost =
        StringUtils.isEmpty(items[descriptionIndex])
            ? false
            : items[descriptionIndex].toLowerCase().contains("-month");

    ReadWriteData usageData = usageDataByProduct.get(null);
    ReadWriteData costData = costDataByProduct.get(null);
    ReadWriteData usageDataOfProduct = usageDataByProduct.get(product);
    ReadWriteData costDataOfProduct = costDataByProduct.get(product);

    if (result == Result.daily) {
      DateMidnight dm = new DateMidnight(millisStart, DateTimeZone.UTC);
      millisStart = dm.getMillis();
      startIndex = (int) ((millisStart - startMilli) / AwsUtils.hourMillis);
      endIndex = startIndex + 24;
    } else if (result == Result.monthly) {
      startIndex = 0;
      endIndex = usageData.getNum();
      int numHoursInMonth =
          new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24;
      usageValue = usageValue * endIndex / numHoursInMonth;
      costValue = costValue * endIndex / numHoursInMonth;
    }

    if (monthlyCost) {
      int numHoursInMonth =
          new DateTime(startMilli, DateTimeZone.UTC).dayOfMonth().getMaximumValue() * 24;
      usageValue = usageValue * numHoursInMonth;
    }

    int[] indexes;
    if (endIndex - startIndex > 1) {
      usageValue = usageValue / (endIndex - startIndex);
      costValue = costValue / (endIndex - startIndex);
      indexes = new int[endIndex - startIndex];
      for (int i = 0; i < indexes.length; i++) indexes[i] = startIndex + i;
    } else {
      indexes = new int[] {startIndex};
    }

    TagGroup tagGroup =
        TagGroup.getTagGroup(
            account, reformedMetaData.region, zone, product, operation, usageType, null);
    TagGroup resourceTagGroup = null;

    if (costValue > 0
        && !reservationUsage
        && product == Product.ec2_instance
        && tagGroup.operation == Operation.ondemandInstances) {
      String key = operation + "|" + tagGroup.region + "|" + usageType;
      ondemandRate.put(key, costValue / usageValue);
    }

    double resourceCostValue = costValue;
    if (items.length > resourceIndex
        && !StringUtils.isEmpty(items[resourceIndex])
        && config.resourceService != null) {

      if (config.useCostForResourceGroup.equals("modeled") && product == Product.ec2_instance)
        operation =
            Operation.getReservedInstances(
                config.reservationService.getDefaultReservationUtilization(0L));

      if (product == Product.ec2_instance && operation instanceof Operation.ReservationOperation) {
        UsageType usageTypeForPrice = usageType;
        if (usageType.name.endsWith(InstanceOs.others.name())) {
          usageTypeForPrice =
              UsageType.getUsageType(
                  usageType.name.replace(InstanceOs.others.name(), InstanceOs.windows.name()),
                  usageType.unit);
        }
        try {
          resourceCostValue =
              usageValue
                  * config.reservationService.getLatestHourlyTotalPrice(
                      millisStart,
                      tagGroup.region,
                      usageTypeForPrice,
                      config.reservationService.getDefaultReservationUtilization(0L));
        } catch (Exception e) {
          logger.error("failed to get RI price for " + tagGroup.region + " " + usageTypeForPrice);
          resourceCostValue = -1;
        }
      }

      String resourceGroupStr =
          config.resourceService.getResource(
              account, reformedMetaData.region, product, items[resourceIndex], items, millisStart);
      if (!StringUtils.isEmpty(resourceGroupStr)) {
        ResourceGroup resourceGroup = ResourceGroup.getResourceGroup(resourceGroupStr);
        resourceTagGroup =
            TagGroup.getTagGroup(
                account,
                reformedMetaData.region,
                zone,
                product,
                operation,
                usageType,
                resourceGroup);
        if (usageDataOfProduct == null) {
          usageDataOfProduct = new ReadWriteData();
          costDataOfProduct = new ReadWriteData();
          usageDataByProduct.put(product, usageDataOfProduct);
          costDataByProduct.put(product, costDataOfProduct);
        }
      }
    }

    if (config.randomizer != null && product == Product.monitor) return result;

    for (int i : indexes) {

      if (config.randomizer != null) {

        if (tagGroup.product != Product.rds
            && tagGroup.product != Product.s3
            && usageData.getData(i).get(tagGroup) != null) break;

        long time = millisStart + i * AwsUtils.hourMillis;
        usageValue =
            config.randomizer.randomizeUsage(
                time, resourceTagGroup == null ? tagGroup : resourceTagGroup, usageValue);
        costValue = usageValue * config.randomizer.randomizeCost(tagGroup);
      }
      if (product != Product.monitor) {
        Map<TagGroup, Double> usages = usageData.getData(i);
        Map<TagGroup, Double> costs = costData.getData(i);

        addValue(
            usages,
            tagGroup,
            usageValue,
            config.randomizer == null
                || tagGroup.product == Product.rds
                || tagGroup.product == Product.s3);
        addValue(
            costs,
            tagGroup,
            costValue,
            config.randomizer == null
                || tagGroup.product == Product.rds
                || tagGroup.product == Product.s3);
      } else {
        resourceCostValue = usageValue * config.costPerMonitorMetricPerHour;
      }

      if (resourceTagGroup != null) {
        Map<TagGroup, Double> usagesOfResource = usageDataOfProduct.getData(i);
        Map<TagGroup, Double> costsOfResource = costDataOfProduct.getData(i);

        if (config.randomizer == null
            || tagGroup.product == Product.rds
            || tagGroup.product == Product.s3) {
          addValue(usagesOfResource, resourceTagGroup, usageValue, product != Product.monitor);
          if (!config.useCostForResourceGroup.equals("modeled") || resourceCostValue < 0) {
            addValue(costsOfResource, resourceTagGroup, costValue, product != Product.monitor);
          } else {
            addValue(
                costsOfResource, resourceTagGroup, resourceCostValue, product != Product.monitor);
          }
        } else {
          Map<String, Double> distribution = config.randomizer.getDistribution(tagGroup);
          for (Map.Entry<String, Double> entry : distribution.entrySet()) {
            String app = entry.getKey();
            double dist = entry.getValue();
            resourceTagGroup =
                TagGroup.getTagGroup(
                    account,
                    reformedMetaData.region,
                    zone,
                    product,
                    operation,
                    usageType,
                    ResourceGroup.getResourceGroup(app));
            double usage = usageValue * dist;
            if (product == Product.ec2_instance) usage = (int) usageValue * dist;
            addValue(usagesOfResource, resourceTagGroup, usage, false);
            addValue(
                costsOfResource,
                resourceTagGroup,
                usage * config.randomizer.randomizeCost(tagGroup),
                false);
          }
        }
      }
    }

    return result;
  }
  protected ReformedMetaData reform(
      long millisStart,
      ProcessorConfig config,
      Product product,
      boolean reservationUsage,
      String operationStr,
      String usageTypeStr,
      String description,
      double cost) {

    Operation operation = null;
    UsageType usageType = null;
    InstanceOs os = null;

    // first try to retrieve region info
    int index = usageTypeStr.indexOf("-");
    String regionShortName = index > 0 ? usageTypeStr.substring(0, index) : "";
    Region region = regionShortName.isEmpty() ? null : Region.getRegionByShortName(regionShortName);
    if (region != null) {
      usageTypeStr = usageTypeStr.substring(index + 1);
    } else {
      region = Region.US_EAST_1;
    }

    if (operationStr.equals("EBS Snapshot Copy")) {
      product = Product.ebs;
    }

    if (usageTypeStr.startsWith("ElasticIP:")) {
      product = Product.eip;
    } else if (usageTypeStr.startsWith("EBS:")) product = Product.ebs;
    else if (usageTypeStr.startsWith("EBSOptimized:")) product = Product.ebs;
    else if (usageTypeStr.startsWith("CW:")) product = Product.cloudwatch;
    else if (usageTypeStr.startsWith("BoxUsage") && operationStr.startsWith("RunInstances")) {
      index = usageTypeStr.indexOf(":");
      usageTypeStr = index < 0 ? "m1.small" : usageTypeStr.substring(index + 1);

      if (reservationUsage && product == Product.ec2 && cost == 0)
        operation = Operation.reservedInstancesFixed;
      else if (reservationUsage && product == Product.ec2)
        operation =
            Operation.getReservedInstances(
                config.reservationService.getDefaultReservationUtilization(millisStart));
      else operation = Operation.ondemandInstances;
      os = getInstanceOs(operationStr);
    } else if (usageTypeStr.startsWith("Node") && operationStr.startsWith("RunComputeNode")) {
      index = usageTypeStr.indexOf(":");
      usageTypeStr = index < 0 ? "m1.small" : usageTypeStr.substring(index + 1);

      operation = getOperation(operationStr, reservationUsage, null);
      os = getInstanceOs(operationStr);
    } else if (usageTypeStr.startsWith("HeavyUsage")
        || usageTypeStr.startsWith("MediumUsage")
        || usageTypeStr.startsWith("LightUsage")) {
      index = usageTypeStr.indexOf(":");
      String offeringType;
      if (index < 0) {
        offeringType = usageTypeStr;
        usageTypeStr = "m1.small";
      } else {
        offeringType = usageTypeStr;
        usageTypeStr = usageTypeStr.substring(index + 1);
      }

      operation =
          getOperation(
              operationStr,
              reservationUsage,
              Ec2InstanceReservationPrice.ReservationUtilization.get(offeringType));
      os = getInstanceOs(operationStr);
    }

    if (usageTypeStr.equals("Unknown") || usageTypeStr.equals("Not Applicable")) {
      usageTypeStr = product.name;
    }

    if (operation == null) {
      operation = Operation.getOperation(operationStr);
    }

    if (product == Product.ec2 && operation instanceof Operation.ReservationOperation) {
      product = Product.ec2_instance;
      if (operation instanceof Operation.ReservationOperation) {
        if (os != InstanceOs.linux) {
          usageTypeStr = usageTypeStr + "." + os;
          operation =
              operation.name.startsWith("ReservedInstances")
                  ? operation
                  : Operation.ondemandInstances;
        }
      }
    }

    if (usageType == null) {
      usageType = UsageType.getUsageType(usageTypeStr, operation, description);
    }

    return new ReformedMetaData(region, product, operation, usageType);
  }