// C/C++ Headers
#include <stdlib.h>
#include <cmath>
#include <string>
#include <algorithm>

// Utility library headers
#include <boost/algorithm/string.hpp>

// ROS Headers
#include <ros/ros.h>
#include <std_msgs/Float32.h>
#include <sensor_msgs/JointState.h>
#include <std_srvs/Empty.h>
#include <std_srvs/Trigger.h>
#include <std_srvs/SetBool.h>

using std::string;

ros::Publisher pub_setpoint, pub_goal;

static double speed = 600 / 200 * 20 * 2;
static double x, y;
static double x_col, y_col;
static double x_goal, y_goal;
static double x_bound, y_bound;
static bool led_on, motors_on, endeff_down;
static double v_scale;
static bool reported = true;

// Relative movement subscriber callback
void move(const sensor_msgs::JointState &msg) {
  x_goal += msg.position[0];
  y_goal += msg.position[1];
  reported = false;
}

// Absolute movement subscriber callback
void move_to(const sensor_msgs::JointState &msg) {
  x_goal = msg.position[0];
  y_goal = msg.position[1];
  reported = false;
}

// Scale maximum velocity of device callback
void velocity_scale(const std_msgs::Float32 &msg) {
  float scale = std::min(std::max(msg.data, 0.0f), 1.0f);
  v_scale = scale;
}

// Halt service handler
bool halt(std_srvs::Empty::Request &req, std_srvs::Empty::Response &resp) {
  x_goal = x;
  y_goal = y;

  return true;
}

// End effector tap action service handler
bool tap(std_srvs::Empty::Request &req, std_srvs::Empty::Response &resp) {
  endeff_down = true;
  ros::Duration(0.5).sleep();
  endeff_down = false;

  return true;
}

// Homing sequence service handler
bool home(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &resp) {
  if (motors_on) {
    ros::Rate r(60);

    while (x > x_bound) {
      x -= speed * v_scale * 0.2;
      r.sleep();
    }
    x = x_bound;
    while (x < 0) {
      x += speed * v_scale * 0.2;
      r.sleep();
    }
    x = 0;

    while (x > -80) {
      x -= speed * v_scale * 0.2;
      r.sleep();
    }
    x = -80;

    while (y < y_bound) {
      y += speed * v_scale * 0.2;
      r.sleep();
    }
    y = y_bound;
    while (y > 0) {
      y -= speed * v_scale * 0.2;
      r.sleep();
    }
    y = 0;

    x_goal = 0;
    y_goal = 0;
    reported = false;

    resp.success = true;
    resp.message = "Successfully homed.";
  } else {
    resp.success = false;
    resp.message = "Failed to home.";
  }

  return true;
}

// End effector state set service handler
bool set_endeff(std_srvs::SetBool::Request &req, std_srvs::SetBool::Response &resp) {
  if (req.data) {
    endeff_down = true;
    resp.message = "End Effector Pressed";
  } else {
    endeff_down = false;
    resp.message = "End Effector Released";
  }

  resp.success = true;

  return true;
}

// LED state set service handler
bool set_led(std_srvs::SetBool::Request &req, std_srvs::SetBool::Response &resp) {
  if (req.data) {
    led_on = true;
    resp.message = "LED Lit";
  } else {
    led_on = false;
    resp.message = "LED Off";
  }

  resp.success = true;

  return true;
}

// Motor state set service handler
bool set_motors(std_srvs::SetBool::Request &req, std_srvs::SetBool::Response &resp) {
  if (req.data) {
    motors_on = true;
    resp.message = "Motors Enabled";
  } else {
    motors_on = false;
    resp.message = "Motors Disabled";
  }

  resp.success = true;

  return true;
}

// Position publisher timer event
void publishPosition(const ros::TimerEvent &e) {
  unsigned static int s = 0;

    sensor_msgs::JointState msg;

    // Assign message data
    msg.header.seq = s;
    msg.header.stamp = ros::Time::now();
    msg.header.frame_id = "rvc";

    msg.name.resize(2);
    msg.name[0] = "x";
    msg.name[1] = "y";

    msg.position.resize(2);
    msg.position[0] = x;
    msg.position[1] = y;

    msg.effort.resize(2);
    msg.effort[0] = x_col;
    msg.effort[1] = y_col;

    // Publish and increment sequence counter
    pub_setpoint.publish(msg);
    // Also publish to goal_js if message indicates end of motion
    if (x_goal == x && y_goal == y && !reported) {
      pub_goal.publish(msg);
      reported = true;
    }
    s++;

}

void simulateMotors(const ros::TimerEvent &e) {
  double y_vel, x_vel;
  if (y_goal - y == 0 && x_goal - x == 0) {
    x_vel = 0;
    y_vel = 0;
  } else if (fabs(y_goal - y) > fabs(x_goal - x)) {
    y_vel = speed * v_scale * (y_goal - y) / fabs(y_goal - y);
    x_vel = (x_goal - x) / fabs(y_goal - y) * speed * v_scale;
  } else {
    x_vel = speed * v_scale * (x_goal - x) / fabs(x_goal - x);
    y_vel = (y_goal - y) / fabs(x_goal - x) * speed * v_scale;
  }

  x += x_vel * 0.02;
  y += y_vel * 0.02;

  if (fabs(x_goal - x) <= 2.4)
    x = x_goal;
  if (fabs(y_goal - y) <= 2.4)
    y = y_goal;

}

int main(int argc, char **argv) {
  // Initialize ROS
  ros::init(argc, argv, "rvc_ros_simulator");
  ros::NodeHandle nh;

  x_bound = -580;
  y_bound = 300;
  v_scale = 1.0;

  // Open serial port to device
  string port = "/dev/ttyUSB0";

  // Check that port is open, exit if not
  if (true)
    ROS_INFO("Opened serial port %s", port.c_str());
  else {
    ROS_FATAL("Failed to open serial port %s", port.c_str());
    ros::shutdown();
    return 0;
  }

  // Wait for device to ready
  ros::Duration(4).sleep();

  ROS_INFO("Beginning homing sequence");
  // Enable motors and perform auto-home
  motors_on = true;
  std_srvs::Trigger::Request rq;
  std_srvs::Trigger::Response rp;
  home(rq, rp);

  // Wait for homing to complete
  if (rp.success) {
    ROS_INFO("Homing successful.");
  } else {
    ROS_INFO("Homing failed.");
  }

  // Define subscribers, publishers, and services
  ros::Timer position_timer = nh.createTimer(ros::Duration(0.2), &publishPosition);
  ros::Timer motor_timer = nh.createTimer(ros::Duration(0.02), &simulateMotors);

  pub_setpoint = nh.advertise<sensor_msgs::JointState>("setpoint_js", 1000);
  pub_goal = nh.advertise<sensor_msgs::JointState>("goal_js", 1000);

  ros::Subscriber move_sub = nh.subscribe("move_jr", 1000, &move);
  ros::Subscriber move_to_sub = nh.subscribe("move_jp", 1000, &move_to);
  ros::Subscriber velocity_scale_sub = nh.subscribe("velocity_scale", 1000, &velocity_scale);

  ros::ServiceServer halt_serv = nh.advertiseService("halt", &halt);
  ros::ServiceServer tap_serv = nh.advertiseService("tap", &tap);
  ros::ServiceServer home_serv = nh.advertiseService("home", &home);
  ros::ServiceServer set_endeff_serv = nh.advertiseService("set_endeff", &set_endeff);
  ros::ServiceServer set_led_serv = nh.advertiseService("set_led", &set_led);
  ros::ServiceServer set_motors_serv = nh.advertiseService("set_motors", &set_motors);

  // Release flow control to ROS
  ros::spin();

  return 0;
}


//
// Copyright Jason Rice 2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef NBDL_EXAMPLE_TILES_GAME_HPP
#define NBDL_EXAMPLE_TILES_GAME_HPP

#include <nbdl.hpp>

#include <array>
#include <boost/hana.hpp>
#include <utility>

namespace tiles
{
  namespace hana = boost::hana;
  namespace message = nbdl::message;

  namespace detail
  {
    // Returns the index to swap with.
    template <typename GameState, typename Index>
    Index get_tile_swap_index(GameState const& g, Index i)
    {
      // Move the tile at `i` to the adjacent empty space if there is one.

      if (g.board[i] == 0)                  return i;               // current

      if (i % g.width != 0
        && g.board[i - 1] == 0)             return i - 1;           // left

      if (i % g.width != g.width - 1
        && g.board[i + 1] == 0)             return i + 1;           // right

      if (i >= g.width
        && g.board[i - g.width] == 0)       return i - g.width;     // above

      if (i <= g.height * g.width - g.width
        && g.board[i + g.width] == 0)       return i + g.width;     // below

      // The tile is not adjacent to the empty space.
      return i;
    }

    template <typename GameState, typename GameMove>
    bool apply_game_move(GameState&& game_, GameMove&& gm)
    {
      // Silently disregard out of range junk.
      auto i = std::forward<GameMove>(gm).index;
      if (i < 0 || i >= game_.width * game_.height)
      {
        return false;
      }
      auto i2 = detail::get_tile_swap_index(game_, i);
      if (i2 != i)
      {
        std::swap(game_.board[i], game_.board[i2]);
        return true;
      }
      return false;
    }
  }

  struct game
  {
    int width;
    int height;
    std::array<unsigned char, 64> board;
  };

  struct game_move
  {
    // The index of the tile to move to the
    // adjacent empty space.
    unsigned char index;
  };

  struct game_store { };

  template <typename Path>
  struct game_store_impl
  {
    using hana_tag = game_store;
    // just a singleton game right now
    game game_;

    // initialize the one game that we have :/
    game_store_impl()
      : game_({ 3, 3,
        std::array<unsigned char, 64>{{
          3, 4, 6,
          8, 0, 1,
          2, 5, 7
        }}
      })
    { }

    // returns true if the game state actually changes
    template <typename GamePath, typename GameMove>
    bool apply_move(GamePath, GameMove gm)
    {
      return detail::apply_game_move(game_, gm);
    }
  };

  // need a better way to get path types
  using game_path = hana::tuple<int>;
  using game_move_path = hana::tuple<int, int>;

  template <typename StateConsumer, typename Index>
  void create_game_move(StateConsumer& c, Index i)
  {
    nbdl::apply_message(
      c.context
    , message::make_upstream_create(
        message::make_create_path<int>(hana::make_tuple(0))
      , message::no_uid
      , game_move{i}
      )
    );
  }

  template <typename ProducerTag, typename ConsumerTag>
  struct context
  {
    static constexpr auto make_def()
    {
      using namespace nbdl_def;
      namespace hana = boost::hana;
      return
        Context(
          Producer(
            Name("producer"),
            Type<ProducerTag>,
            AccessPoint(
              Entity<game>,
              PathKey<int>,
              Store<game_store>,
              ListenPaths(Path<int, int>),
              AccessPoint(
                Entity<game_move>,
                PathKey<int>,
                Actions(Create())
              )
            )
          ),
          Consumer(
            Name("consumer"),
            Type<ConsumerTag>
          )
        );
    }
  };

  template <typename ProducerTag, typename ConsumerTag, typename ...Args>
  auto make_context(Args&& ... args)
  {
    return nbdl::make_unique_context<tiles::context<ProducerTag, ConsumerTag>>
      (std::forward<Args>(args)...);
  }
}

namespace nbdl
{
  NBDL_ENTITY(
    tiles::game
    , width
    , height
    , board
  );

  NBDL_ENTITY(
    tiles::game_move
    , index
  );

  template <>
  struct make_store_impl<tiles::game_store>
  {
    template <typename PathType, typename ValueType>
    static constexpr auto apply(PathType, ValueType)
    {
      return tiles::game_store_impl<typename PathType::type>();
    }
  };

  template <>
  struct get_impl<tiles::game_store>
  {
    template <typename Store, typename Path>
    static constexpr auto apply(Store const& s, Path const&)
    {
      return s.game_;
    }
  };

  template <>
  struct apply_foreign_message_impl<tiles::game_store>
  {
    template <typename Store, typename Message, typename Fn>
    static constexpr auto apply(Store&& s, Message&& m, Fn&& fn)
    {
      if constexpr(decltype(hana::and_(
        hana::bool_c<message::is_downstream<Message>>
      , hana::bool_c<message::is_create<Message>>
      , message::get_path_type(m) == hana::type_c<tiles::game_move_path>
      ))::value)
      {
        auto parent_path = hana::drop_back(message::get_path(m), hana::int_c<1>);
        if (std::forward<Store>(s).apply_move(
          parent_path,
          message::get_payload(std::forward<Message>(m)))
        )
        {
          std::forward<Fn>(fn)(std::move(parent_path));
        }
      }
    }
  };
}

#endif


#include <boost/make_unique.hpp>
#include <cassert>
#include <catch2/catch.hpp>
#include <cmath>
#include <map>
#include <string>
#include <vector>
#include "ear/bs2051.hpp"
#include "ear/ear.hpp"
#include "ear/metadata.hpp"

using namespace ear;

using Gains = std::vector<float>;

ObjectsTypeMetadata otmWithPos(Position p) {
  ObjectsTypeMetadata otm;
  otm.position = p;
  return otm;
}

using GainsMap = std::map<std::string, double>;
struct GainsMaps {
  GainsMap direct;
  GainsMap diffuse;
};

GainsMap mapGainsToChannelNames(std::vector<std::string> channelNames,
                                Gains gains) {
  assert(channelNames.size() == gains.size());
  GainsMap ret;
  std::transform(channelNames.begin(), channelNames.end(), gains.begin(),
                 std::inserter(ret, ret.end()),
                 std::make_pair<std::string const&, double const&>);
  return ret;
}

class GainCalculatorObjectsTester {
 public:
  GainCalculatorObjectsTester(Layout layout)
      : _layout(layout),
        _gainCalc(boost::make_unique<GainCalculatorObjects>(layout)){};

  GainsMaps run(const ObjectsTypeMetadata& metadata) {
    Gains directGains(_layout.channels().size(), 0.0);
    Gains diffuseGains(_layout.channels().size(), 0.0);
    _gainCalc->calculate(metadata, directGains, diffuseGains);
    auto channelNames = _layout.channelNames();
    auto directGainsMap = mapGainsToChannelNames(channelNames, directGains);
    auto diffuseGainsMap = mapGainsToChannelNames(channelNames, diffuseGains);
    double epsilon = 1e-6;
    for (auto it = directGainsMap.begin(); it != directGainsMap.end();) {
      if (std::abs(it->second) < epsilon) {
        it = directGainsMap.erase(it);
      } else {
        ++it;
      }
    }
    for (auto it = diffuseGainsMap.begin(); it != diffuseGainsMap.end();) {
      if (std::abs(it->second) < epsilon) {
        it = diffuseGainsMap.erase(it);
      } else {
        ++it;
      }
    }
    return GainsMaps{directGainsMap, diffuseGainsMap};
  };

 private:
  Layout _layout;
  std::unique_ptr<GainCalculatorObjects> _gainCalc;
};

TEST_CASE("gain_calculator") {
  auto layout = getLayout("4+7+0").withoutLfe();
  GainCalculatorObjectsTester gainCalc(layout);

  SECTION("basic_centre") {
    auto gainMap = gainCalc.run(otmWithPos(PolarPosition{0.0, 0.0, 1.0}));
    REQUIRE(gainMap.direct.size() == 1);
    REQUIRE(gainMap.direct.at("M+000") == Approx(1.0));
    REQUIRE(gainMap.diffuse.size() == 0);
  }
  SECTION("basic_left") {
    auto gainMap = gainCalc.run(otmWithPos(PolarPosition{30.0, 0.0, 1.0}));
    REQUIRE(gainMap.direct.size() == 1);
    REQUIRE(gainMap.direct.at("M+030") == Approx(1.0));
    REQUIRE(gainMap.diffuse.size() == 0);
  }
  SECTION("basic_left_up") {
    auto gainMap = gainCalc.run(otmWithPos(PolarPosition{45.0, 30.0, 1.0}));
    REQUIRE(gainMap.direct.size() == 1);
    REQUIRE(gainMap.direct.at("U+045") == Approx(1.0));
    REQUIRE(gainMap.diffuse.size() == 0);
  }
}

TEST_CASE("diffuse") {
  auto layout = getLayout("4+7+0").withoutLfe();
  GainCalculatorObjectsTester gainCalc(layout);

  SECTION("half") {
    auto otm = otmWithPos(PolarPosition{0.0, 0.0, 1.0});
    otm.diffuse = 0.5;
    auto gainMap = gainCalc.run(otm);
    REQUIRE(gainMap.direct.size() == 1);
    REQUIRE(gainMap.direct.at("M+000") == Approx(std::sqrt(0.5)));
    REQUIRE(gainMap.diffuse.size() == 1);
    REQUIRE(gainMap.diffuse.at("M+000") == Approx(std::sqrt(0.5)));
  }

  SECTION("full") {
    auto otm = otmWithPos(PolarPosition{0.0, 0.0, 1.0});
    otm.diffuse = 1.0;
    auto gainMap = gainCalc.run(otm);
    REQUIRE(gainMap.direct.size() == 0);
    REQUIRE(gainMap.diffuse.size() == 1);
    REQUIRE(gainMap.diffuse.at("M+000") == Approx(1.0));
  }
}

TEST_CASE("gain_value") {
  auto layout = getLayout("4+7+0").withoutLfe();
  GainCalculatorObjectsTester gainCalc(layout);

  auto otm = otmWithPos(PolarPosition{0.0, 0.0, 1.0});
  otm.gain = 0.5;
  auto gainMap = gainCalc.run(otm);
  REQUIRE(gainMap.direct.size() == 1);
  REQUIRE(gainMap.direct.at("M+000") == Approx(0.5));
  REQUIRE(gainMap.diffuse.size() == 0);
}

TEST_CASE("not implemented") {
  auto layout = getLayout("4+7+0").withoutLfe();
  GainCalculatorObjectsTester gainCalc(layout);

  auto otm = otmWithPos(PolarPosition{0.0, 0.0, 1.0});

  SECTION("cartesian") {
    otm.cartesian = true;
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
  SECTION("Cartesian position") {
    otm.position = CartesianPosition(0.0, 0.0, 0.0);
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
  SECTION("objectDivergence polar") {
    otm.objectDivergence = PolarObjectDivergence(0.5);
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
  SECTION("objectDivergence Cartesian") {
    otm.objectDivergence = CartesianObjectDivergence(0.5);
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
  SECTION("channelLock") {
    otm.channelLock.flag = true;
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
  SECTION("zone") {
    otm.zoneExclusion.zones.push_back(
        PolarExclusionZone{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ""});
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
  SECTION("screenRef") {
    otm.screenRef = true;
    REQUIRE_THROWS_AS(gainCalc.run(otm), not_implemented);
  }
}


#include <stan/math/rev.hpp>
#include <boost/mp11.hpp>
#include <gtest/gtest.h>
#include <test/unit/math/rev/functor/test_fixture_ode.hpp>
#include <test/unit/math/rev/functor/test_fixture_linear_dae.hpp>
#include <test/unit/math/rev/functor/dae_test_functors.hpp>

/**
 *
 * Use same solver functor type for both w & w/o tolerance control
 */
template <typename solve_type, typename... Ts>
using ode_test_tuple = std::tuple<solve_type, solve_type, Ts...>;

using degenerated_dae_test_types = boost::mp11::mp_product<
    ode_test_tuple, ::testing::Types<dae_functor>,
    ::testing::Types<double>,  // t
    ::testing::Types<double, stan::math::var_value<double>>,
    ::testing::Types<double, stan::math::var_value<double>>,
    ::testing::Types<double, stan::math::var_value<double>>>;
TYPED_TEST_SUITE_P(degenerated_dae_test);
TYPED_TEST_P(degenerated_dae_test, y0_sens) { this->test_ode_sens_y0(); }
TYPED_TEST_P(degenerated_dae_test, theta_sens) { this->test_ode_sens_theta(); }

REGISTER_TYPED_TEST_SUITE_P(degenerated_dae_test, y0_sens, theta_sens);
INSTANTIATE_TYPED_TEST_SUITE_P(StanDAE, degenerated_dae_test,
                               degenerated_dae_test_types);


// Copyright (c) 2013, German Neuroinformatics Node (G-Node)
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted under the terms of the BSD License. See
// LICENSE file in the root of the Project.

#ifndef NIX_CHECKS_H
#define NIX_CHECKS_H

#include <nix/util/util.hpp>
#include <nix/valid/helper.hpp>

#include <nix/base/IDimensions.hpp>

#include <nix/types.hpp>

#include <boost/optional.hpp>
#include <boost/any.hpp>

namespace nix {
namespace valid {

    /**
     * @brief Check if later given not greater than initally defined value.
     *
     * One Check struct that checks whether the given value is not greater
     * than the initially given other value, both of which have to be
     * convertible to double.
     */
    struct notGreater {
        const double value;

        template<typename T>
        notGreater(T value) : value(static_cast<double>(value)) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return !(static_cast<double>(val) > value);
        }
    };

    /**
     * @brief Check if later given greater than initally defined value.
     *
     * One Check struct that checks whether the given value is greater than
     * the initially given other value, both of which have to be
     * convertible to double.
     */
    struct isGreater {
        const double value;

        template<typename T>
        isGreater(T value) : value(static_cast<double>(value)) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return static_cast<double>(val) > value;
        }
    };

    /**
     * @brief Check if later given not smaller than initally defined value.
     *
     * One Check struct that checks whether the given value is not smaller
     * than the initially given other value, both of which have to be
     * convertible to double.
     */
    struct notSmaller {
        const double value;

        template<typename T>
        notSmaller(T value) : value(static_cast<double>(value)) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return !(static_cast<double>(val) < value);
        }
    };

    /**
     * @brief Check if later given smaller than initally defined value.
     *
     * One Check struct that checks whether the given value is smaller than
     * the initially given other value, both of which have to be
     * convertible to double.
     */
    struct isSmaller {
        const double value;

        template<typename T>
        isSmaller(T value) : value(static_cast<double>(value)) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return static_cast<double>(val) < value;
        }
    };

    /**
     * @brief Check for un-equality of initally defined and later given value.
     *
     * One Check struct that checks whether the given value is not equal
     * to the initially given other value.
     */
    template<typename T>
    struct notEqual {
        const T value;

        notEqual(T value) : value(value) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return value != val;
        }
    };

    /**
     * @brief Check for equality of initally defined and later given value.
     *
     * One Check struct that checks whether the given value is equal to
     * the initially given other value.
     */
    template<typename T>
    struct isEqual {
        const T value;

        isEqual(T value) : value(value) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return value == val;
        }
    };
    // needed because: for bizarre reasons bool converts to int when compared to boost::optional
    template<>
    struct isEqual<bool> {
        const bool value;

        isEqual(bool value) : value(value) {}

        template<typename T2>
        bool operator()(const T2 &val) const {
            return value ? !!val : !val;
        }
    };

    /**
     * @brief Check if given value casts to boolean true
     *
     * One Check struct that checks whether the given value casts to true
     * or to false.
     * T can be: boost::optional, boost::none, nix-entity
     * and any basic type.
     */
    struct notFalse {
        // WARNING: enum will convert via int, which means 0 = false !
        template<typename T>
        bool operator()(const T &val) const {
            return !!val;
        }
    };

    /**
     * @brief Check if given value casts to boolean false
     *
     * One Check struct that checks whether the given value casts to false
     * or to true.
     * T can be: boost::optional, boost::none, nix-entity
     * and any basic type.
     */
    struct isFalse {
        // WARNING: enum will convert via int, which means 0 = false !
        template<typename T>
        bool operator()(const T &val) const {
            return !val;
        }
    };

    /**
     * @brief Check if given class/struct returns "empty() == false"
     *
     * One Check struct that checks whether the given value is not empty
     * or is empty.
     * T can be: any STL container.
     */
    struct notEmpty {
        template<typename T>
        bool operator()(const T &val) const {
            return !(val.empty());
        }
    };

    /**
     * @brief Check if given class/struct returns "empty() == true"
     *
     * One Check struct that checks whether the given value is empty or
     * not.
     * T can be: any STL container.
     */
    struct isEmpty {
        template<typename T>
        bool operator()(const T &val) const {
            return val.empty();
        }
    };

    /**
     * @brief Check if given class represents valid SI unit string(s)
     *
     * Base struct to be inherited by the {@link isValidUnit}, {@link
     * isAtomicUnit}, {@link isCompoundUnit}. Not viable on its own!
     */
    struct isUnit {
        typedef std::function<bool(std::string)> TPRED;

        virtual bool operator()(const std::string &u) const = 0;

        bool operator()(const boost::optional<std::string> &u) const {
            // note: relying on short-curcuiting here
            return u && (*this)(*u);
        }

        bool operator()(const std::vector<std::string> &u, TPRED obj) const {
            // if test succeeds find_if_not will not find anything & return it == end
            return std::find_if_not(u.begin(), u.end(), obj) == u.end();
        }

        virtual ~isUnit() { }
    };

    /**
     * @brief Check if given class represents valid SI unit string(s)
     *
     * One Check struct that checks whether the given string(s) represent(s)
     * a valid atomic or compound SI unit.
     * Parameter can be of type boost optional (containing nothing or
     * string) or of type string or a vector of strings.
     */
    struct isValidUnit : public isUnit {
        bool operator()(const std::string &u) const {
            return (util::isSIUnit(u) || util::isCompoundSIUnit(u));
        }
        bool operator()(const boost::optional<std::string> &u) const {
            return isUnit::operator()(u);
        }
        bool operator()(const std::vector<std::string> &u) const {
            return isUnit::operator()(u, *this);
        }
    };

    /**
     * @brief Check if given class represents valid atomic SI unit string(s)
     *
     * One Check struct that checks whether the given string(s) represent(s)
     * a valid atomic SI unit.
     * Parameter can be of type boost optional (containing nothing or
     * string) or of type string or a vector of strings.
     */
    struct isAtomicUnit : public isUnit {
        bool operator()(const std::string &u) const {
            return util::isSIUnit(u);
        }
        bool operator()(const boost::optional<std::string> &u) const {
            return isUnit::operator()(u);
        }
        bool operator()(const std::vector<std::string> &u) const {
            return isUnit::operator()(u, *this);
        }
    };

    /**
     * @brief Check if given class represents valid compound SI unit string(s)
     *
     * One Check struct that checks whether the given string(s) represent(s)
     * a valid compound SI unit.
     * Parameter can be of type boost optional (containing nothing or
     * string) or of type string or a vector of strings.
     */
    struct isCompoundUnit : public isUnit {
        bool operator()(const std::string &u) const {
            return util::isCompoundSIUnit(u);
        }
        bool operator()(const boost::optional<std::string> &u) const {
            return isUnit::operator()(u);
        }
        bool operator()(const std::vector<std::string> &u) const {
            return isUnit::operator()(u, *this);
        }
    };

    /**
     * @brief Check if given value can be regarded as being set
     *
     * One Check struct that checks whether the given value can be
     * considered set, by applying {@link notFalse} and {@link notEmpty}
     * checks. Value thus is set if: STL cotnainer not empty OR
     * bool is true OR boost optional is set OR number is not 0.
     * Parameter can be of above types or even boost none_t.
     * NOTE: use this if you don't know wheter a type has and "empty"
     * method.
     */
    struct isSet {
        template<typename T>
        bool operator()(const T &val) const {
            typedef typename std::conditional<hasEmpty<T>::value, notEmpty, notFalse>::type subCheck;
            return subCheck(val);
        }
    };

    /**
     * @brief Check if given container is sorted using std::is_sorted
     *
     * One Check struct that checks whether the given container is sorted
     * according to std::is_sorted. Thus supports types that are
     * supported by std::is_sorted.
     */
    struct isSorted {
        template<typename T>
        bool operator()(const T &container) const {
            return std::is_sorted(container.begin(), container.end());
        }
    };

    /**
     * @brief Check if given DataArray has given dimensionality
     *
     * One Check struct that checks whether the given DataArray entity
     * has a dimensionality of the given uint value by getting its'
     * NDSize class via the "dataExtent" method and checking its' size
     * via "size" the method.
     */
    struct NIXAPI dimEquals {
        size_t value;

        dimEquals(const size_t &value) : value(value) {}

        bool operator()(const DataArray &array) const;
    };

    /**
     * @brief Check if given DataArrays' dimensions all have units where given units vector has
     *
     * One Check struct that checks whether the given referenced
     * DataArrays' dimensions all have units defined where the tag has.
     * (where the tag has means at the same index in the tag's units
     * vector as the dimension index in the referenced DataArray)
     * Therefore it takes all non-SetDimension dimensions of all given
     * references and checks whether the dimension has a unit set if the
     * tag has. If a dimension is a SetDimension the test counts as
     * passed. Thus in the end the test counts as passed if all non-
     * SetDimension dimensions have units set where the tag has and
     * have no units set where the tag has not. It counts as failed
     * immediately if number of dimensions differs from number of units
     * in given unit vector.
     */
    struct NIXAPI tagRefsHaveUnits {
        std::vector<std::string> units;

        tagRefsHaveUnits(const std::vector<std::string> &units) : units(units) {}

        bool operator()(const std::vector<DataArray> &references) const;
    };

    /**
     * @brief Check if given units match given referenced DataArrays' units
     *
     * One Check struct that checks whether the given units (vector of
     * strings) match the given referenced DataArrays' (vector of
     * DataArray references) units. Therefore it takes all non-
     * SetDimension dimensions of all given references and checks
     * whether the dimension has a unit convertible to the unit with the
     * same index in the given units vector. If a dimension is a
     * SetDimension the test counts as passed. Thus in the end the test
     * counts as passed if all non-SetDimension dimensions have units
     * set that are convertible where the units vector has a unit set
     * and all dims have no unit set where the units vector has not.
     * The test counts as failed immediately if the number of dimensions
     * in a DataArray differs the number of units in the units vector.
     */
    struct NIXAPI tagUnitsMatchRefsUnits {
        std::vector<std::string> units;

        tagUnitsMatchRefsUnits(const std::vector<std::string> &units) : units(units) {}

        bool operator()(const std::vector<DataArray> &references) const;
    };

    /**
     * @brief Check if given number of positions and extents matches
     *
     * One Check struct that checks whether the given number of
     * positions matches the given number of extents. It is irrelevant
     * which gets passed at construction time and which via operator().
     */
    struct NIXAPI extentsMatchPositions {
        boost::any extents;

        extentsMatchPositions(const DataArray &extents) : extents(extents) {}

        extentsMatchPositions(const std::vector<double> &extents) : extents(extents) {}

        bool operator()(const DataArray &positions) const;
        bool operator()(const std::vector<double> &positions) const;
    };

    /**
     * @brief Check if number of extents (along 2nd dim) match number of references' data dims
     *
     * One Check struct that checks whether the given number of
     * extents (if DataArray: size along 2nd dimensions of extents
     * DataArray; if vector: size of vector) matches the data's
     * dimensionality in each of the given referenced DataArrays.
     */
    struct NIXAPI extentsMatchRefs {
        std::vector<DataArray> refs;

        extentsMatchRefs(const std::vector<DataArray> &refs) : refs(refs) {}

        bool operator()(const DataArray &extents) const;
        bool operator()(const std::vector<double> &extents) const;
    };

    /**
     * @brief Check if number of positions (along 2nd dim) match number of references' data dims
     *
     * One Check struct that checks whether the given number of
     * positions (if DataArray: size along 2nd dimensions of positions
     * DataArray; if vector: size of vector) matches the data's
     * dimensionality in each of the given referenced DataArrays.
     * Note: this is just an alias for extentsMatchRefs wich does the
     * same thing.
     */
    struct NIXAPI positionsMatchRefs {
        std::vector<DataArray> refs;

        positionsMatchRefs(const std::vector<DataArray> &refs) : refs(refs) {}

        bool operator()(const DataArray &positions) const;
        bool operator()(const std::vector<double> &positions) const;
    };

    /**
     * @brief Check if range dimension specifics ticks match data
     *
     * One Check struct that checks whether the dimensions of type
     * "Range" in the given dimensions vector have ticks that match
     * the given DataArray's data: number of ticks == number of entries
     * along the corresponding dimension in the data.
     */
    struct NIXAPI dimTicksMatchData {
        const DataArray &data;

        dimTicksMatchData(const DataArray &data) : data(data) {}

        bool operator()(const std::vector<Dimension> &dims) const;
    };

    /**
     * @brief Check if set dimension specifics labels match data
     *
     * One Check struct that checks whether the dimensions of type
     * "Set" in the given dimensions vector have labels that match
     * the given DataArray's data: number of labels == number of entries
     * along the corresponding dimension in the data.
     */
    struct NIXAPI dimLabelsMatchData {
        const DataArray &data;

        dimLabelsMatchData(const DataArray &data) : data(data) {}

        bool operator()(const std::vector<Dimension> &dims) const;
    };

} // namespace valid
} // namespace nix

#endif // NIX_CHECKS_H


/*******************************************************************************
 *
 * MIT License
 *
 * Copyright (c) 2017 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *******************************************************************************/
#include <cmath>
#include <miopen/kernel_cache.hpp>
#include <miopen/util.hpp>
#include <miopen/logger.hpp>
#include <miopen/datatype.hpp>

#include <boost/range/adaptors.hpp>

#define WG_SIZE 256
#define MAX_ACTIVE_THREADS (64 * 4 * 64)
#define MAX_LOCAL_MEM 65536

namespace miopen {

float Im2d2ColGPU(Handle& handle,
                  ConstData_t im,
                  const int im_offset,
                  const int c,
                  const int in_h,
                  const int in_w,
                  const int wei_h,
                  const int wei_w,
                  const int out_h,
                  const int out_w,
                  const int pad_h,
                  const int pad_w,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_h,
                  const int dilation_w,
                  Data_t col,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenIm2d2Col.cl";
    std::string kernel_name  = "Im2d2Col";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(c) +
        "i" + std::to_string(in_h) +
        "_" + std::to_string(in_w) +
        "w" + std::to_string(wei_h) +
        "_" + std::to_string(wei_w) +
        "p" + std::to_string(pad_h) +
        "_" + std::to_string(pad_w) +
        "s" + std::to_string(stride_h) +
        "_" + std::to_string(stride_w) +
        "d" + std::to_string(dilation_h) +
        "_" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenIm2d2Col", network_config);

    int data_size_bound = c * in_h * in_w;

    int data_size_bound_pack = type == miopenInt8x4 ? data_size_bound * 4 : data_size_bound;
    int im_offset_pack       = type == miopenInt8x4 ? im_offset / 4 : im_offset;

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(data_size_bound_pack,
               im,
               im_offset_pack,
               in_h,
               in_w,
               wei_h,
               wei_w,
               out_h,
               out_w,
               pad_h,
               pad_w,
               stride_h,
               stride_w,
               dilation_h,
               dilation_w,
               col);
    }
    else
    {
        const int c_pack = type == miopenInt8x4 ? c / 4 : c;

        std::string params;
        int num_ch_per_wg;
        if((out_h <= 8 && out_w <= 8) && (stride_h == 1 && stride_w == 1) && (c_pack % 4 == 0))
            num_ch_per_wg = 4;
        else
            num_ch_per_wg = 1;

        int tile_sz_x  = 32;
        int tile_sz_y  = 8;
        int num_blks_x = std::ceil(static_cast<float>(out_w) / static_cast<float>(tile_sz_x));
        int num_blks =
            num_blks_x *
            static_cast<int>(std::ceil(static_cast<float>(out_h) / static_cast<float>(tile_sz_y)));
        int local_mem_sz;
        if(num_ch_per_wg == 1)
        {
            local_mem_sz = ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                           ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
        }
        else
        {
            auto uprdTileX = static_cast<int>(
                std::ceil(static_cast<float>(tile_sz_x) / static_cast<float>(num_ch_per_wg)) - 1);
            auto uprdTileY = static_cast<int>(
                std::ceil(static_cast<float>(tile_sz_y) / static_cast<float>(num_ch_per_wg)) - 1);
            auto memXsize = (num_ch_per_wg * uprdTileX * stride_w + (wei_w - 1) * dilation_w + 1) *
                            ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
            auto memYsize = num_ch_per_wg *
                            ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                            (uprdTileY * stride_h + (wei_h - 1) * dilation_h + 1);
            local_mem_sz = static_cast<int>(std::max(memXsize, memYsize));
        }

        // adjust mapping for large kernel
        int type_size    = 4; // Need to adjust for fp16, int8
        int extreme_case = num_ch_per_wg * ((wei_w - 1) * dilation_w + 1) *
                           ((wei_h - 1) * dilation_h + 1) * type_size;
        if(extreme_case > MAX_LOCAL_MEM)
        {
            params += " -DEXTREME_LARGE";
            params += " -DNUM_CH_TOTAL=" + std::to_string(c_pack);
        }
        else
        {
            while(local_mem_sz * type_size > MAX_LOCAL_MEM)
            {
                tile_sz_x  = tile_sz_x == 1 ? 1 : (tile_sz_y == 1 ? (tile_sz_x / 2) : tile_sz_x);
                tile_sz_y  = tile_sz_y == 1 ? 1 : (tile_sz_y / 2);
                num_blks_x = std::ceil(static_cast<float>(out_w) / static_cast<float>(tile_sz_x));
                num_blks   = num_blks_x * static_cast<int>(std::ceil(static_cast<float>(out_h) /
                                                                   static_cast<float>(tile_sz_y)));
                if(num_ch_per_wg == 1)
                {
                    local_mem_sz = ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                                   ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
                }
                else
                {
                    auto uprdTileX = static_cast<int>(std::ceil(static_cast<float>(tile_sz_x) /
                                                                static_cast<float>(num_ch_per_wg)) -
                                                      1);
                    auto uprdTileY = static_cast<int>(std::ceil(static_cast<float>(tile_sz_y) /
                                                                static_cast<float>(num_ch_per_wg)) -
                                                      1);
                    auto memXsize =
                        (num_ch_per_wg * uprdTileX * stride_w + (wei_w - 1) * dilation_w + 1) *
                        ((tile_sz_y - 1) * stride_h + (wei_h - 1) * dilation_h + 1);
                    auto memYsize = num_ch_per_wg *
                                    ((tile_sz_x - 1) * stride_w + (wei_w - 1) * dilation_w + 1) *
                                    (uprdTileY * stride_h + (wei_h - 1) * dilation_h + 1);
                    local_mem_sz = static_cast<int>(std::max(memXsize, memYsize));
                }
            }
        }

        params += " -DNUM_CH_PER_WG=" + std::to_string(num_ch_per_wg);
        params += " -DNUM_IM_BLKS_X=" + std::to_string(num_blks_x);
        params += " -DNUM_IM_BLKS=" + std::to_string(num_blks);
        params += " -DLOCAL_MEM_SIZE=" + std::to_string(local_mem_sz);
        params += " -DSTRIDE_GT_1=" + std::to_string(static_cast<int>(stride_h * stride_w > 1));
        params += " -DTILE_SZ_X=" + std::to_string(tile_sz_x);
        params += " -DTILE_SZ_Y=" + std::to_string(tile_sz_y);
        params += " -DUSE_IM_OFF_GUARD=1";

        params += GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = 256 * std::max(1, (c_pack / num_ch_per_wg)) * num_blks;
        const std::vector<size_t> vgd{global_threads, 1, 1};
        handle.AddKernel(
            "miopenIm2Col", network_config, program_name, kernel_name, vld, vgd, params)(
            data_size_bound_pack,
            im,
            im_offset_pack,
            in_h,
            in_w,
            wei_h,
            wei_w,
            out_h,
            out_w,
            pad_h,
            pad_w,
            stride_h,
            stride_w,
            dilation_h,
            dilation_w,
            col);
    }

    return handle.GetKernelTime();
}

float Im3d2ColGPU(Handle& handle,
                  ConstData_t im,
                  const int im_offset,
                  const int im_c,
                  const int im_d,
                  const int im_h,
                  const int im_w,
                  const int wei_d,
                  const int wei_h,
                  const int wei_w,
                  const int out_d,
                  const int out_h,
                  const int out_w,
                  const int pad_d,
                  const int pad_h,
                  const int pad_w,
                  const int stride_d,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_d,
                  const int dilation_h,
                  const int dilation_w,
                  Data_t col,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenIm3d2Col.cl";
    std::string kernel_name  = "Im3d2Col";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(im_c) +
        "i" + std::to_string(im_d) +
        "_" + std::to_string(im_h) +
        "_" + std::to_string(im_w) +
        "w" + std::to_string(wei_d) +
        "_" + std::to_string(wei_h) +
        "_" + std::to_string(wei_w) +
        "p" + std::to_string(pad_d) +
        "_" + std::to_string(pad_h) +
        "_" + std::to_string(pad_w) +
        "s" + std::to_string(stride_d) +
        "_" + std::to_string(stride_h) +
        "_" + std::to_string(stride_w) +
        "d" + std::to_string(dilation_d) +
        "_" + std::to_string(dilation_h) +
        "_" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenIm3d2Col", network_config);

    // int8x4 vectorize-c format
    int im_offset_pack = type == miopenInt8x4 ? im_offset / 4 : im_offset;
    int im_c_pack      = type == miopenInt8x4 ? im_c / 4 : im_c;

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(im,
               im_offset_pack,
               im_c_pack,
               im_d,
               im_h,
               im_w,
               wei_d,
               wei_h,
               wei_w,
               out_d,
               out_h,
               out_w,
               pad_d,
               pad_h,
               pad_w,
               stride_d,
               stride_h,
               stride_w,
               dilation_d,
               dilation_h,
               dilation_w,
               col);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = std::min(
            256 * static_cast<std::size_t>(out_d * out_h * out_w * im_c * wei_d * wei_h * wei_w) /
                8,
            static_cast<std::size_t>(256) * 1024);
        const std::vector<size_t> vgd{global_threads, 1, 1};

        handle.AddKernel(
            "miopenIm3d2Col", network_config, program_name, kernel_name, vld, vgd, params)(
            im,
            im_offset_pack,
            im_c_pack,
            im_d,
            im_h,
            im_w,
            wei_d,
            wei_h,
            wei_w,
            out_d,
            out_h,
            out_w,
            pad_d,
            pad_h,
            pad_w,
            stride_d,
            stride_h,
            stride_w,
            dilation_d,
            dilation_h,
            dilation_w,
            col);
    }

    return handle.GetKernelTime();
}

float Col2Im2dGPU(Handle& handle,
                  ConstData_t col,
                  const int out_h,
                  const int out_w,
                  const int wei_h,
                  const int wei_w,
                  const int pad_h,
                  const int pad_w,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_h,
                  const int dilation_w,
                  const int in_c,
                  const int in_h,
                  const int in_w,
                  Data_t im,
                  int im_offset,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenCol2Im2d.cl";
    std::string kernel_name  = "Col2Im2d";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(in_c) +
        "in_h" + std::to_string(in_h) +
        "in_w" + std::to_string(in_w) +
        "y" + std::to_string(wei_h) +
        "x" + std::to_string(wei_w) +
        "p" + std::to_string(pad_h) +
        "q" + std::to_string(pad_w) +
        "u" + std::to_string(stride_h) +
        "v" + std::to_string(stride_w) +
        "l" + std::to_string(dilation_h) +
        "j" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenCol2Im2d", network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(col,
               out_h,
               out_w,
               wei_h,
               wei_w,
               pad_h,
               pad_w,
               stride_h,
               stride_w,
               dilation_h,
               dilation_w,
               in_h,
               in_w,
               im,
               im_offset);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = in_c * in_h * in_w;
        const std::vector<size_t> vgd{global_threads, 1, 1};

        handle.AddKernel(
            "miopenCol2Im2d", network_config, program_name, kernel_name, vld, vgd, params)(
            col,
            out_h,
            out_w,
            wei_h,
            wei_w,
            pad_h,
            pad_w,
            stride_h,
            stride_w,
            dilation_h,
            dilation_w,
            in_h,
            in_w,
            im,
            im_offset);
    }
    return handle.GetKernelTime();
}

float Col2Im3dGPU(Handle& handle,
                  ConstData_t col,
                  const int out_d,
                  const int out_h,
                  const int out_w,
                  const int wei_d,
                  const int wei_h,
                  const int wei_w,
                  const int pad_d,
                  const int pad_h,
                  const int pad_w,
                  const int stride_d,
                  const int stride_h,
                  const int stride_w,
                  const int dilation_d,
                  const int dilation_h,
                  const int dilation_w,
                  const int in_c,
                  const int in_d,
                  const int in_h,
                  const int in_w,
                  Data_t im,
                  int im_offset,
                  miopenDataType_t type)
{
    std::string program_name = "MIOpenCol2Im3d.cl";
    std::string kernel_name  = "Col2Im3d";

    // clang-format off
    std::string network_config =
        "c" + std::to_string(in_c) +
        "i" + std::to_string(in_d) +
        "_" + std::to_string(in_h) +
        "_" + std::to_string(in_w) +
        "w" + std::to_string(wei_d) +
        "_" + std::to_string(wei_h) +
        "_" + std::to_string(wei_w) +
        "p" + std::to_string(pad_d) +
        "_" + std::to_string(pad_h) +
        "_" + std::to_string(pad_w) +
        "s" + std::to_string(stride_d) +
        "_" + std::to_string(stride_h) +
        "_" + std::to_string(stride_w) +
        "d" + std::to_string(dilation_d) +
        "_" + std::to_string(dilation_h) +
        "_" + std::to_string(dilation_w) +
        "t" + std::to_string(type);
    // clang-format on

    auto&& kernels = handle.GetKernels("miopenCol2Im3d", network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(col,
               out_d,
               out_h,
               out_w,
               wei_d,
               wei_h,
               wei_w,
               pad_d,
               pad_h,
               pad_w,
               stride_d,
               stride_h,
               stride_w,
               dilation_d,
               dilation_h,
               dilation_w,
               in_d,
               in_h,
               in_w,
               im,
               im_offset);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        const std::vector<size_t> vld{256, 1, 1};
        size_t global_threads = in_c * in_d * in_h * in_w;
        const std::vector<size_t> vgd{global_threads, 1, 1};

        handle.AddKernel(
            "miopenCol2Im3d", network_config, program_name, kernel_name, vld, vgd, params)(
            col,
            out_d,
            out_h,
            out_w,
            wei_d,
            wei_h,
            wei_w,
            pad_d,
            pad_h,
            pad_w,
            stride_d,
            stride_h,
            stride_w,
            dilation_d,
            dilation_h,
            dilation_w,
            in_d,
            in_h,
            in_w,
            im,
            im_offset);
    }
    return handle.GetKernelTime();
}

float Im2ColGPU(
    Handle& handle,
    std::size_t spatial_dim,
    ConstData_t im,
    std::size_t im_offset,
    std::size_t in_c,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& in_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& wei_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& out_spatial,
    const std::vector<int>& pad_spatial,
    const std::vector<int>& stride_spatial,
    const std::vector<int>& dilation_spatial,
    Data_t col,
    miopenDataType_t type)
{
    switch(spatial_dim)
    {
    case 2:
    {
        return Im2d2ColGPU(handle,
                           im,
                           im_offset,
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           wei_spatial[0],
                           wei_spatial[1],
                           out_spatial[0],
                           out_spatial[1],
                           pad_spatial[0],
                           pad_spatial[1],
                           stride_spatial[0],
                           stride_spatial[1],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           col,
                           type);
    }
    case 3:
    {
        return Im3d2ColGPU(handle,
                           im,
                           im_offset,
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           in_spatial[2],
                           wei_spatial[0],
                           wei_spatial[1],
                           wei_spatial[2],
                           out_spatial[0],
                           out_spatial[1],
                           out_spatial[2],
                           pad_spatial[0],
                           pad_spatial[1],
                           pad_spatial[2],
                           stride_spatial[0],
                           stride_spatial[1],
                           stride_spatial[2],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           dilation_spatial[2],
                           col,
                           type);
    }
    default: { MIOPEN_THROW("unsupported convolution dimension");
    }
    }
}

float Col2ImGPU(
    Handle& handle,
    std::size_t spatial_dim,
    ConstData_t col,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& out_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& wei_spatial,
    const std::vector<int>& pad_spatial,
    const std::vector<int>& stride_spatial,
    const std::vector<int>& dilation_spatial,
    std::size_t in_c,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& in_spatial,
    Data_t im,
    std::size_t im_offset,
    miopenDataType_t type)
{
    switch(spatial_dim)
    {
    case 2:
    {
        return Col2Im2dGPU(handle,
                           col,
                           out_spatial[0],
                           out_spatial[1],
                           wei_spatial[0],
                           wei_spatial[1],
                           pad_spatial[0],
                           pad_spatial[1],
                           stride_spatial[0],
                           stride_spatial[1],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           im,
                           im_offset,
                           type);
    }
    case 3:
    {
        return Col2Im3dGPU(handle,
                           col,
                           out_spatial[0],
                           out_spatial[1],
                           out_spatial[2],
                           wei_spatial[0],
                           wei_spatial[1],
                           wei_spatial[2],
                           pad_spatial[0],
                           pad_spatial[1],
                           pad_spatial[2],
                           stride_spatial[0],
                           stride_spatial[1],
                           stride_spatial[2],
                           dilation_spatial[0],
                           dilation_spatial[1],
                           dilation_spatial[2],
                           in_c,
                           in_spatial[0],
                           in_spatial[1],
                           in_spatial[2],
                           im,
                           im_offset,
                           type);
    }
    default: { MIOPEN_THROW("unsupported convolution dimension");
    }
    }

    MIOPEN_THROW("unsupported convolution dimension");
}

float transpose_NCHW2CNHW(Handle& handle,
                          int n,
                          int c,
                          int h_in,
                          int w_in,
                          int h_out,
                          int w_out,
                          ConstData_t in,
                          Data_t out,
                          int in_offset,
                          int out_offset,
                          int h_stride,
                          int w_stride,
                          miopenDataType_t type)
{

    std::string program_name = "MIOpenUtilKernels4.cl";

    std::string network_config = "n" + std::to_string(n) + "c" + std::to_string(c) + "h" +
                                 std::to_string(h_in) + "w" + std::to_string(w_in) + "inoff" +
                                 std::to_string(in_offset) + "otoff" + std::to_string(out_offset) +
                                 "u" + std::to_string(h_stride) + "v" + std::to_string(w_stride) +
                                 "t" + std::to_string(type);

    std::string kernel_name = "transpose_NCHW2CNHW";

    if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        kernel_name += "_opt";

    auto&& kernels = handle.GetKernels(kernel_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        if(type == miopenInt8x4)
        {
            c /= 4;
            in_offset /= 4;
            out_offset /= 4;
        }

        if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=1 -DNC_TRANS_CNHW_OPT=0 -DNC_TRANS_NCHW=0 -DNC_TRANS_CNHW=0";

            int RD_BLCK      = ((h_in * w_in) % 4 == 0) ? 4 : ((h_in * w_in) % 2 == 0) ? 2 : 1;
            int HW_RD        = (h_in * w_in) / RD_BLCK;
            size_t MAP_RD    = HW_RD * c;
            size_t lcl_size0 = WG_SIZE; //((MAP_RD + 63)/64 < 4) ? ((MAP_RD + 63)/64)*64 : 256;

            std::string READ_TYPE = (RD_BLCK == 1) ? "float" : "float" + std::to_string(RD_BLCK);

            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);
            params += " -DH=" + std::to_string(h_in);
            params += " -DW=" + std::to_string(w_in);
            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DRD_BLCK=" + std::to_string(RD_BLCK);
            params += " -DHW_RD=" + std::to_string(HW_RD);
            params += " -DMAP_RD=" + std::to_string(MAP_RD);
            params += " -DREAD_TYPE=" + READ_TYPE;

            const std::vector<size_t> vld{lcl_size0, 1, 1};
            std::vector<size_t> vgd{MAP_RD, 1, 1};

            if(MAP_RD < MAX_ACTIVE_THREADS)
            {
                vgd = {MAP_RD, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
        else
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=0 -DNC_TRANS_CNHW_OPT=0 -DNC_TRANS_NCHW=1 -DNC_TRANS_CNHW=0";

            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DHW_IN=" + std::to_string(h_in * w_in);
            params += " -DHW_OUT=" + std::to_string(h_out * w_out);
            params += " -DW_IN=" + std::to_string(w_in);
            params += " -DW_OUT=" + std::to_string(w_out);
            params += " -DH_STRIDE=" + std::to_string(h_stride);
            params += " -DW_STRIDE=" + std::to_string(w_stride);
            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);

            size_t ld0 = WG_SIZE;
            size_t gd0 = c * h_out * w_out;
            const std::vector<size_t> vld{ld0, 1, 1};
            std::vector<size_t> vgd{gd0, 1, 1};

            if(gd0 < MAX_ACTIVE_THREADS)
            {
                vgd = {gd0, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
    }

    return handle.GetKernelTime();
}

float transpose_CNHW2NCHW(Handle& handle,
                          int n,
                          int c,
                          int h_out,
                          int w_out,
                          int h_in,
                          int w_in,
                          ConstData_t in,
                          Data_t out,
                          int in_offset,
                          int out_offset,
                          int h_stride,
                          int w_stride,
                          miopenDataType_t type)
{

    std::string program_name = "MIOpenUtilKernels4.cl";

    std::string network_config = "n" + std::to_string(n) + "c" + std::to_string(c) + "h" +
                                 std::to_string(h_in) + "w" + std::to_string(w_in) + "inoff" +
                                 std::to_string(in_offset) + "otoff" + std::to_string(out_offset) +
                                 "h_stride" + std::to_string(h_stride) + "w_stride" +
                                 std::to_string(w_stride) + "t" + std::to_string(type);

    std::string kernel_name = "transpose_CNHW2NCHW";

    if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        kernel_name += "_opt";

    auto&& kernels = handle.GetKernels(kernel_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);

        if(type == miopenInt8x4)
        {
            c /= 4;
            in_offset /= 4;
            out_offset /= 4;
        }

        if(h_stride == 1 && w_stride == 1 && type == miopenFloat)
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=0 -DNC_TRANS_CNHW_OPT=1 -DNC_TRANS_NCHW=0 -DNC_TRANS_CNHW=0";

            int RD_BLCK      = ((h_out * w_out) % 4 == 0) ? 4 : ((h_out * w_out) % 2 == 0) ? 2 : 1;
            int HW_RD        = (h_out * w_out) / RD_BLCK;
            size_t MAP_RD    = HW_RD * c;
            size_t lcl_size0 = WG_SIZE; //((MAP_RD + 63)/64 < 4) ? ((MAP_RD + 63)/64)*64 : 256;

            std::string READ_TYPE = (RD_BLCK == 1) ? "float" : "float" + std::to_string(RD_BLCK);

            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);
            params += " -DH=" + std::to_string(h_out);
            params += " -DW=" + std::to_string(w_out);
            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DRD_BLCK=" + std::to_string(RD_BLCK);
            params += " -DHW_RD=" + std::to_string(HW_RD);
            params += " -DMAP_RD=" + std::to_string(MAP_RD);
            params += " -DREAD_TYPE=" + READ_TYPE;

            const std::vector<size_t> vld{lcl_size0, 1, 1};
            std::vector<size_t> vgd{MAP_RD, 1, 1};

            if(MAP_RD < MAX_ACTIVE_THREADS)
            {
                vgd = {MAP_RD, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
        else
        {
            params +=
                " -DNC_TRANS_NCHW_OPT=0 -DNC_TRANS_CNHW_OPT=0 -DNC_TRANS_NCHW=0 -DNC_TRANS_CNHW=1";

            params += " -DN=" + std::to_string(n);
            params += " -DC=" + std::to_string(c);
            params += " -DHW_IN=" + std::to_string(h_in * w_in);
            params += " -DHW_OUT=" + std::to_string(h_out * w_out);
            params += " -DW_IN=" + std::to_string(w_in);
            params += " -DW_OUT=" + std::to_string(w_out);
            params += " -DH_STRIDE=" + std::to_string(h_stride);
            params += " -DW_STRIDE=" + std::to_string(w_stride);
            params += " -DIN_OFF=" + std::to_string(in_offset);
            params += " -DOUT_OFF=" + std::to_string(out_offset);

            size_t ld0 = WG_SIZE;
            size_t gd0 = c * h_out * w_out;
            const std::vector<size_t> vld{ld0, 1, 1};
            std::vector<size_t> vgd{gd0, 1, 1};

            if(gd0 < MAX_ACTIVE_THREADS)
            {
                vgd = {gd0, static_cast<size_t>(n), 1};
                params += " -DIS_2D_WG=1";
            }
            else
            {
                params += " -DIS_2D_WG=0";
            }

            handle.AddKernel(
                kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(in, out);
        }
    }

    return handle.GetKernelTime();
}

// NCHW (or NCDHW) to NCHW_C4 (or NCDHW_C4)
float transpose_NCHW2Vec(Handle& handle,
                         const std::vector<std::size_t>& lens,
                         ConstData_t in,
                         Data_t out,
                         std::size_t vec_size,
                         bool trans,
                         bool forward)
{
    std::string program_name = "MIOpenUtilKernels5.cl";

    if(!(vec_size == 2 || vec_size == 4))
    {
        MIOPEN_THROW("Only support type half and int8!");
    }

    const auto n = lens[0];
    const auto c = lens[1];

    // "hw" is for any-D spatial data
    const auto hw = std::accumulate(
        lens.begin() + 2, lens.end(), std::size_t(1), std::multiplies<std::size_t>());

    // clang-format off
    std::string network_config =
        "n" + std::to_string(n) +
        "c" + std::to_string(c) +
        "hw" + std::to_string(hw) +
        "t" + std::to_string(static_cast<int>(trans)) +
        "v" + std::to_string(vec_size) +
        "f" + std::to_string(static_cast<int>(forward));
    // clang-format on

    std::string algo_name = "transpose_NCHWVecForward";

    auto&& kernels = handle.GetKernels(algo_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        auto n_vec = (trans && (n % vec_size != 0)) ? (n + (vec_size - n % vec_size)) : n;
        auto c_vec = (!trans && (c % vec_size != 0)) ? (c + (vec_size - c % vec_size)) : c;

        std::string kernel_name = "transpose_NCHW2Vec";

        const std::vector<size_t> vld{WG_SIZE, 1, 1};
        std::vector<size_t> vgd{1, 1, 1};

        int RD_BLCK = ((hw) % (vec_size * 2) == 0) ? static_cast<int>(vec_size) * 2
                                                   : static_cast<int>(vec_size);
        int HW_RD     = (static_cast<int>(hw) + RD_BLCK - 1) / RD_BLCK;
        size_t MAP_RD = HW_RD * (trans ? c : (c_vec / vec_size));

        std::string READ_TYPE =
            (RD_BLCK == vec_size) ? "uint" : "uint" + std::to_string(RD_BLCK / vec_size);
        int WR_BLCK            = RD_BLCK * static_cast<int>(vec_size);
        std::string WRITE_TYPE = "uint" + std::to_string(WR_BLCK / vec_size);

        std::string params;
        params += " -DFORWARD=" + std::to_string(static_cast<int>(forward));
        params += " -DN=" + std::to_string(n);
        params += " -DC=" + std::to_string(c);
        params += " -DHW=" + std::to_string(hw);
        params += " -DCHW=" + std::to_string(c * hw);
        params += " -DVEC_SIZE=" + std::to_string(vec_size);
        params += vec_size == 4 ? " -DDATA_TYPE=char" : " -DDATA_TYPE=ushort";

        params += " -DTRANS=" + std::to_string(static_cast<int>(trans));
        if(trans)
        {
            params += " -DNHW_OUT=" + std::to_string(n_vec * hw);
            params += " -DN_OUT=" + std::to_string(n_vec);
            params += " -DIS_N_ODD=" + std::to_string(static_cast<int>((n % vec_size) != 0));
        }
        else
        {
            params += " -DCHW_OUT=" + std::to_string(c_vec * hw);
            params += " -DIS_C_ODD=" + std::to_string(static_cast<int>((c % vec_size) != 0));
        }

        params += " -DIS_HW_ODD=" + std::to_string(static_cast<int>(((hw) % vec_size) != 0));
        params += " -DRD_BLCK=" + std::to_string(RD_BLCK);
        params += " -DWR_BLCK=" + std::to_string(WR_BLCK);
        params += " -DHW_RD=" + std::to_string(HW_RD);
        params += " -DMAP_RD=" + std::to_string(MAP_RD);
        params += " -DREAD_TYPE=" + READ_TYPE;
        params += " -DWRITE_TYPE=" + WRITE_TYPE;

        vgd[0] = MAP_RD;

        size_t gd1 = trans ? static_cast<size_t>(n_vec / vec_size) : static_cast<size_t>(n);

        /// disable iteration of n due to perf degrade
        /// \to-do fix the perf issue
        // if(vgd[0] < MAX_ACTIVE_THREADS)
        {
            vgd[1] = gd1;
            params += " -DIS_2D_WG=1";
        }
        // else
        //{
        // params += " -DIS_2D_WG=0";
        // params += " -DGD_1=" + std::to_string(gd1);
        //}

        handle.AddKernel(algo_name, network_config, program_name, kernel_name, vld, vgd, params)(
            in, out);
    }

    return handle.GetKernelTime();
}

float transpose_packed_MN2NM(Handle& handle,
                             int m,
                             int n,
                             int in_offset,
                             int out_offset,
                             ConstData_t in,
                             Data_t out,
                             miopenDataType_t type)
{

    std::string program_name = "MIOpenUtilKernels4.cl";

    std::string network_config = "n" + std::to_string(n) + "m" + std::to_string(m) + "inoff" +
                                 std::to_string(in_offset) + "otoff" + std::to_string(out_offset) +
                                 "t" + std::to_string(type);

    std::string kernel_name = "transpose_packed_MN2NM";

    auto&& kernels = handle.GetKernels(kernel_name, network_config);

    if(!kernels.empty())
    {
        auto kernel = kernels.front();
        kernel(in, out);
    }
    else
    {
        std::string params = GetDataTypeKernelParams(type);
        if(type == miopenInt8x4)
        {
            m /= 4;
            in_offset /= 4;
            out_offset /= 4;
        }

        if(!(type == miopenInt8x4 || type == miopenInt8))
        {
            MIOPEN_THROW("transpose_packed_MN2NM only meant for int8 variants.");
        }

        params += " -DNC_TRANS_MN2NM=1";

        params += " -DN=" + std::to_string(n);
        params += " -DM=" + std::to_string(m);
        params += " -DIN_OFF=" + std::to_string(in_offset);
        params += " -DOUT_OFF=" + std::to_string(out_offset);

        size_t ld0 = WG_SIZE;
        size_t gd0 = m * n;
        const std::vector<size_t> vld{ld0, 1, 1};
        std::vector<size_t> vgd{gd0, 1, 1};

        handle.AddKernel(kernel_name, network_config, program_name, kernel_name, vld, vgd, params)(
            in, out);
    }

    return handle.GetKernelTime();
}
} // namespace miopen


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_NONE_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_NONE_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/function/splatted.hpp>
#include <boost/simd/function/hmsb.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD ( none_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::pack_<bd::fundamental_<A0>, X>
                          )
  {
    BOOST_FORCEINLINE bool do_(const A0& a0, aggregate_storage const&) const BOOST_NOEXCEPT
    {
      auto const all0 = none(slice_high(a0));
      auto const all1 = none(slice_low(a0) );
      return  all0 && all1;
    }

    template<typename K>
    BOOST_FORCEINLINE bool do_(const A0& a0, K const&) const BOOST_NOEXCEPT
    {
      return hmsb(genmask(a0)).none();
    }

    BOOST_FORCEINLINE bool operator()(const A0& a0) const BOOST_NOEXCEPT
    {
      return do_(a0, typename A0::storage_kind{});
    }
  };

  BOOST_DISPATCH_OVERLOAD ( none_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::splatted_tag
                          , bs::pack_<bd::fundamental_<A0>, X>
                          )
  {
    BOOST_FORCEINLINE
    as_logical_t<A0> operator()( bs::splatted_tag const&, const A0& a0) const BOOST_NOEXCEPT
    {
      return as_logical_t<A0>(bs::none(a0));
    }
  };
} } }

#endif