/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.infinispan.server.rhq;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementDataTrait;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.modules.plugins.jbossas7.BaseComponent;
import org.rhq.modules.plugins.jbossas7.PluginStats;
import org.rhq.modules.plugins.jbossas7.json.Operation;
import org.rhq.modules.plugins.jbossas7.json.ReadAttribute;
import org.rhq.modules.plugins.jbossas7.json.Result;

public abstract class MetricsRemappingComponent<T extends MetricsRemappingComponent<?>> extends BaseComponent<T> {
   final Log log = LogFactory.getLog(this.getClass());
   private static final String INTERNAL = "_internal:";
   private static final int INTERNAL_SIZE = INTERNAL.length();
   public static final Map<String, String> server2plugin;
   public static final Map<String, String> plugin2server;

   static {
      server2plugin = new HashMap<String, String>();
      server2plugin.put("bytes-read", "bytesRead");
      server2plugin.put("bytes-written", "bytesWritten");
      server2plugin.put("cache-manager-status", "cacheManagerStatus");
      server2plugin.put("cluster-name", "clusterName");
      server2plugin.put("coordinator-address", "coordinatorAddress");
      server2plugin.put("local-address", "localAddress");
      server2plugin.put("cache-status", "cacheStatus");
      server2plugin.put("number-of-locks-available", "numberOfLocksAvailable");
      server2plugin.put("number-of-locks-held", "numberOfLocksHeld");
      server2plugin.put("concurrency-level", "concurrencyLevel");
      server2plugin.put("average-read-time", "averageReadTime");
      server2plugin.put("hit-ratio", "hitRatio");
      server2plugin.put("elapsed-time", "elapsedTime");
      server2plugin.put("read-write-ratio", "readWriteRatio");
      server2plugin.put("average-write-time", "averageWriteTime");
      server2plugin.put("hits", "hits");
      server2plugin.put("evictions", "evictions");
      server2plugin.put("remove-misses", "removeMisses");
      server2plugin.put("time-since-reset", "timeSinceReset");
      server2plugin.put("number-of-entries", "numberOfEntries");
      server2plugin.put("stores", "stores");
      server2plugin.put("remove-hits", "removeHits");
      server2plugin.put("misses", "misses");
      server2plugin.put("success-ratio", "successRatio");
      server2plugin.put("replication-count", "replicationCount");
      server2plugin.put("replication-failures", "replicationFailures");
      server2plugin.put("average-replication-time", "averageReplicationTime");
      server2plugin.put("commits", "commits");
      server2plugin.put("prepares", "prepares");
      server2plugin.put("rollbacks", "rollbacks");
      server2plugin.put("invalidations", "invalidations");
      server2plugin.put("passivations", "passivations");
      server2plugin.put("activations", "activations");
      server2plugin.put("cache-loader-loads", "cacheLoaderLoads");
      server2plugin.put("cache-loader-misses", "cacheLoaderMisses");

      plugin2server = new HashMap<String, String>(server2plugin.size());
      for (Entry<String, String> entry : server2plugin.entrySet()) {
         plugin2server.put(entry.getValue(), entry.getKey());
      }
   }

   @Override
   public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> metrics) throws Exception {

      for (MeasurementScheduleRequest req : metrics) {

         if (req.getName().startsWith(INTERNAL))
            processPluginStats(req, report);
         else {
            // Metrics from the application server

            String reqName = plugin2server.get(req.getName());
            if (reqName == null) {
               reqName = req.getName();
            }

            ComplexRequest request = null;
            Operation op;
            if (reqName.contains(":")) {
               request = ComplexRequest.create(reqName);
               op = new ReadAttribute(getAddress(), request.getProp());
            } else {
               op = new ReadAttribute(getAddress(), reqName); // TODO batching
            }

            Result res = getASConnection().execute(op);
            if (!res.isSuccess()) {
               log.warn("Getting metric [" + req.getName() + "] at [ " + getAddress() + "] failed: " + res.getFailureDescription());
               continue;
            }

            Object val = res.getResult();
            if (val == null) // One of the AS7 ways of telling "This is not implemented" See also AS7-1454
               continue;

            if (req.getDataType() == DataType.MEASUREMENT) {
               if (!val.equals("no metrics available")) { // AS 7 returns this
                  try {
                     if (request != null) {
                        HashMap<String, Number> myValues = (HashMap<String, Number>) val;
                        for (String key : myValues.keySet()) {
                           String sub = request.getSub();
                           if (key.equals(sub)) {
                              addMetric2Report(report, req, myValues.get(key));
                           }
                        }
                     } else {
                        addMetric2Report(report, req, val);
                     }
                  } catch (NumberFormatException e) {
                     log.warn("Non numeric input for [" + req.getName() + "] : [" + val + "]");
                  }
               }
            } else if (req.getDataType() == DataType.TRAIT) {

               String realVal = getStringValue(val);

               MeasurementDataTrait data = new MeasurementDataTrait(req, realVal);
               report.addData(data);
            }
         }
      }
   }

   private void processPluginStats(MeasurementScheduleRequest req, MeasurementReport report) {

      String name = req.getName();
      if (!name.startsWith(INTERNAL))
         return;

      name = name.substring(INTERNAL_SIZE);

      PluginStats stats = PluginStats.getInstance();
      MeasurementDataNumeric data;
      Double val;
      if (name.equals("mgmtRequests")) {
         val = (double) stats.getRequestCount();
      } else if (name.equals("requestTime")) {
         val = (double) stats.getRequestTime();
      } else if (name.equals("maxTime")) {
         val = (double) stats.getMaxTime();
      } else
         val = Double.NaN;

      data = new MeasurementDataNumeric(req, val);
      report.addData(data);
   }

   private void addMetric2Report(MeasurementReport report, MeasurementScheduleRequest req, Object val) {
      Double d = Double.parseDouble(getStringValue(val));
      MeasurementDataNumeric data = new MeasurementDataNumeric(req, d);
      report.addData(data);
   }

   private static class ComplexRequest {
      private String prop;
      private String sub;

      private ComplexRequest(String prop, String sub) {
         this.prop = prop;
         this.sub = sub;
      }

      public String getProp() {
         return prop;
      }

      public String getSub() {
         return sub;
      }

      public static ComplexRequest create(String requestName) {
         StringTokenizer tokenizer = new StringTokenizer(requestName, ":");
         return new ComplexRequest(tokenizer.nextToken(), tokenizer.nextToken());
      }
   }

}
