#include "objects_update_state.h"

#include "objects_deleter.h"
#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/check_permissions.h"
#include "maps/wikimap/mapspro/services/editor/src/commit.h"
#include "maps/wikimap/mapspro/services/editor/src/objects_cache.h"
#include "maps/wikimap/mapspro/services/editor/src/serialize/xml_parser.h"
#include "maps/wikimap/mapspro/services/editor/src/serialize/request_data.h"

namespace maps {
namespace wiki {

namespace {

const std::string TASK_METHOD_NAME = "ObjectsUpdateState";
const std::string ACTION_ID_DELETE = "delete";

class ObjectsUpdateStateRequestData : public RequestData
{
public:
    ObjectsUpdateStateRequestData(common::FormatType format, const std::string& body, ObjectsCache& cache)
        : state_(GeoObject::State::Deleted)
    {
        parse(format, body, cache);
    }

    GeoObject::State state() const { return state_; }

protected:
    void parseContextDataJson(const json::Value& context, ObjectsCache&) final
    {
        state_ = boost::lexical_cast<GeoObject::State>(
            context[STR_STATE].as<std::string>());
    }

    void parseContextDataXml(const maps::xml3::Node& contextNode, ObjectsCache&) final
    {
        xml3::Node stateNode = contextNode.node("wmp:state");
        state_ = boost::lexical_cast<GeoObject::State>(stateNode.value<std::string>());
    }

private:
    GeoObject::State state_;
};

} // namespace

ObjectsUpdateState::ObjectsUpdateState(
        const ObserverCollection& observers,
        const Request& request,
        taskutils::TaskID asyncTaskID)
    : controller::BaseController<ObjectsUpdateState>(BOOST_CURRENT_FUNCTION, asyncTaskID)
    , observers_(observers)
    , request_(request)
{}

std::string
ObjectsUpdateState::printRequest() const
{
    return request_.dump();
}

std::string
ObjectsUpdateState::Request::dump() const
{
    std::stringstream ss;
    ss << " user: " << userId();
    ss << " oid: " << objectId;
    ss << " state: " << state;
    ss << " branch: " << branchId;
    if (feedbackTaskId) {
        ss << " feedback-task-id: " << *feedbackTaskId;
    }
    return ss.str();
}

void
ObjectsUpdateState::control()
{
   setObjectState();
}

void
ObjectsUpdateState::saveAndNotify(ObjectsCache& cache)
{
    CommitContext commitContext{request_.feedbackTaskId};
    cache.save(
        request_.userId(),
        request_.objectId
            ? common::COMMIT_PROPVAL_OBJECT_DELETED
            : common::COMMIT_PROPVAL_GROUP_DELETED);
    result_->taskName = taskName();
    result_->token = observers_.doCommit(cache, request_.userContext, commitContext);
    result_->commit = cache.savedCommit();
}

void
ObjectsUpdateState::setObjectState()
{
    auto branchContext = BranchContextFacade::acquireWrite(request_.branchId, request_.userId());
    ObjectsCache cache(
        branchContext,
        boost::none);
    TOIds oids;
    GeoObject::State state = GeoObject::State::Deleted;
    if (request_.body.empty()) {
        oids.insert(request_.objectId);
        state =  boost::lexical_cast<GeoObject::State>(request_.state);
    } else {
        ObjectsUpdateStateRequestData requestData(request_.format, request_.body, cache);
        oids = TOIds(requestData.oids().begin(), requestData.oids().end());
        state = requestData.state();
        CheckPermissions(request_.userId(), cache.workCore()).checkPermissionsForGroupDelete();
        cfg()->editor()->objectsUpdateRestrictions().checkObjects(ACTION_ID_DELETE, cache.get(oids));
    }

    //REMINDER Remember to add related commit 'action'
    //if adding setting other states
    WIKI_REQUIRE(
        state == GeoObject::State::Deleted, ERR_INVALID_OPERATION,
        "Not implemented changing non-delete state");

    ObjectsDeleter deleter(cache);
    if (deleter.changeState(request_.userId(), oids)) {
        saveAndNotify(cache);
    }
}

const std::string&
ObjectsUpdateState::taskName()
{
    return TASK_METHOD_NAME;
}

} // namespace wiki
} // namespace maps
