/** 
 * @file llkeyconflict.h
 * @brief 
 *
 * $LicenseInfo:firstyear=2019&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2019, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLKEYCONFLICT_H
#define LL_LLKEYCONFLICT_H

#include "llkeybind.h"
#include "llviewerinput.h"


class LLKeyConflict
{
public:
    LLKeyConflict() : mAssignable(true), mConflictMask(U32_MAX) {} //temporary assignable, don't forget to change once all keys are recorded
    LLKeyConflict(bool assignable, U32 conflict_mask)
        : mAssignable(assignable), mConflictMask(conflict_mask) {}
    LLKeyConflict(const LLKeyBind &bind, bool assignable, U32 conflict_mask)
        : mAssignable(assignable), mConflictMask(conflict_mask), mKeyBind(bind) {}

    LLKeyData getPrimaryKeyData() { return mKeyBind.getKeyData(0); }
    LLKeyData getKeyData(U32 index) { return mKeyBind.getKeyData(index); }
    void setPrimaryKeyData(const LLKeyData& data) { mKeyBind.replaceKeyData(data, 0); }
    void setKeyData(const LLKeyData& data, U32 index) { mKeyBind.replaceKeyData(data, index); }
    bool canHandle(EMouseClickType mouse, KEY key, MASK mask) { return mKeyBind.canHandle(mouse, key, mask); }

    LLKeyBind mKeyBind;
    bool mAssignable; // whether user can change key or key simply acts as placeholder
    U32 mConflictMask;
};

class LLKeyConflictHandler
{
public:

    enum ESourceMode // partially repeats e_keyboard_mode
    {
        MODE_FIRST_PERSON,
        MODE_THIRD_PERSON,
        MODE_EDIT_AVATAR,
        MODE_SITTING,
        MODE_SAVED_SETTINGS, // for settings from saved settings
        MODE_COUNT
    };

    const U32 CONFLICT_NOTHING = 0;
    const U32 CONFLICT_LMOUSE = 0x1 << 1;
    // at the moment this just means that key will conflict with everything that is identical
    const U32 CONFLICT_ANY = U32_MAX;

    // Note: missed selection and edition commands (would be really nice to go through selection via MB4/5 or wheel)

    LLKeyConflictHandler();
    LLKeyConflictHandler(ESourceMode mode);
    ~LLKeyConflictHandler();

    bool canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask);
    bool canHandleKey(const std::string &control_name, KEY key, MASK mask);
    bool canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask);
    bool canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask); //Just for convinience
    bool canAssignControl(const std::string &control_name);
    static bool isReservedByMenu(const KEY &key, const MASK &mask);
    static bool isReservedByMenu(const LLKeyData &data);

    // @control_name - see REGISTER_KEYBOARD_ACTION in llviewerinput for avaliable options,
    // usually this is just name of the function
    // @data_index - single control (function) can have multiple key combinations trigering
    // it, this index indicates combination function will change/add; Note that preferences
    // floater can only display up to 3 options, but data_index can be bigger than that
    // @mouse_ind - mouse action (middle click, MB5 etc)
    // @key - keyboard key action
    // @mask - shift/ctrl/alt flags
    // @ignore_mask - Either to expect exact match (ctrl+K will not trigger if ctrl+shift+K
    // is active) or ignore not expected masks as long as expected mask is present
    // (ctrl+K will be triggered if ctrl+shift+K is active)
    bool registerControl(const std::string &control_name, U32 data_index, EMouseClickType mouse_ind, KEY key, MASK mask, bool ignore_mask); //todo: return conflicts?
    bool clearControl(const std::string &control_name, U32 data_index);

    LLKeyData getControl(const std::string &control_name, U32 data_index);
    bool isControlEmpty(const std::string &control_name);

    // localized string
    static std::string getStringFromKeyData(const LLKeyData& keydata);
    std::string getControlString(const std::string &control_name, U32 data_index);

    // Load single control, overrides existing one if names match
    void loadFromControlSettings(const std::string &name);
    // Drops any changes loads controls with ones from 'saved settings' or from xml
    void loadFromSettings(ESourceMode load_mode);

    // Saves settings to 'saved settings' or to xml
    // If 'temporary' is set, function will save settings to temporary
    // file and reload input bindings from temporary file.
    // 'temporary' does not support gSavedSettings, those are handled
    // by preferences, so 'temporary' is such case will simply not
    // reset mHasUnsavedChanges
    //
    // 'temporary' exists to support ability of live-editing settings in
    // preferences: temporary for testing changes 'live' without saving them,
    // then hitting ok/cancel and save/discard values permanently.
    void saveToSettings(bool apply_temporary = false);

    LLKeyData getDefaultControl(const std::string &control_name, U32 data_index);
    // Resets keybinding to default variant from 'saved settings' or xml
    void resetToDefault(const std::string &control_name, U32 index);
    void resetToDefault(const std::string &control_name);
    // resets current mode to defaults
    void resetToDefaults();

    bool empty() const { return mControlsMap.empty(); }
    void clear();

    // reloads bindings from last valid user's xml or from default xml
    // to keyboard's handler
    static void resetKeyboardBindings();

    bool hasUnsavedChanges() const { return mHasUnsavedChanges; }
    void setLoadMode(ESourceMode mode) { mLoadMode = mode; }
    ESourceMode getLoadMode() const { return mLoadMode; }

private:
    void resetToDefaultAndResolve(const std::string &control_name, bool ignore_conflicts);
    void resetToDefaultsAndResolve();

    // at the moment these kind of control is not savable, but takes part in conflict resolution
    void registerTemporaryControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask, U32 conflict_mask);
    // conflict mask 0 means that any conflicts will be ignored
    void registerTemporaryControl(const std::string &control_name, U32 conflict_mask = 0);

    typedef std::map<std::string, LLKeyConflict> control_map_t;
    void loadFromSettings(const LLViewerInput::KeyMode& keymode, control_map_t *destination);
    bool loadFromSettings(const ESourceMode &load_mode, const std::string &filename, control_map_t *destination);
    void generatePlaceholders(ESourceMode load_mode); //E.x. non-assignable values
    // returns false in case user is trying to reuse control that can't be reassigned
    bool removeConflicts(const LLKeyData &data, U32 conlict_mask);

    // removes flags and removes temporary file, returns 'true' if file was removed
    bool clearUnsavedChanges();
    // return true if there was a file to remove
    static bool clearTemporaryFile();

    control_map_t mControlsMap;
    control_map_t mDefaultsMap;
    bool mHasUnsavedChanges;
    ESourceMode mLoadMode;

    // To implement 'apply immediately'+revert on cancel, class applies changes to temporary file
    // but this only works for settings from keybndings files (key_bindings.xml)
    // saved setting rely onto external mechanism of preferences floater
    bool mUsesTemporaryFile;
    static S32 sTemporaryFileUseCount;
};


#endif  // LL_LLKEYCONFLICT_H