// ============================================================ //
//                                                              //
//   File      : info_window.h                                  //
//   Purpose   :                                                //
//                                                              //
//   Coded by Ralf Westram (coder@reallysoft.de) in July 2013   //
//   Institute of Microbiology (Technical University Munich)    //
//   http://www.arb-home.de/                                    //
//                                                              //
// ============================================================ //

#ifndef INFO_WINDOW_H
#define INFO_WINDOW_H

#ifndef AW_WINDOW_HXX
#include <aw_window.hxx>
#endif
#ifndef DB_SCANNER_HXX
#include <db_scanner.hxx>
#endif
#ifndef ARB_MSG_H
#include <arb_msg.h>
#endif
#ifndef ARBDBT_H
#include <arbdbt.h>
#endif
#ifndef ARB_UNORDERED_MAP_H
#include <arb_unordered_map.h>
#endif

// --------------------
//      InfoWindow

class InfoWindow : virtual Noncopyable {
    AW_window *aww;
    DbScanner *scanner;
    int        detach_id;

    mutable bool used; // window in use (i.e. not yet popped down)

    const ItemSelector& getSelector() const { return scanner->get_selector(); }
    QUERY_ITEM_TYPE itemtype() const { return getSelector().type; }
    const char *itemname() const { return getSelector().item_name; }

    AW_root *get_root() const { return aww->get_root(); }

    void update_window_title() const {
        if (is_detached()) { // only change window titles of detached windows
            char *title         = NULp;
            char *mapped_item = scanner->get_mapped_item_id();
            if (mapped_item) {
                title = GBS_global_string_copy("%s information (%s)", mapped_item, itemname());
                free(mapped_item);
            }
            else {
                title = GBS_global_string_copy("Press 'Update' to attach selected %s", itemname());
            }

            arb_assert(title);
            aww->set_window_title(title);
            free(title);
        }
    }

public:

    static const int MAIN_WINDOW = 0;
    typedef SmartPtr<InfoWindow> Ptr;
    typedef void (*detached_uppopper)(AW_window*, const InfoWindow*);

    InfoWindow(AW_window *aww_, DbScanner *scanner_, int detach_id_)
        : aww(aww_),
          scanner(scanner_),
          detach_id(detach_id_),
          used(true)
    {}
    ~InfoWindow() {
        // @@@ should free 'scanner'
    }

    bool is_detached() const { return detach_id>MAIN_WINDOW; }
    bool is_maininfo() const { return detach_id == MAIN_WINDOW; }
    int getDetachID() const { return detach_id; }

    GBDATA *get_gbmain() const { return scanner->get_gb_main(); }

    bool is_used() const { return used; }
    void set_used(bool used_) const {
        arb_assert(!is_maininfo()); // cannot change for maininfo window (it is never reused!)
        used = used_;
    }

    bool mapsOrganism() const {
        return itemtype() == QUERY_ITEM_ORGANISM;
    }
    bool handlesSameItemtypeAs(const InfoWindow& other) const {
        return itemtype() == other.itemtype();
    }

    GBDATA *get_selected_item() const {
        GBDATA         *gb_main = get_gbmain();
        GB_transaction  ta(gb_main);
        return getSelector().get_selected_item(gb_main, get_root());
    }

    void map_item(GBDATA *gb_item) const {
        scanner->Map(gb_item, getSelector().change_key_path);
    }
    void map_selected_item() const { map_item(get_selected_item()); }
    void attach_selected_item() const {
        map_selected_item();
        update_window_title();
    }
    void reuse() const {
        arb_assert(is_detached());
        set_used(true);
        aww->activate();
        attach_selected_item();
    }
    void reactivate() const {
        arb_assert(is_maininfo());
        arb_assert(is_used());
        aww->activate();     // popup existing window
        map_selected_item(); // force item update (did not follow while it was popped-down)
    }
    void hide() const { aww->hide(); }

    void bind_to_selected_item() const;
    void add_detach_area(detached_uppopper popup_detached_cb) const;

    void display_selected_item() const;
    void detach_selected_item(detached_uppopper popup_detached_cb) const;
};

class InfoWindowRegistry {
    typedef arb_unordered_map<AW_window*, InfoWindow::Ptr> WinMap;
    WinMap win;

    InfoWindowRegistry(){} // InfoWindowRegistry is a singleton and always exists

public:
    const InfoWindow& registerInfoWindow(AW_window *aww, DbScanner *scanner, int detach_id) {
        // registers a new instance of an InfoWindow
        // returned reference is persistent
        arb_assert(aww && scanner);
        arb_assert(detach_id >= InfoWindow::MAIN_WINDOW);

        arb_assert(!find(aww));

        win[aww] = new InfoWindow(aww, scanner, detach_id);
        return *win[aww];
    }

    const InfoWindow* find(AW_window *aww) {
        // returns InfoWindow for 'aww' (or NULp if no such InfoWindow)
        WinMap::iterator found = win.find(aww);
        return found == win.end() ? NULp : &*found->second;
    }

    int allocate_detach_id(const InfoWindow& other) {
        arb_assert(other.is_maininfo());

        int maxUsedID = InfoWindow::MAIN_WINDOW;
        for (WinMap::iterator i = win.begin(); i != win.end(); ++i) {
            const InfoWindow::Ptr& info = i->second;
            if (info->handlesSameItemtypeAs(other)) {
                maxUsedID = std::max(maxUsedID, info->getDetachID());
            }
        }
        return maxUsedID+1;
    }
    const InfoWindow *find_reusable_of_same_type_as(const InfoWindow& other) {
        for (WinMap::iterator i = win.begin(); i != win.end(); ++i) {
            const InfoWindow::Ptr& info = i->second;
            if (info->handlesSameItemtypeAs(other) && !info->is_used()) return &*info;
        }
        return NULp;
    }
    const InfoWindow *find_maininfo_of_same_type_as(const InfoWindow& other) {
        for (WinMap::iterator i = win.begin(); i != win.end(); ++i) {
            const InfoWindow::Ptr& info = i->second;
            if (info->handlesSameItemtypeAs(other) && info->is_used() && info->is_maininfo()) return &*info;
        }
        return NULp;
    }

    static InfoWindowRegistry infowin;

    static void reactivate(AW_window *aww)  {
        const InfoWindow *iwin = infowin.find(aww);
        arb_assert(iwin); // not called with an info-window
        if (iwin) iwin->reactivate();
    }

    static const char *localize_scanner_id(const char *scanner_id, int detach_id) {
        return detach_id == InfoWindow::MAIN_WINDOW
            ? scanner_id
            : GBS_global_string("%s_%i", scanner_id, detach_id);
    }
};

#else
#error info_window.h included twice
#endif // INFO_WINDOW_H
