// ライセンス: GPL2

//#define _DEBUG
#include "jddebug.h"

#include "dragnote.h"
#include "tablabel.h"
#include "toolbar.h"
#include "view.h"
#include "iconpopup.h"

#include "icons/iconmanager.h"

#include "control/controlid.h"

#include "dndmanager.h"
#include "session.h"

#include <cstring>

using namespace SKELETON;

DragableNoteBook::DragableNoteBook()
    : Gtk::VBox()
    , m_notebook_tab( this )
    , m_notebook_toolbar( this )
    , m_notebook_view( this )
    , m_bt_tabswitch( this )
    , m_page( -1 )
    , m_dragging_tab( false )
    , m_dragable( false )
    , m_down_arrow( nullptr )
{
    set_spacing( 0 );

    m_notebook_tab.signal_switch_page().connect( sigc::mem_fun( *this, &DragableNoteBook::slot_switch_page_tab ) );
    m_notebook_tab.sig_button_press().connect( sigc::mem_fun( *this, &DragableNoteBook::slot_button_press_event ) );
    m_notebook_tab.sig_button_release().connect( sigc::mem_fun( *this, &DragableNoteBook::slot_button_release_event ) );

    m_notebook_tab.sig_tab_drag_motion().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_motion ) );

    m_notebook_tab.sig_scroll_event().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_scroll_event ) );

    m_hbox_tab.pack_start( m_notebook_tab );
    m_hbox_tab.pack_start( m_bt_tabswitch, Gtk::PACK_SHRINK );
    m_bt_tabswitch.set_tooltip_text( "タブの一覧表示" );

    pack_start( m_hbox_tab, Gtk::PACK_SHRINK );
    pack_start( m_notebook_toolbar, Gtk::PACK_SHRINK );
    pack_start( m_notebook_view );
    m_show_tabs = true;
    m_show_toolbar = true;

    memset( &m_alloc_old, 0, sizeof( Alloc_NoteBook ) );

    show_all_children();
}



DragableNoteBook::~DragableNoteBook()
{
    if( m_down_arrow ) delete m_down_arrow;
}



//
// クロック入力
//
void DragableNoteBook::clock_in()
{
    m_notebook_tab.clock_in();
}


void DragableNoteBook::set_show_tabs( bool show_tabs )
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::set_show_tabs show_tabs = " << show_tabs << std::endl;
#endif

    if( ! show_tabs && m_show_tabs ){

        remove( m_hbox_tab );

        m_show_tabs = false;
    }
    else if( show_tabs && ! m_show_tabs ){

        if( m_show_toolbar && m_notebook_toolbar.get_n_pages() )
            remove( m_notebook_toolbar );
        remove( m_notebook_view );

        pack_start( m_hbox_tab, Gtk::PACK_SHRINK );
        if( m_show_toolbar && m_notebook_toolbar.get_n_pages() )
            pack_start( m_notebook_toolbar, Gtk::PACK_SHRINK );
        pack_start( m_notebook_view );

        m_show_tabs = true;
    }
}


void DragableNoteBook::set_scrollable( bool scrollable )
{
    m_notebook_tab.set_scrollable( scrollable );
}


int DragableNoteBook::get_n_pages()
{
    return m_notebook_view.get_n_pages();
}


Gtk::Widget* DragableNoteBook::get_nth_page( int page_num )
{
    return m_notebook_view.get_nth_page( page_num );
}


int DragableNoteBook::page_num( const Gtk::Widget& child )
{
    return m_notebook_view.page_num( child );
}


int DragableNoteBook::get_current_page()
{
    return m_notebook_view.get_current_page();
}


void DragableNoteBook::set_current_page( int page_num )
{
    if( get_current_page() == page_num ) return;
    if( page_num >= get_n_pages() ) page_num = get_n_pages()-1;

    m_notebook_tab.set_current_page( page_num );
    m_notebook_view.set_current_page( page_num );

    SKELETON::View* view = dynamic_cast< View* >( get_nth_page( page_num ) );
    if( view ) set_current_toolbar( view->get_id_toolbar(), view );
}


//
// ページのappendとinsert
//
int DragableNoteBook::append_page( const std::string& url, Gtk::Widget& child )
{
    m_notebook_view.append_page( child );

    m_bt_tabswitch.show_button();

    SKELETON::TabLabel* tablabel = create_tablabel( url );
    return m_notebook_tab.append_tab( *tablabel );
}


int DragableNoteBook::insert_page( const std::string& url, Gtk::Widget& child, int page )
{
    m_notebook_view.insert_page( child, page );

    m_bt_tabswitch.show_button();

    SKELETON::TabLabel* tablabel = create_tablabel( url );
    return m_notebook_tab.insert_tab( *tablabel, page );
}


//
// タブの文字列取得
//
const std::string& DragableNoteBook::get_tab_fulltext( const int page )
{
    return m_notebook_tab.get_tab_fulltext( page );
}



//
// タブに文字列をセットとタブ幅調整
//
void DragableNoteBook::set_tab_fulltext( const std::string& str, const int page )
{
    m_notebook_tab.set_tab_fulltext( str, page );
}



//
// ページ取り除き
//
void DragableNoteBook::remove_page( const int page, const bool adjust_tab )
{
    SKELETON::TabLabel* tablabel = m_notebook_tab.get_tablabel( page );

    m_notebook_tab.remove_tab( page, adjust_tab );
    m_notebook_view.remove_page( page );

    if( tablabel ) delete tablabel;

    if( ! get_n_pages() ) m_bt_tabswitch.hide_button();
}


//
// ツールバー取得
//
SKELETON::ToolBar* DragableNoteBook::get_toolbar( int page )
{
    return dynamic_cast< SKELETON::ToolBar* >( m_notebook_toolbar.get_nth_page( page ) );
}


//
// ツールバー全体を表示
//
// タブにビューが表示されたら admin から呼び出される
//
void DragableNoteBook::show_toolbar()
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::show_toolbar\n";
#endif

    if( ! m_show_toolbar && m_notebook_toolbar.get_n_pages() ){

        remove( m_notebook_view );

        pack_start( m_notebook_toolbar, Gtk::PACK_SHRINK );
        pack_start( m_notebook_view );

        m_show_toolbar = true;
    }
}


//
// ツールバー全体を非表示
//
// タブに全てのビューが無くなったら admin から呼び出される
//
void DragableNoteBook::hide_toolbar()
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::hide_toolbar\n";
#endif

    if( m_show_toolbar && m_notebook_toolbar.get_n_pages() ){

        remove( m_notebook_toolbar );

        m_show_toolbar = false;
    }
}


//
// ツールバーセット
//
// 各Adminクラスの virtual void show_toolbar() でセットされる
//
void DragableNoteBook::append_toolbar( Gtk::Widget& toolbar )
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::append_toolbar\n";
#endif

    m_notebook_toolbar.append_page( toolbar );
}


//
// ツールバー切り替え
//
void DragableNoteBook::set_current_toolbar( const int id_toolbar, SKELETON::View* view )
{
    // タブ操作中
    if( SESSION::is_tab_operating( view->get_url_admin() ) ) return;

#ifdef _DEBUG
    std::cout << "DragableNoteBook::set_current_toolbar id = " << id_toolbar
              << " / " << m_notebook_toolbar.get_n_pages() << std::endl
              << "url = " << view->get_url() << std::endl;
#endif

    if( m_notebook_toolbar.get_n_pages() <= id_toolbar ) return;

    m_notebook_toolbar.set_current_page( id_toolbar );

    // ツールバーのラベルなどの情報を更新
    SKELETON::ToolBar* toolbar = get_toolbar( id_toolbar );
    if( toolbar ){
        toolbar->set_view( view );
        toolbar->show_toolbar();
    }
}


int DragableNoteBook::get_current_toolbar()
{
    return m_notebook_toolbar.get_current_page();
}


//
// ツールバー内の検索ボックスにフォーカスを移す
//
void DragableNoteBook::focus_toolbar_search()
{
    SKELETON::ToolBar* toolbar = get_toolbar( m_notebook_toolbar.get_current_page() );
    if( toolbar ) toolbar->focus_entry_search();
}


//
// ツールバーURL更新
//
void DragableNoteBook::update_toolbar_url( const std::string& url_old, const std::string& url_new )
{
    for( int i = 0; i < m_notebook_toolbar.get_n_pages(); ++i ){
        SKELETON::ToolBar* toolbar = get_toolbar( i );
        if( toolbar && toolbar->get_url() == url_old ) toolbar->set_url( url_new );
    }
}


//
// ツールバーボタン表示更新
//
void DragableNoteBook::update_toolbar_button()
{
    for( int i = 0; i < m_notebook_toolbar.get_n_pages(); ++i ){
        SKELETON::ToolBar* toolbar = get_toolbar( i );
        if( toolbar ) toolbar->update_button();
    } 
}


//
// タブのアイコンを取得する
//
int DragableNoteBook::get_tabicon( const int page )
{
    SKELETON::TabLabel* tablabel = m_notebook_tab.get_tablabel( page );
    if( tablabel ) return tablabel->get_id_icon();

    return ICON::NONE;
}


//
// タブにアイコンをセットする
//
void DragableNoteBook::set_tabicon( const std::string& iconname, const int page, const int id )
{
    SKELETON::TabLabel* tablabel = m_notebook_tab.get_tablabel( page );
    if( tablabel && id != ICON::NONE ) tablabel->set_id_icon( id );
}


//
// タブ作成
//
SKELETON::TabLabel* DragableNoteBook::create_tablabel( const std::string& url )
{
    SKELETON::TabLabel *tablabel = new SKELETON::TabLabel( url );

    // ドラッグ設定
    GdkEventButton event;
    m_control.get_eventbutton( CONTROL::DragStartButton, event );
    tablabel->set_dragable( m_dragable, event.button );

    tablabel->sig_tab_drag_begin().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_begin ) );
    tablabel->sig_tab_drag_data_get().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_data_get ) );
    tablabel->sig_tab_drag_end().connect( sigc::mem_fun(*this, &DragableNoteBook::slot_drag_end ) );
    
    return tablabel;
}


//
// タブの幅を固定するか
//
void DragableNoteBook::set_fixtab( bool fix )
{
    m_notebook_tab.set_fixtab( fix );
}


//
// タブ幅調整
//
bool DragableNoteBook::adjust_tabwidth()
{
    return m_notebook_tab.adjust_tabwidth();
}


//
// notebook_tabのタブが切り替わったときに呼ばれるslot
//
void DragableNoteBook::slot_switch_page_tab( Gtk::Widget* bookpage, guint page )
{
    // view も切り替える
    m_notebook_view.set_current_page( page );

    // ツールバー(枠) 再描画
    m_notebook_toolbar.queue_draw();

    m_sig_switch_page.emit( bookpage, page );
}


//
// タブの上でボタンを押した
//
bool DragableNoteBook::slot_button_press_event( GdkEventButton* event )
{
    // ボタンを押した時点でのページ番号を記録しておく
    m_page = m_notebook_tab.get_page_under_mouse();
    m_dragging_tab = false;

    // ダブルクリック
    // button_release_eventでは event->type に必ず GDK_BUTTON_RELEASE が入る
    m_dblclick = false;
    if( event->type == GDK_2BUTTON_PRESS ) m_dblclick = true; 

#ifdef _DEBUG
    std::cout << "DragableNoteBook::on_button_press_event page = " << m_page  << std::endl;
    std::cout << "x = " << (int)event->x_root << " y = " << (int)event->y_root
              << " dblclick = " << m_dblclick << std::endl;
#endif

    if( m_page >= 0 && m_page < get_n_pages() ){

        // ページ切替え
        if( m_control.button_alloted( event, CONTROL::ClickButton ) ){

            set_current_page( m_page );
            m_sig_tab_clicked.emit( m_page );
        }

        return true;
    }
    else m_page = -1;

    return false;
}


//
// タブの上でボタンを離した
//
bool DragableNoteBook::slot_button_release_event( GdkEventButton* event )
{
    const int page = m_notebook_tab.get_page_under_mouse();
    const int x = (int)event->x_root;
    const int y = (int)event->y_root;

#ifdef _DEBUG
    std::cout << "DragableNoteBook::on_button_release_event\n";
    std::cout << "x = " << (int)event->x_root << " y = " << (int)event->y_root << std::endl;
#endif

    if( ! m_dragging_tab && m_page >= 0 && m_page < get_n_pages() ){

        // ダブルクリックの処理のため一時的にtypeを切替える
        GdkEventType type_copy = event->type;
        if( m_dblclick ) event->type = GDK_2BUTTON_PRESS;

        // タブを閉じる
        if( page == m_page && m_control.button_alloted( event, CONTROL::CloseTabButton ) ){
            m_sig_tab_close.emit( m_page );

            // タブにページが残ってなかったらtrueをreturnしないと落ちる
            // TabNotebook::on_button_release_event() も参照せよ
            //
            // (注意) なぜか m_notebook_tab.get_n_pages() < m_notebook_view.get_n_pages() の時があって
            // 以前の様に
            // if( get_n_pages() == 0 )
            // という条件では m_notebook_tab.get_n_pages() = 0 でも get_n_pages() != 0 になって落ちることがある
            if( m_notebook_tab.get_n_pages() == 0 ){
                m_page = -1;
                return true;
            }
        }

        // タブを再読み込み
        else if( m_control.button_alloted( event, CONTROL::ReloadTabButton ) ) m_sig_tab_reload.emit( m_page );

        // ポップアップメニュー
        else if( m_control.button_alloted( event, CONTROL::PopupmenuButton ) ) m_sig_tab_menu.emit( m_page, x, y );

        m_page = -1;
        event->type = type_copy;
    }

    return false;
}



// notebook_tab の上でホイールを回した
bool DragableNoteBook::slot_scroll_event( GdkEventScroll* event )
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::slot_scroll_event direction = " << event->direction << " page = " << get_current_page() << std::endl;
#endif

    bool ret = false;
    int next_page;

    if( event->direction == GDK_SCROLL_UP ) {
        next_page = get_current_page() - 1; // If negative, the last page will be used.
        ret = true;
    }
    else if( event->direction == GDK_SCROLL_DOWN ) {
        next_page = ( get_current_page() + 1 ) % get_n_pages();
        ret = true;
    }
    else if( event->direction == GDK_SCROLL_SMOOTH ) {
        constexpr double smooth_scroll_factor{ 4.0 };
        m_smooth_dy += smooth_scroll_factor * event->delta_y;
        if( m_smooth_dy < -1.0 ) {
            next_page = get_current_page() - 1; // If negative, the last page will be used.
            ret = true;
            m_smooth_dy = 0.0;
        }
        else if( m_smooth_dy > 1.0 ) {
            next_page = ( get_current_page() + 1 ) % get_n_pages();
            ret = true;
            m_smooth_dy = 0.0;
        }
    }

    if( ret ) {
        set_current_page( next_page );
    }
    m_sig_tab_scrolled.emit( event );

    return ret;
}



//
// タブのドラッグを開始
//
void DragableNoteBook::slot_drag_begin()
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::slot_drag_begin page = " << m_page << std::endl;
#endif

    CORE::DND_Begin();

    m_dragging_tab = true;
}


//
// タブをドラッグ中
//
// 矢印アイコンをタブの上に表示する
//
void DragableNoteBook::slot_drag_motion( const int page, const int tab_x, const int tab_y, const int tab_width )
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::slot_drag_motion page = " << page
              << " tab_x = " << tab_x << " tab_y = " << tab_y << " tab_w " << tab_width << std::endl;
#endif

    if( page < 0 || page == m_page ){
        if( m_down_arrow ) m_down_arrow->hide();
    }
    else if( m_dragging_tab ){

        if( ! m_down_arrow ) m_down_arrow = new SKELETON::IconPopup( ICON::DOWN );
        m_down_arrow->show();

        const int space = 4;
        int x, y;
        m_notebook_tab.get_window()->get_origin( x, y );

        x += tab_x - m_down_arrow->get_img_width()/2;
        y += tab_y - m_down_arrow->get_img_height() - space;
        if( page > m_page ) x += tab_width;

        m_down_arrow->move( x , y );
    }
}


//
// D&Dで受信側がデータ送信を要求してきた
//
void DragableNoteBook::slot_drag_data_get( Gtk::SelectionData& selection_data )
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::slot_drag_data_get target = " << selection_data.get_target()
              << " page = " << m_page << std::endl;
#endif

    // お気に入りがデータ送信を要求してきた
    if( selection_data.get_target() == DNDTARGET_FAVORITE ) m_sig_drag_data_get.emit( selection_data, m_page );

    // タブの入れ替え処理
    else if( selection_data.get_target() == DNDTARGET_TAB ){

        int page = m_notebook_tab.get_page_under_mouse();
        if( page >= m_notebook_tab.get_n_pages() ) page = m_notebook_tab.get_n_pages()-1;

        // ドラッグ前とページが変わっていたら入れ替え
        if( m_page != -1 && page != -1 && m_page != page ){

#ifdef _DEBUG
            std::cout << "reorder_chiled " << m_page << " -> " << page << std::endl;
#endif
            m_notebook_tab.reorder_child( m_page, page );
            m_notebook_tab.queue_draw();
            m_notebook_view.reorder_child( *m_notebook_view.get_nth_page( m_page ), page );
            m_page = -1;
        }

        if( m_down_arrow ) m_down_arrow->hide();
    }
}


//
// タブのドラッグを終了
//
void DragableNoteBook::slot_drag_end()
{
#ifdef _DEBUG
    std::cout << "DragableNoteBook::slot_drag_end\n";
#endif

    m_dragging_tab = false;

    if( m_down_arrow ) m_down_arrow->hide();

    CORE::DND_End();
}
