#ifndef __MOTION_DETECT_HPP__
#define __MOTION_DETECT_HPP__

#pragma onece

#include <typeinfo>
#include <sml.hpp>
#include <map>
#include <string>

namespace sml = boost::sml;

namespace md{
    /// types
    struct none{};
    struct motion{};
    struct people{};
    struct mpboth{};
    /// consts
    const std::map<const std::string, int> kNumIn2Post = {{"n", 3}, {"m", 6}, {"p", 10}};
    const std::map<const std::string, int> kNumPost2None = {{"n", 2}, {"m", 4}, {"p", 8}};
    const string kKeyM= "m";
    const string kKeyN= "n";
    const string kKeyP= "p";
    const int kMaxCntMotionInPre = 5; // about 20s

    /// helpers
    auto event_inspection(auto e, std::map<const std::string, int> &m){
        if(std::is_same<decltype(e), people>::value){
            spdlog::info("type is people: {}",  ++m[kKeyP]);
        }else if(std::is_same<decltype(e), motion>::value){
            spdlog::info("type is motion: {}",  ++m[kKeyM]);
        }else if(std::is_same<decltype(e), mpboth>::value){
            spdlog::info("type is mpboth");
            return true;
        }else if(std::is_same<decltype(e), none>::value){
            spdlog::info("type is none: {}",  ++m[kKeyN]);
        }
        return false;
    }

    auto make_guard_fn(const std::map<const std::string, int> &konst){
        return [&konst](auto e){
            static std::map<const std::string, int>guards = {{"m", 0}, {"p", 0}, {"n", 0}};
            bool trans = false;
            bool reset = true;
            if(event_inspection(e, guards)||(guards[kKeyM] != 0 && guards[kKeyP] != 0)){
                trans = false;
            }else if(guards[kKeyP] >= konst.at(kKeyP)){
                trans = true;
            }else if(guards[kKeyM] >= konst.at(kKeyM)){
                trans = true;
            }else if(guards[kKeyN] >= konst.at(kKeyN)){
                trans = true;
            }else{
                trans = false;
                reset = false;
            }

            if(reset){
                guards = {{"m", 0}, {"p", 0}, {"n", 0}};
            }

            return trans;
        };
    }

    typedef bool (*fn_guard_t)(auto e);
    struct fsm {
        const fn_guard_t guard_in2post = make_guard_fn(kNumIn2Post);
        const bool guard_post2none = make_guard_fn(kNumPost2None);
        const void action_none2in = [this](auto e){
            this->timeStart = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
            if(std::is_same<decltype(e), people>::value){
                spdlog::info("none -p-> in, {}", this->timeStart/1000);
            }else if(std::is_same<decltype(e), mpboth>::value){
                spdlog::info("none -b-> in, {}", this->timeStart/1000);
                this->hasMotion = true;
            }
        };
        const void action_pre2in = [this](auto e){
            if(this->timeStart==0)
                this->timeStart = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
            if(std::is_same<decltype(e), people>::value){
                spdlog::info("pre -p-> in: time {}", this->timeStart/1000);
            }else if(std::is_same<decltype(e), mpboth>::value){
                spdlog::info("pre -b-> in: time {}", this->timeStart/1000);
            }
            this->hasMotion = true;
        };
        const void action_post2none = [this](auto e){
            auto timeEnd = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
            if(std::is_same<decltype(e), people>::value){
                spdlog::info("post -p-> none, {}", timeEnd/1000);
            }else if(std::is_same<decltype(e), motion>::value){
                spdlog::info("post -m-> none, {}", timeEnd/1000);
            }else if(std::is_same<decltype(e), none>::value){
                spdlog::info("post -n-> none, {}", timeEnd/1000);
            }
        };
        const void action_pre2pre = [this](auto e){
            static int cntMotionOnly = 0;
            cntMotionOnly++;
            if(cntMotionOnly >= kMaxCntMotionInPre) {
                cntMotionOnly = 0;
                this->timeStart = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
            }
        };

        inline auto operator()() noexcept{
            using namespace sml;
            return make_transition_table(
                *"init"_s = "none"_s,
                "none"_s + event<none>/[]{spdlog::info("none -> none");} = "none"_s,
                "none"_s + event<motion>/[]{spdlog::info("none -m-> pre");} = "pre"_s,
                "none"_s + event<people>/action_none2in = "in"_s,
                "none"_s + event<mpboth>/action_none2in = "in"_s,
                // pre
                "pre"_s + event<none>/[]{spdlog::info("pre -n-> none");} = "none"_s,
                "pre"_s + event<motion>/action_pre2pre = "pre"_s,
                "pre"_s + event<people>/action_pre2in = "in"_s,
                "pre"_s + event<mpboth>/action_pre2in = "in"_s,
                // in
                "in"_s + event<none> [guard_in2post]/[]{spdlog::info("in -n-> post");} = "post"_s,
                "in"_s + event<motion> [guard_in2post]/[]{spdlog::info("in -m-> post");} = "post"_s,
                "in"_s + event<people> [guard_in2post]/[]{spdlog::info("in -p-> post");} = "post"_s,
                "in"_s + event<mpboth>/guard_in2post,
                // post
                "post"_s + event<mpboth>/guard_post2none = "in"_s,
                "post"_s + event<people> [guard_post2none]/action_post2none = "none"_s,
                "post"_s + event<motion> [guard_post2none]/action_post2none = "none"_s,
                "post"_s + event<none> [guard_post2none]/action_post2none = "none"_s,
                "none"_s + sml::on_entry<_> / [this]{hasMotion = false,timeStart = 0;}
            );
        }
        public:
            bool hasMotion = false;
            uint64_t timeStart =0;
    };
}

#endif