#ifndef FACT_DimState
#define FACT_DimState

#include "State.h"

class DimState
{
protected:
    typedef function<void(const EventImp &)> callback;

    callback fCallback;

public:
    DimState(const string &n, const string s="STATE") : server(n),
        service(n+"/"+s), info(make_pair(Time(), -256))
    {
    }
    virtual ~DimState()
    {
    }

    string server;
    string service;

    pair<Time, int> info;
    string msg;

    virtual void Subscribe(StateMachineImp &imp)
    {
        imp.Subscribe(service.c_str())
            (imp.Wrap(bind(&DimState::Handler, this, placeholders::_1)));
    }

    void SetCallback(const callback &cb)
    {
        fCallback = cb;
    }

    void HandlerImp(const EventImp &evt)
    {
        const bool disconnected = evt.GetSize()==0;

        info = make_pair(evt.GetTime(), disconnected ? -256 : evt.GetQoS());

        msg = disconnected ? "" : evt.GetString();
    }

    void Callback(const EventImp &evt)
    {
        if (fCallback)
            fCallback(evt);
    }

    virtual void Handler(const EventImp &evt)
    {
        HandlerImp(evt);
        Callback(evt);
    }


    const Time &time() const { return info.first; }
    const int  &state() const { return info.second; }

    bool online() const { return info.second>-256; }

    virtual State description() const { return State(-257, ""); }
};

ostream &operator<<(ostream& out, const DimState &s)
{
    const State rc = s.description();

    out << s.time().GetAsStr("%H:%M:%S.%f").substr(0, 12) << " - ";
    out << kBold << s.server;

    if (s.state()==-256)
        return out << ": Offline";

    if (rc.index==-257)
        return out;

    out << ": ";

//    if (rc.index==-2)
//        out << s.state();
//    else
        out << rc.name << "[" << rc.index << "]";

    if (!rc.comment.empty())
        out << " - " << kBlue << rc.comment;

    return out;
}


class DimDescribedState : public DimState
{
    vector<State> states;

public:
    DimDescribedState(const string &n) : DimState(n)
    {
    }

    virtual void Subscribe(StateMachineImp &imp)
    {
        imp.Subscribe((server+"/STATE_LIST").c_str())
            (imp.Wrap(bind(&DimDescribedState::HandleDesc, this, placeholders::_1)));

        DimState::Subscribe(imp);
    }

    void HandleDesc(const EventImp &evt)
    {
        if (evt.GetSize()>0)
        {
            states = State::SplitStates(evt.GetString());
            states.push_back(State(-256, "Offline"));
        }
    }

    State description() const
    {
        for (auto it=states.begin(); it!=states.end(); it++)
            if (it->index==state())
                return *it;

        return State(-257, "n/a");
    }
};

class DimVersion : public DimState
{
public:
    DimVersion() : DimState("DIS_DNS", "VERSION_NUMBER") { }

    void Handler(const EventImp &evt)
    {
        HandlerImp(evt);

        info.second = evt.GetSize()==4 ? evt.GetInt() : -256;
        if (info.second==0)
            info.second=-256;

        Callback(evt);
    }

    string version() const
    {
        if (!online())
            return "Offline";

        ostringstream out;
        out << "V" << info.second/100 << 'r' << info.second%100;
        return out.str();
    }

    State description() const
    {
        return State(state(), version());
    }
};

class DimControl : public DimState
{
    map<string, callback> fCallbacks;

public:
    DimControl() : DimState("DIM_CONTROL") { }

    string file;
    string shortmsg;
    int scriptdepth;

    void AddCallback(const string &script, const callback &cb)
    {
        fCallbacks[script] = cb;
    }

    void Handler(const EventImp &evt)
    {
        HandlerImp(evt);

        shortmsg    = msg;
        file        = "";
        scriptdepth = -1;

        // Evaluate msg
        const size_t p0 = msg.find_first_of(':');
        if (p0==string::npos)
            return;

        // Evaluate scriptdepth
        const size_t ps = msg.find_first_of('-');
        if (ps!=string::npos)
            scriptdepth = atoi(msg.c_str()+ps+1);

        // Find filename
        const size_t p1 = msg.find_last_of('[');
        if (p1==string::npos)
            return;

        const size_t p2 = msg.find_first_of(':', p0+1);

        const size_t p3 = p2==string::npos || p2>p1 ? p1-1 : p2;

        file = msg.substr(p0+2, p3-p0-2);

        shortmsg.erase(p0, p3-p0);

        Callback(evt);

        const auto func = fCallbacks.find(file);
        if (func==fCallbacks.end())
            return;

        // Call callback
        func->second(evt);
    }

    State description() const
    {
        return State(state(), "Current label");
    }
};

#endif
