/*
 * 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.cassandra.tools;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.quicktheories.core.Gen;
import org.quicktheories.generators.SourceDSL;

import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.tools.ToolRunner.ToolResult;
import org.apache.cassandra.utils.Generators;

import static org.apache.cassandra.utils.FailingConsumer.orFail;
import static org.quicktheories.QuickTheory.qt;

public class JMXToolTest
{
    @Test
    public void jsonSerde()
    {
        serde(JMXTool.Dump.Format.json, JMXTool.Diff.Format.json);
    }

    @Test
    public void yamlSerde()
    {
        serde(JMXTool.Dump.Format.yaml, JMXTool.Diff.Format.yaml);
    }

    @Test
    public void cliHelp()
    {
        ToolResult result = jmxtool();
        result.assertOnCleanExit();

        Assertions.assertThat(result.getStdout())
                  .isEqualTo("Usage: jmxtool [-hV] [COMMAND]\n" +
                             "JMX tool for Apache Cassandra\n" +
                             "  -h, --help      Show this help message and exit.\n" +
                             "  -V, --version   Print version information and exit.\n" +
                             "Commands:\n" +
                             "  help  Display help information about the specified command.\n" +
                             "  dump  Dump the Apache Cassandra JMX objects and metadata.\n" +
                             "  diff  Diff two jmx dump files and report their differences\n");
    }

    @Test
    public void cliHelpDiff()
    {
        ToolResult result = jmxtool("help", "diff");
        result.assertOnCleanExit();

        Assertions.assertThat(result.getStdout())
                  .isEqualTo("Usage: jmxtool diff [-hV] [--ignore-missing-on-left]\n" +
                             "                    [--ignore-missing-on-right] [-f=format]\n" +
                             "                    [--exclude-attribute=exclude_attributes]...\n" +
                             "                    [--exclude-object=exclude_objects]...\n" +
                             "                    [--exclude-operation=exclud_operations]... [<files>...]\n" +
                             "Diff two jmx dump files and report their differences\n" +
                             "      [<files>...]      Files to diff\n" +
                             "      --exclude-attribute=exclude_attributes\n" +
                             "                        Ignores processing specific attributes. Each usage\n" +
                             "                          should take a single attribute, but can use this flag\n" +
                             "                          multiple times.\n" +
                             "      --exclude-object=exclude_objects\n" +
                             "                        Ignores processing specific objects. Each usage should\n" +
                             "                          take a single object, but can use this flag multiple\n" +
                             "                          times.\n" +
                             "      --exclude-operation=exclud_operations\n" +
                             "                        Ignores processing specific operations. Each usage\n" +
                             "                          should take a single operation, but can use this flag\n" +
                             "                          multiple times.\n" +
                             "  -f, --format=format   What format the files are in; only support json and\n" +
                             "                          yaml as format\n" +
                             "  -h, --help            Show this help message and exit.\n" +
                             "      --ignore-missing-on-left\n" +
                             "                        Ignore results missing on the left\n" +
                             "      --ignore-missing-on-right\n" +
                             "                        Ignore results missing on the right\n" +
                             "  -V, --version         Print version information and exit.\n");
    }

    @Test
    public void cliHelpDump()
    {
        ToolResult result = jmxtool("help", "dump");
        result.assertOnCleanExit();

        Assertions.assertThat(result.getStdout())
                  .isEqualTo("Usage: jmxtool dump [-hV] [-f=format] [-u=url]\n" +
                             "Dump the Apache Cassandra JMX objects and metadata.\n" +
                             "  -f, --format=format   What format to dump content as; supported values are\n" +
                             "                          console (default), json, and yaml\n" +
                             "  -h, --help            Show this help message and exit.\n" +
                             "  -u, --url=url         JMX url to target\n" +
                             "  -V, --version         Print version information and exit.\n");
    }

    private static ToolResult jmxtool(String... args)
    {
        List<String> cmd = new ArrayList<>(1 + args.length);
        cmd.add("tools/bin/jmxtool");
        cmd.addAll(Arrays.asList(args));
        return ToolRunner.invoke(cmd);
    }

    private void serde(JMXTool.Dump.Format serializer, JMXTool.Diff.Format deserializer)
    {
        DataOutputBuffer buffer = new DataOutputBuffer();
        qt().withShrinkCycles(0).forAll(gen()).checkAssert(orFail(map -> serde(serializer, deserializer, buffer, map)));
    }

    private void serde(JMXTool.Dump.Format serializer,
                       JMXTool.Diff.Format deserializer,
                       DataOutputBuffer buffer,
                       Map<String, JMXTool.Info> map) throws IOException
    {
        buffer.clear();
        serializer.dump(buffer, map);
        Map<String, JMXTool.Info> read = deserializer.load(new DataInputBuffer(buffer.buffer(), false));
        Assertions.assertThat(read)
                  .as("property deserialize(serialize(value)) == value failed")
                  .isEqualTo(map);
    }

    private static final Gen<JMXTool.Attribute> attributeGen = Generators.IDENTIFIER_GEN.zip(Generators.IDENTIFIER_GEN, Generators.IDENTIFIER_GEN, JMXTool.Attribute::new);
    private static final Gen<JMXTool.Parameter> parameterGen = Generators.IDENTIFIER_GEN.zip(Generators.IDENTIFIER_GEN, JMXTool.Parameter::new);
    private static final Gen<JMXTool.Operation> operationGen = Generators.IDENTIFIER_GEN.zip(SourceDSL.arrays().ofClass(parameterGen, JMXTool.Parameter.class).withLengthBetween(0, 10), Generators.IDENTIFIER_GEN, JMXTool.Operation::new);
    private static final Gen<JMXTool.Info> infoGen = SourceDSL.arrays().ofClass(attributeGen, JMXTool.Attribute.class).withLengthBetween(0, 10).zip(SourceDSL.arrays().ofClass(operationGen, JMXTool.Operation.class).withLengthBetween(0, 10), JMXTool.Info::new);

    private static Gen<Map<String, JMXTool.Info>> gen()
    {
        return SourceDSL.maps().of(Generators.IDENTIFIER_GEN, infoGen).ofSizeBetween(0, 10);
    }
}
