/**
 * 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.ambari.view.slider.rest.client;

import org.apache.log4j.Logger;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import java.util.ArrayList;
import java.util.List;

@JsonIgnoreProperties({"keyName", "matchers", "xPathExpression", "xPathExpressionComputed"})
public class Metric {
  private static final Logger logger = Logger
      .getLogger(Metric.class);
  private static String SEPARATOR = ".";
  private static char SEPARATOR_REPLACED = '#';
  private String metric;
  private boolean pointInTime;
  private boolean temporal;
  @JsonIgnore
  private String keyName = null;
  @JsonIgnore
  private List<List<String>> matchers = null;
  @JsonIgnore
  private XPathExpression xPathExpression = null;
  @JsonIgnore
  private boolean xPathExpressionComputed = false;

  private Metric() {
  }

  protected Metric(String metric, boolean pointInTime, boolean temporal) {
    this.metric = metric;
    this.pointInTime = pointInTime;
    this.temporal = temporal;
  }

  public String getMetric() {
    return metric;
  }

  public void setMetric(String metric) {
    this.metric = metric;
  }

  public boolean isPointInTime() {
    return pointInTime;
  }

  public void setPointInTime(boolean pointInTime) {
    this.pointInTime = pointInTime;
  }

  public boolean isTemporal() {
    return temporal;
  }

  public void setTemporal(boolean temporal) {
    this.temporal = temporal;
  }

  @JsonIgnore
  public XPathExpression getxPathExpression() {
    if (!xPathExpressionComputed) {
      XPathFactory xPathfactory = XPathFactory.newInstance();
      XPath xpath = xPathfactory.newXPath();
      XPathExpression schemaPath = null;
      try {
        schemaPath = xpath.compile(metric);
      } catch (XPathExpressionException e) {
        logger.info(String.format("Unable to compile %s into xpath expression", metric));
      }
      xPathExpression = schemaPath;
      xPathExpressionComputed = true;
    }

    return xPathExpression;
  }

  @JsonIgnore
  public String getJmxBeanKeyName() {
    if (keyName == null) {
      int firstIndex = metric.indexOf(SEPARATOR);
      if (firstIndex > 0) {
        keyName = metric.substring(0, firstIndex).replace(SEPARATOR_REPLACED, '.');
      }
    }
    return keyName;
  }

  /**
   * Matcher is of the form a.b.c... They can be matched as a -> b-> c or a.b -> c or a -> b.c etc. The matcher returns
   * all possibilities in priority order
   *
   * @return
   */
  @JsonIgnore
  public List<List<String>> getMatchers() {
    if (matchers == null) {
      List<List<String>> tmpMatchers = new ArrayList<List<String>>();
      int matcherStartIndex = metric.indexOf(SEPARATOR);
      if (matcherStartIndex > 0) {
        String allTagsStr = metric.substring(matcherStartIndex + 1);
        String[] tags = allTagsStr.split("\\.");
        if (tags.length > 0) {
          extractMatchers(tags, -1, tmpMatchers, null);
        }
      }

      matchers = tmpMatchers;
    }
    return matchers;
  }

  public void extractMatchers(String[] tags, int index, List<List<String>> matchers, ArrayList<String> currentSet) {
    if (tags.length == index + 1) {
      matchers.add(currentSet);
    } else {
      if (index == -1) {
        currentSet = new ArrayList<String>();
        currentSet.add(tags[0]);
        extractMatchers(tags, 0, matchers, currentSet);
      } else {
        ArrayList<String> mergeAndProceed = new ArrayList<String>(currentSet);
        mergeAndProceed.add(tags[index + 1]);
        extractMatchers(tags, index + 1, matchers, mergeAndProceed);

        ArrayList<String> appendAndProceed = new ArrayList<String>(currentSet);
        int lastIndex = appendAndProceed.size() - 1;
        appendAndProceed.set(lastIndex, appendAndProceed.get(lastIndex) + "." + tags[index + 1]);
        extractMatchers(tags, index + 1, matchers, appendAndProceed);
      }
    }
  }
}
