/*
 * Copyright (c) 2015, Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "Config.hpp"
#include "Test.hpp"
#include "Exception.hpp"
#include "TmpFile.hpp"

#include "ParameterFramework.hpp"
#include "ElementHandle.hpp"

#include <catch.hpp>

#include <libxml/parser.h>
#include <libxml/tree.h>

#include <string>
#include <list>

#include <stdlib.h>

using std::string;
using std::list;
using Bytes = std::vector<uint8_t>;

namespace parameterFramework
{

struct AllParamsPF : public ParameterFramework
{
    AllParamsPF() : ParameterFramework{getConfig()} { REQUIRE_NOTHROW(start()); }

    string getBasicParams()
    {
        string structure = R"(
            <BooleanParameter Name="bool" Description="bool"/>
            <BooleanParameter ArrayLength="2" Name="bool_array" Description="bool-array"/>

            <IntegerParameter Signed="false" Min="33" Max="123" Size="16" Name="integer"/>
            <IntegerParameter Signed="true" Min="-10" Max="10" Size="32" ArrayLength="4" Name="integer_array"/>

            <FixedPointParameter Size="32" Integral="3" Fractional="4" Name="fix_point"/>
            <FixedPointParameter Size="32" Integral="3" Fractional="4" ArrayLength="3" Name="fix_point_array"/>

            <EnumParameter Size="8" Name="enum">
                <ValuePair Literal="min"  Numerical="-128"/>
                <ValuePair Literal="five" Numerical="5"/>
                <ValuePair Literal="max"  Numerical="127"/>
            </EnumParameter>
            <EnumParameter Size="16" ArrayLength="4" Name="enum_array">
                <ValuePair Literal="eight"  Numerical="8"/>
                <ValuePair Literal="min"  Numerical="-32767"/>
            </EnumParameter>)";

        // String and bit parameter arrays are not supported
        structure += R"(
            <StringParameter MaxLength="63" Name="string"/>

            <BitParameterBlock Size="64" Name="bit_block">
                <BitParameter Pos="1"  Size="1"  Max="1"  Name="one"/>
                <BitParameter Pos="2"  Size="2"  Max="2"  Name="two"/>
                <BitParameter Pos="6"  Size="6"  Max="10" Name="six"/>
                <BitParameter Pos="16" Size="16" Max="99" Name="sixteen"/>
                <BitParameter Pos="32" Size="32" Max="4294967295" Name="thirty_two"/>
            </BitParameterBlock>
            )";
        return structure;
    }

    Config getConfig()
    {
        Config config;
        config.components = nodeDesc("ComponentType", "component_type", getBasicParams());
        config.instances =
            getBasicParams() + nodeDesc("ParameterBlock", "parameter_block", getBasicParams()) +
            nodeDesc("ParameterBlock", "parameter_block_array", getBasicParams(),
                     "ArrayLength='2'") +
            nodeDesc("Component", "component_scalar", "", "Type='component_type'") +
            nodeDesc("Component", "component_array", "", "Type='component_type' ArrayLength='2'");
        return config;
    }

    void checkStructure(const string &path, const string &expected)
    {
        CHECK_NOTHROW(checkXMLEq(ElementHandle{*this, path}.getStructureAsXML(), expected));
    }

    /** Use libxml2 to pretty format xml.
     * Equivalent of xmllint --format
     */
    static string canonicalizeXML(const string &xml)
    {
        // Parse xml
        // Might be better to specialize std::default_delete<xmlDoc>.
        std::unique_ptr<xmlDoc, void (*)(xmlDoc *)> doc{
            xmlReadMemory(xml.c_str(), (int)xml.length(), "structure.xml", nullptr,
                          XML_PARSE_NOBLANKS),
            xmlFreeDoc};
        if (doc == nullptr) {
            throw Exception{"Failed to parse document: " + xml};
        }

        // Dump it formated
        int size;

        // Need to use exception unsafe raw pointer as of libxml2 c api
        xmlChar *unsafeFormated;

        // TODO: Should use canonicalization (aka c14n).
        //       cf: http://xmlsoft.org/html/libxml-c14n.html
        //           https://en.wikipedia.org/wiki/Canonical_XML
        //       Additionally to what is listed on that page,
        //       attributes are also ordered deterministically.
        //       That would solve the workaround in the node function with pre/post attributes.
        //       Unfortunately c14n is not available in appveyor (Windows CI) libxml2 prebuild
        xmlDocDumpFormatMemoryEnc(doc.get(), &unsafeFormated, &size, "UTF-8", 1);
        std::unique_ptr<xmlChar, void (*)(void *)> formated{unsafeFormated, xmlFree};

        if (formated == nullptr) {
            throw Exception{"Could not dump xml: " + xml};
        }

        return string{(char *)formated.get()};
    }

    static void checkEq(const string &result, const string &expected)
    {
        CHECK(result == expected);

        // Pretty print the word differences with colors
        // It does not matter if it fails as the test would still fail
        // due to the above CHECK.
        if (result != expected) {
            utility::TmpFile resultFile(result);
            utility::TmpFile expectedFile(expected);
            string command = "git --no-pager diff --word-diff-regex='[^ <>]+'"
                             "                    --color --no-index --exit-code " +
                             resultFile.getPath() + ' ' + expectedFile.getPath();

            // `system` return -1 or 127 on failure, the command error code otherwise
            // `git diff` return 1 if the files are the different (thanks to --exit-code)
            auto status = system(command.c_str());
#ifdef WIFEXITED // Posix platform
            bool success = WIFEXITED(status) and WEXITSTATUS(status) == 1;
#else
            bool success = status == 1;
#endif
            if (not success) {
                WARN("Warning: Failed to pretty-print the difference between "
                     "actual and expected results with `git diff'");
            }
        }
    }

    static void checkXMLEq(const string &result, const string &expected)
    {
        checkEq(canonicalizeXML(result), canonicalizeXML(expected));
    }

    static string node(string tag, string name, string content, string attributes = "",
                       string postAttributes = "")
    {
        return "<" + tag + " " + attributes + " Name='" + name + "' " + postAttributes + ">" +
               content + "</" + tag + ">";
    }
    /** Node with a description.
     * @param[in] maybeDescription If nullptr, description will be generated from the name
     *                             Otherwise, the description.
     */
    static string nodeDesc(string tag, string name, string content, string attributes = "",
                           const char *maybeDescription = nullptr)
    {
        string description = "description_" + name;
        if (maybeDescription != nullptr) {
            description = maybeDescription;
        }
        return node(tag, name, content, attributes, "Description='" + description + "'");
    }

    static string rootNode(string name, string attributes, string content)
    {
        return '<' + name + ' ' + attributes + '>' + content + "</" + name + '>';
    }
};

SCENARIO_METHOD(AllParamsPF, "Export boolean", "[handler][structure][xml]")
{
    string expected = rootNode("BooleanParameter", "Name='bool' Description='bool'", "");
    checkStructure("/test/test/bool", expected);
}

SCENARIO_METHOD(AllParamsPF, "Export component", "[handler][structure][xml]")
{
    string expected = rootNode("ParameterBlock", "Name='component_scalar' "
                                                 "Description='description_component_scalar'",
                               getBasicParams());
    checkStructure("/test/test/component_scalar", expected);
}

SCENARIO_METHOD(AllParamsPF, "Export component array", "[handler][structure][xml]")
{
    string expected = rootNode(
        "ParameterBlock", "Name='component_array' Description='description_component_array'",
        nodeDesc("ParameterBlock", "0", getBasicParams(), "", "description_component_array") +
            nodeDesc("ParameterBlock", "1", getBasicParams(), "", "description_component_array"));
    checkStructure("/test/test/component_array", expected);
}

SCENARIO_METHOD(AllParamsPF, "Export all parameters", "[handler][structure][xml]")
{
    string paramExpected = getBasicParams() +
                           nodeDesc("ParameterBlock", "parameter_block", getBasicParams()) +
                           nodeDesc("ParameterBlock", "parameter_block_array",
                                    nodeDesc("ParameterBlock", "0", getBasicParams(), "",
                                             // description is inherited from array
                                             "description_parameter_block_array") +
                                        nodeDesc("ParameterBlock", "1", getBasicParams(), "",
                                                 "description_parameter_block_array")) +
                           // Components should be exported as parameterBlock
                           nodeDesc("ParameterBlock", "component_scalar", getBasicParams()) +
                           nodeDesc("ParameterBlock", "component_array",
                                    nodeDesc("ParameterBlock", "0", getBasicParams(), "",
                                             // description is inherited from array
                                             "description_component_array") +
                                        nodeDesc("ParameterBlock", "1", getBasicParams(), "",
                                                 "description_component_array"));

    WHEN ("Exporting subsystem") {
        string expected = rootNode("Subsystem", "Name='test'", paramExpected);
        checkStructure("/test/test", expected);
    }

    WHEN ("Exporting systemClass") {
        string expected = rootNode("SystemClass", "Name='test'",
                                   "<Subsystem Name='test'>" + paramExpected + "</Subsystem>");

        // Awkwardly, the root and its first child are the same element
        checkStructure("/test", expected);
        checkStructure("/", expected);
    }
}

struct SettingsTestPF : public AllParamsPF
{
    static string parameterBlockNode(string name, string settings)
    {
        return node("ParameterBlock", name, settings);
    };
    static string mkBasicSettings(string settings, string name)
    {
        return rootNode("ParameterBlock", "Name='" + name + "'", settings);
    }

    static string fullXMLSettings(const string &basicSettings)
    {
        string settings = basicSettings;
        settings +=
            parameterBlockNode("parameter_block", settings) +
            parameterBlockNode("parameter_block_array", parameterBlockNode("0", settings) +
                                                            parameterBlockNode("1", settings)) +
            parameterBlockNode("component_scalar", settings) +
            parameterBlockNode("component_array", parameterBlockNode("0", settings) +
                                                      parameterBlockNode("1", settings));

        return rootNode("SystemClass", "Name='test'", node("Subsystem", "test", settings, ""));
    }

    static string fullBytesSettings(const string &basicSettings)
    {
        string fullSettings;
        // We have the "basic params" repeated 7 times across the test
        // structure
        for (size_t i = 0; i < 7; ++i) {
            fullSettings += basicSettings;
        }
        return fullSettings;
    }

    /** Print Bytes as string separated hexadecimal number. */
    static string showBytes(const Bytes &bytes)
    {
        using namespace std;
        ostringstream ss;
        ss.exceptions(ostream::badbit | ostream::failbit);
        for (auto byte : bytes) {
            ss << hex << setw(2) << setfill('0') << int{byte} << ' ';
        }
        return ss.str();
    }

    static Bytes readBytes(const string &strBytes)
    {
        using namespace std;
        istringstream ss{strBytes};
        ss.exceptions(istream::badbit | istream::failbit);
        Bytes bytes(strBytes.size() / 3);

        for (auto &byte : bytes) {
            uint16_t notCharByte;
            ss >> hex >> setw(2) >> notCharByte;
            byte = static_cast<char>(notCharByte);
        }
        return bytes;
    }

    static void checkBytesEq(const Bytes &result, const string &expect)
    {
        checkEq(showBytes(result), expect);
    }
    static void checkBytesEq(const Bytes &result, const Bytes &expect)
    {
        checkEq(showBytes(result), showBytes(expect));
    }
};

static const char *defaultBasicSettingsXML = R"(
      <BooleanParameter Name="bool">0</BooleanParameter>
      <BooleanParameter Name="bool_array">0 0</BooleanParameter>
      <IntegerParameter Name="integer">33</IntegerParameter>
      <IntegerParameter Name="integer_array">-10 -10 -10 -10</IntegerParameter>
      <FixedPointParameter Name="fix_point">0.0000</FixedPointParameter>
      <FixedPointParameter Name="fix_point_array">0.0000 0.0000 0.0000</FixedPointParameter>
      <EnumParameter Name="enum">min</EnumParameter>
      <EnumParameter Name="enum_array">eight eight eight eight</EnumParameter>
      <StringParameter Name="string"></StringParameter>
      <BitParameterBlock Name="bit_block">
        <BitParameter Name="one">0</BitParameter>
        <BitParameter Name="two">0</BitParameter>
        <BitParameter Name="six">0</BitParameter>
        <BitParameter Name="sixteen">0</BitParameter>
        <BitParameter Name="thirty_two">0</BitParameter>
      </BitParameterBlock>
)";

static const char *testBasicSettingsXML = R"(
      <BooleanParameter Name="bool">1</BooleanParameter>
      <BooleanParameter Name="bool_array">0 1</BooleanParameter>
      <IntegerParameter Name="integer">100</IntegerParameter>
      <IntegerParameter Name="integer_array">-10 0 8 10</IntegerParameter>
      <FixedPointParameter Name="fix_point">2.2500</FixedPointParameter>
      <FixedPointParameter Name="fix_point_array">7.1250 0.6875 -1.0000</FixedPointParameter>
      <EnumParameter Name="enum">five</EnumParameter>
      <EnumParameter Name="enum_array">eight min eight min</EnumParameter>
      <StringParameter Name="string">A string of 32 character.@@@@@@@</StringParameter>
      <BitParameterBlock Name="bit_block">
        <BitParameter Name="one">1</BitParameter>
        <BitParameter Name="two">2</BitParameter>
        <BitParameter Name="six">10</BitParameter>
        <BitParameter Name="sixteen">72</BitParameter>
        <BitParameter Name="thirty_two">4294967295</BitParameter>
      </BitParameterBlock>
)";
static const char *testRawHexBasicSettingsXML = R"(
      <BooleanParameter Name="bool">0x1</BooleanParameter>
      <BooleanParameter Name="bool_array">0x0 0x1</BooleanParameter>
      <IntegerParameter Name="integer">0x0064</IntegerParameter>
      <IntegerParameter Name="integer_array">0xFFFFFFF6 0x00000000 0x00000008 0x0000000A</IntegerParameter>
      <FixedPointParameter ValueSpace="Raw" Name="fix_point">0x24000000</FixedPointParameter>
      <FixedPointParameter ValueSpace="Raw" Name="fix_point_array">0x72000000 0x0B000000 0xF0000000</FixedPointParameter>
      <EnumParameter Name="enum">five</EnumParameter>
      <EnumParameter Name="enum_array">eight min eight min</EnumParameter>
      <StringParameter Name="string">A string of 32 character.@@@@@@@</StringParameter>
      <BitParameterBlock Name="bit_block">
        <BitParameter Name="one">0x1</BitParameter>
        <BitParameter Name="two">0x2</BitParameter>
        <BitParameter Name="six">0xA</BitParameter>
        <BitParameter Name="sixteen">0x48</BitParameter>
        <BitParameter Name="thirty_two">0xFFFFFFFF</BitParameter>
      </BitParameterBlock>
)";

SCENARIO_METHOD(SettingsTestPF, "Export and import XML settings", "[handler][settings][xml]")
{
    WHEN ("Exporting root XML") {
        auto getAsXML = [this](string path) { return ElementHandle(*this, path).getAsXML(); };
        CHECK(getAsXML("/") == getAsXML("/test"));
        checkXMLEq(getAsXML("/"), fullXMLSettings(defaultBasicSettingsXML));
    }

    ElementHandle basicParams(*this, "/test/test/parameter_block");
    WHEN ("Exporting basic parameter XML") {
        checkXMLEq(basicParams.getAsXML(),
                   mkBasicSettings(defaultBasicSettingsXML, "parameter_block"));
    }
    string testSettings = mkBasicSettings(testBasicSettingsXML, "parameter_block");
    string rawTestSettings = mkBasicSettings(testRawHexBasicSettingsXML, "parameter_block");

    auto checkExport = [&] {
        THEN ("Exported settings should be the ones imported") {
            checkXMLEq(basicParams.getAsXML(), testSettings);
        }
        THEN ("Exported raw settings should be the ones imported") {
            setRawValueSpace(true);
            setHexOutputFormat(true);
            checkXMLEq(basicParams.getAsXML(), rawTestSettings);
        }
    };
    WHEN ("Importing basic parameter XML") {
        CHECK_NOTHROW(basicParams.setAsXML(testSettings));
        checkExport();
    }
    WHEN ("Importing raw basic parameter XML") {
        CHECK_NOTHROW(basicParams.setAsXML(rawTestSettings));
        checkExport();
    }
}

static const string defaultBasicSettingsBytes =
    "00 00 00 21 00 f6 ff ff ff f6 ff ff ff f6 ff ff ff f6 ff ff ff 00 00 00 00 "
    "00 00 00 00 00 00 00 00 00 00 00 00 80 08 00 08 00 08 00 08 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ";

static const string testBasicSettingsBytes =
    "01 00 01 64 00 f6 ff ff ff 00 00 00 00 08 00 00 00 0a 00 00 00 00 00 00 24 "
    "00 00 00 72 00 00 00 0b 00 00 00 f0 05 08 00 01 80 08 00 01 80 41 20 73 74 72 "
    "69 6e 67 20 6f 66 20 33 32 20 63 68 61 72 61 63 74 65 72 2e 40 40 40 40 40 40 "
    "40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
    "00 00 00 00 00 00 00 8a 02 48 00 ff ff ff ff ";

SCENARIO_METHOD(SettingsTestPF, "Bijection of binary show and read", "[identity][test]")
{
    CHECK(showBytes(readBytes(testBasicSettingsBytes)) == testBasicSettingsBytes);
}

SCENARIO_METHOD(SettingsTestPF, "Export and import root binary settings",
                "[handler][settings][bytes]")
{
    ElementHandle root(*this, "/");
    ElementHandle systemClass(*this, "/");

    THEN ("Root and system class should export the same binary") {
        checkBytesEq(root.getAsBytes(), systemClass.getAsBytes());
    }
    WHEN ("Exporting root binary") {
        checkBytesEq(root.getAsBytes(), fullBytesSettings(defaultBasicSettingsBytes));
    }
    WHEN ("Importing root binary") {
        string rootTestSettings = fullBytesSettings(testBasicSettingsBytes);
        REQUIRE_NOTHROW(root.setAsBytes(readBytes(rootTestSettings)));
        THEN ("Exported settings should be the ones imported") {
            checkBytesEq(root.getAsBytes(), rootTestSettings);
        }
    }
}

SCENARIO_METHOD(SettingsTestPF, "Export and import basic binary settings",
                "[handler][settings][bytes]")
{
    ElementHandle basicParams(*this, "/test/test/parameter_block");
    WHEN ("Exporting basic parameter binary") {
        checkBytesEq(basicParams.getAsBytes(), defaultBasicSettingsBytes);
    }
    WHEN ("Importing basic parameter binary") {
        REQUIRE_NOTHROW(basicParams.setAsBytes(readBytes(testBasicSettingsBytes)));
        THEN ("Exported settings should be the ones imported") {
            checkBytesEq(basicParams.getAsBytes(), testBasicSettingsBytes);
        }
    }
}

SCENARIO_METHOD(SettingsTestPF, "Export and import array binary settings",
                "[handler][settings][bytes]")
{
    ElementHandle array(*this, "/test/test/parameter_block_array");
    ElementHandle elem0(*this, "/test/test/parameter_block_array/0");
    WHEN ("Importing one array element") {
        REQUIRE_NOTHROW(elem0.setAsBytes(readBytes(testBasicSettingsBytes)));
        THEN ("The other element should not have changed") {
            checkBytesEq(array.getAsBytes(), testBasicSettingsBytes + defaultBasicSettingsBytes);
        }
    }
}

SCENARIO_METHOD(SettingsTestPF, "Import root in one format, export in an other",
                "[handler][settings][bytes][xml]")
{
    ElementHandle root(*this, "/test");
    string rootBytesSettings = fullBytesSettings(testBasicSettingsBytes);
    string rootXMLSettings = fullXMLSettings(testBasicSettingsXML);

    WHEN ("Importing root binary") {
        REQUIRE_NOTHROW(root.setAsBytes(readBytes(rootBytesSettings)));
        THEN ("Exported XML settings should be the ones imported") {
            checkXMLEq(root.getAsXML(), rootXMLSettings);
        }
    }

    WHEN ("Importing root XML") {
        REQUIRE_NOTHROW(root.setAsXML(rootXMLSettings));
        THEN ("Exported bytes settings should be the ones imported") {
            checkBytesEq(root.getAsBytes(), rootBytesSettings);
        }
    }
}

SCENARIO_METHOD(SettingsTestPF, "Import basic params in one format, export in an other",
                "[handler][settings][bytes][xml]")
{
    ElementHandle basicParams(*this, "/test/test/parameter_block_array/0");
    string basicXMLSettings = mkBasicSettings(testBasicSettingsXML, "0");

    WHEN ("Importing basic parameters binary") {
        REQUIRE_NOTHROW(basicParams.setAsBytes(readBytes(testBasicSettingsBytes)));
        THEN ("Exported XML settings should be the ones imported") {
            checkXMLEq(basicParams.getAsXML(), basicXMLSettings);
        }
    }

    WHEN ("Importing basic parameters XML") {
        REQUIRE_NOTHROW(basicParams.setAsXML(basicXMLSettings));
        THEN ("Exported bytes settings should be the ones imported") {
            checkBytesEq(basicParams.getAsBytes(), testBasicSettingsBytes);
        }
    }
}

struct MappingPF : public ParameterFramework
{
    MappingPF() : ParameterFramework{getConfig()} { REQUIRE_NOTHROW(start()); }

    struct TestVector
    {
        string path;
        string humanReadable;
        list<string> valid;
        list<string> invalid;
    };

    list<TestVector> testVectors = {
        // clang-format off
        {"/test/test",
            {"rootK:rootV"},
            {"root"},
            {"param", "type", "instance", "derived"}},
        {"/test/test/param",
            {"rootK:rootV, paramK:paramV"},
            {"root", "param"},
            {"type", "derived", "instance"}},
        {"/test/test/component",
            {"rootK:rootV, typeK:typeV, derivedK:derivedV, instanceK:instanceV"},
            {"root", "type", "derived", "instance"},
            {"param"}}
        // clang-format on
    };

    Config getConfig()
    {
        Config config;
        config.subsystemMapping = "rootK:rootV";
        config.components = "<ComponentType   Name='componentType' Mapping='typeK:typeV'        />"
                            "<ComponentType   Extends='componentType' Name='derivedComponentType' "
                            "Mapping='derivedK:derivedV' />";
        config.instances = "<BooleanParameter Name='param'         Mapping='paramK:paramV'      />"
                           "<Component        Name='component'     Mapping='instanceK:instanceV'  "
                           "           Type='derivedComponentType'                              />";
        return config;
    }
};

SCENARIO_METHOD(MappingPF, "showMapping command", "[mapping]")
{
    auto cmdHandler = std::unique_ptr<CommandHandlerInterface>(createCommandHandler());

    for (auto &testVector : testVectors) {
        string output;
        CHECK(cmdHandler->process("showMapping", {testVector.path}, output));
        CHECK(output == testVector.humanReadable);
    }
}

SCENARIO_METHOD(MappingPF, "Mapping handle access", "[handler][mapping]")
{
    GIVEN ("A PF with mappings") {
        for (auto &test : testVectors) {
            GIVEN ("An element handle of " + test.path) {
                ElementHandle handle(*this, test.path);

                for (auto &valid : test.valid) {
                    THEN ("The following mapping should exist: " + valid) {
                        CHECK(handle.getMappingData(valid + "K") == valid + "V");
                    }
                }

                for (auto &invalid : test.invalid) {
                    THEN ("The following mapping should not exist: " + invalid) {
                        CHECK_THROWS_AS(handle.getMappingData(invalid + "K"), Exception);
                    }
                }
            }
        }
    }
}

SCENARIO_METHOD(SettingsTestPF, "Handle Get/Set as various kinds", "[handler][dynamic]")
{
    ElementHandle intScalar(*this, "/test/test/parameter_block/integer");
    WHEN ("Setting a scalar integer") {
        WHEN ("As an array") {
            THEN ("It should fail") {
                CHECK_THROWS(intScalar.setAsIntegerArray({0, 0}));
            }
        }
        WHEN ("As a scalalar") {
            THEN ("It should succeed") {
                uint32_t expected = 111;
                CHECK_NOTHROW(intScalar.setAsInteger(expected));
                AND_THEN ("Getting it back should give the same value") {
                    uint32_t back = 42;
                    CHECK_NOTHROW(intScalar.getAsInteger(back));
                    CHECK(back == expected);
                }
            }
        }
    }

    ElementHandle intArray(*this, "/test/test/parameter_block/integer_array");
    WHEN ("Setting a array integer") {
        WHEN ("As a scalar") {
            THEN ("It should fail") {
                CHECK_THROWS(intArray.setAsSignedInteger(0));
            }
        }
        WHEN ("As a integer") {
            THEN ("It should succeed") {
                const std::vector<int32_t> expected = {-9, 8, -7, 6};
                CHECK_NOTHROW(intArray.setAsSignedIntegerArray(expected));
                AND_THEN ("Getting it back should give the same value") {
                    std::vector<int32_t> back = {-42, 42, 43, -43};
                    CHECK_NOTHROW(intArray.getAsSignedIntegerArray(back));
                    CHECK(back == expected);
                }
            }
        }
    }
}
} // namespace parameterFramework