/*
 * Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
 * 
 * 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 monasca.common.model.alarm;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.util.List;

import org.testng.annotations.Test;

import com.google.common.collect.ImmutableMap;

import monasca.common.model.metric.MetricDefinition;

@Test
public class AlarmExpressionTest {
  private final String restrictedChars = "(){}&|<>=\",";

    public void shouldParseExpression() {
        AlarmExpression expr = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3");
        List<AlarmSubExpression> alarms = expr.getSubExpressions();

        AlarmSubExpression expected1 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("instance_id", "5")
                        .put("metric_name", "cpu")
                        .put("device", "1")
                        .build()), AlarmOperator.GT, 5, 1, 3);
        AlarmSubExpression expected2 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("flavor_id", "3")
                        .put("metric_name", "mem")
                        .build()), AlarmOperator.LT, 4, 2, 3);

        assertEquals(alarms.get(0), expected1);
        assertEquals(alarms.get(1), expected2);
    }

    public void shouldParseString() {


        AlarmExpression expr = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1, url=\"https://www.google.com/?startpage=3&happygoing\"}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem, specialchars=\"!@#$%^&*()~<>{}[],.\"}, 2) < 4 times 3");
        List<AlarmSubExpression> alarms = expr.getSubExpressions();

        AlarmSubExpression expected1 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("instance_id", "5")
                        .put("metric_name", "cpu")
                        .put("url", "\"https://www.google.com/?startpage=3&happygoing\"")
                        .put("device", "1")
                        .build()), AlarmOperator.GT, 5, 1, 3);

        AlarmSubExpression expected2 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("flavor_id", "3")
                        .put("metric_name", "mem")
                        .put("specialchars", "\"!@#$%^&*()~<>{}[],.\"")
                        .build()), AlarmOperator.LT, 4, 2, 3);

        assertEquals(alarms.get(0), expected1);
        assertEquals(alarms.get(1), expected2);
    }

    public void shouldParseComplexWithoutQuotes() {


        AlarmExpression expr = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1, url=https%3A%2F%2Fwww.google.com%2F%3Fstartpage%3D3%26happygoing}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem, specialchars=a!@#/\\$%^*~}, 2) < 4 times 3");
        List<AlarmSubExpression> alarms = expr.getSubExpressions();


        AlarmExpression containsDirectories = new AlarmExpression("avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1,global=$_globalVariable,special=__useSparingly,dos=\\system32\\, windows=C:\\system32\\}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem,$globalVariable=global,__useSparingly=special,unix=/opt/vertica/bin/}, 2) < 4 times 3");
        List<AlarmSubExpression> alarmsContainsDirectories = containsDirectories.getSubExpressions();

        AlarmSubExpression expected1 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("instance_id", "5")
                        .put("metric_name", "cpu")
                        .put("url", "https%3A%2F%2Fwww.google.com%2F%3Fstartpage%3D3%26happygoing")
                        .put("device", "1")
                        .build()), AlarmOperator.GT, 5, 1, 3);

        AlarmSubExpression expected2 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("flavor_id", "3")
                        .put("metric_name", "mem")
                        .put("specialchars", "a!@#/\\$%^*~")
                        .build()), AlarmOperator.LT, 4, 2, 3);

        AlarmSubExpression expected3 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("instance_id", "5")
                        .put("metric_name", "cpu")
                        .put("device", "1")
                        .put("global", "$_globalVariable")
                        .put("special", "__useSparingly")
                        .put("dos", "\\system32\\")
                        .put("windows", "C:\\system32\\")
                        .build()), AlarmOperator.GT, 5, 1, 3);

        AlarmSubExpression expected4 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("flavor_id", "3")
                        .put("metric_name", "mem")
                        .put("$globalVariable", "global")
                        .put("__useSparingly", "special")
                        .put("unix", "/opt/vertica/bin/")
                        .build()), AlarmOperator.LT, 4, 2, 3);
        assertEquals(alarms.get(0), expected1);
        assertEquals(alarms.get(1), expected2);
        assertEquals(alarmsContainsDirectories.get(0), expected3);
        assertEquals(alarmsContainsDirectories.get(1), expected4);
    }

    public void shouldParseExpressionWithoutType() {
        AlarmExpression expr = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3");
        List<AlarmSubExpression> alarms = expr.getSubExpressions();

        AlarmSubExpression expected1 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("instance_id", "5")
                        .put("metric_name", "cpu")
                        .put("device", "1")
                        .build()), AlarmOperator.GT, 5, 1, 3);
        AlarmSubExpression expected2 = new AlarmSubExpression(AggregateFunction.AVG,
                new MetricDefinition("hpcs.compute", ImmutableMap.<String, String>builder()
                        .put("flavor_id", "3")
                        .put("metric_name", "mem")
                        .build()), AlarmOperator.LT, 4, 2, 3);

        assertEquals(alarms.get(0), expected1);
        assertEquals(alarms.get(1), expected2);
    }

    public void shouldEvaluateExpression() {
        AlarmExpression expr = new AlarmExpression(
                "sum(hpcs.compute{instance_id=5,metric_name=disk}, 1) > 33 or (avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3)");
        List<AlarmSubExpression> alarms = expr.getSubExpressions();

        AlarmSubExpression alarm1 = alarms.get(0);
        AlarmSubExpression alarm2 = alarms.get(1);
        AlarmSubExpression alarm3 = alarms.get(2);

        assertTrue(expr.evaluate(ImmutableMap.<AlarmSubExpression, Boolean>builder()
                .put(alarm1, true)
                .put(alarm2, false)
                .put(alarm3, false)
                .build()));

        assertTrue(expr.evaluate(ImmutableMap.<AlarmSubExpression, Boolean>builder()
                .put(alarm1, false)
                .put(alarm2, true)
                .put(alarm3, true)
                .build()));

        assertFalse(expr.evaluate(ImmutableMap.<AlarmSubExpression, Boolean>builder()
                .put(alarm1, false)
                .put(alarm2, false)
                .put(alarm3, true)
                .build()));

        assertFalse(expr.evaluate(ImmutableMap.<AlarmSubExpression, Boolean>builder()
                                      .put(alarm1, false)
                                      .put(alarm2, true)
                                      .put(alarm3, false)
                                      .build()));
    }

    public void shouldDefaultPeriodAndPeriods() {
        AlarmExpression expr = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=1}) > 5");
        AlarmSubExpression alarm = expr.getSubExpressions().get(0);
        assertEquals(alarm.getPeriod(), 60);
        assertEquals(alarm.getPeriods(), 1);
    }

    @Test(expectedExceptions = IllegalArgumentException.class)
    public void shouldThrowOnEvaluateInvalidSubExpressions() {
        AlarmExpression expr = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=2}, 1) > 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3");
        expr.evaluate(ImmutableMap.<AlarmSubExpression, Boolean>builder()
                          .put(
                              new AlarmSubExpression(AggregateFunction.AVG,
                                                     new MetricDefinition("hpcs.compute",
                                                                          ImmutableMap
                                                                              .<String, String>builder()
                                                                              .put("flavor_id", "3")
                                                                              .put("metric_name",
                                                                                   "mem")
                                                                              .build()),
                                                     AlarmOperator.LT, 4, 2, 3), true)
                          .build());
    }

    public void shouldGetAlarmExpressionTree() {
        Object expr = AlarmExpression.of(
                "(avg(foo) > 1 and avg(bar) < 2 and avg(baz) > 3) or (avg(foo) > 4 and avg(bar) < 5 and avg(baz) > 6)")
                .getExpressionTree();
        assertEquals(
                expr.toString(),
                "((avg(foo) > 1.0 AND avg(bar) < 2.0 AND avg(baz) > 3.0) OR (avg(foo) > 4.0 AND avg(bar) < 5.0 AND avg(baz) > 6.0))");

        expr = AlarmExpression.of(
                "(avg(foo) > 1 and (avg(bar) < 2 or avg(baz) > 3)) and (avg(foo) > 4 or avg(bar) < 5 or avg(baz) > 6)")
                .getExpressionTree();
        assertEquals(
                expr.toString(),
                "(avg(foo) > 1.0 AND (avg(bar) < 2.0 OR avg(baz) > 3.0) AND (avg(foo) > 4.0 OR avg(bar) < 5.0 OR avg(baz) > 6.0))");
    }

    @Test(enabled = false)
    public void testExpressionEquality() {
        AlarmExpression expr1 = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=a}, 1) lt 5 times 3 and avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3");
        AlarmExpression expr2 = new AlarmExpression(
                "avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) gt 3  times 3 && avg(hpcs.compute{instance_id=5,metric_name=cpu,device=a}, 1) lt 5 times 3");
        assertEquals(expr1, expr2);

        AlarmExpression expr3 = new AlarmExpression(
                "avg(hpcs.compute{instance_id=5,metric_name=cpu,device=a}, 1) lt 5 times 444 and avg(hpcs.compute{flavor_id=3,metric_name=mem}, 2) < 4 times 3");
        assertNotEquals(expr1, expr3);
    }

    public void shouldParseNamesWithUnicode() {
      AlarmExpression expr1 = new AlarmExpression(
          "公{此=该,metric_name=mem} > 4"
      );
      AlarmSubExpression alarm1 = expr1.getSubExpressions().get(0);
      MetricDefinition expected1 = new MetricDefinition("公",
                                                        ImmutableMap.<String, String>builder()
                                                            .put("此", "该")
                                                            .put("metric_name", "mem")
                                                            .build());
      assertEquals(alarm1.getMetricDefinition(), expected1);
    }

    public void shouldParseDimensionsWithSpaces() {
      AlarmExpression[] expr_list = {
          new AlarmExpression("test_metric{this_is_a_test=this is a test} > 10"),
          new AlarmExpression("test_metric{this is also a test = this_is_also_a_test} > 10")
      };
      MetricDefinition[] expected_list = {
          new MetricDefinition("test_metric", ImmutableMap.<String,String>builder()
              .put("this_is_a_test", "this is a test")
              .build()),
          new MetricDefinition("test_metric", ImmutableMap.<String,String>builder()
              .put("this is also a test", "this_is_also_a_test")
              .build())
      };

      for(int i = 0; i < expr_list.length; i++) {
        AlarmSubExpression expr = expr_list[i].getSubExpressions().get(0);
        assertEquals(expr.getMetricDefinition(), expected_list[i]);
      }
    }

    public void shouldFailWithRestrictedChars() {
      String[] expressions = {"%cmetric{foo=bar,metric_name=mem} > 4",
                              "me%ctric{foo=bar,metric_name=mem} > 4",
                              "metric%c{foo=bar,metric_name=mem} > 4",
                              "metric{%cfoo=bar,metric_name=mem} > 4",
                              "metric{f%coo=bar,metric_name=mem} > 4",
                              "metric{foo%c=bar,metric_name=mem} > 4",
                              "metric{foo=%cbar,metric_name=mem} > 4",
                              "metric{foo=b%car,metric_name=mem} > 4",
                              "metric{foo=bar%c,metric_name=mem} > 4"};
      for (int i = 0; i < expressions.length; i++) {
        for (int j = 0; j < restrictedChars.length(); j++) {
          String
              exprStr =
              String.format(expressions[i], restrictedChars.charAt(j));
          try {
            AlarmExpression expr = new AlarmExpression(exprStr);
            fail(String.format("Successfully parsed invalid expression: %s", exprStr));
          } catch (Exception ex) {
            //System.out.println(ex);
          }
        }
      }
    }

    public void shouldParseSpacings() {
      AlarmExpression expr = new AlarmExpression("avg ( metric { foo = bar , metric_name = mem } ) > 4"
                                                 + " or avg(metric{foo=bar,metric_name=mem})>4"
                                                 + " or avg( metric{ foo= bar, metric_name= mem} )> 4"
                                                 + " or avg (metric {foo =bar ,metric_name =mem }) >4");
      List<AlarmSubExpression> subExpressions = expr.getSubExpressions();
      for(int i = 1; i < subExpressions.size(); i++){
        assertEquals(subExpressions.get(0),subExpressions.get(i));
      }
    }

    public void shouldParseComplexExpression() {
      AlarmExpression expr = new AlarmExpression("max(-_.千幸福的笑脸{घोड़ा=馬,"
                                                 + "dn2=dv2,千幸福的笑脸घ=千幸福的笑脸घ}) gte 100 "
                                                 + "times 3 && "
                                                 + "(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) >9 and "
                                                 + "count(fizzle) lt 0 or count(baz) > 1)");
    }
}
