/*
 *  This file is part of RawTherapee.
 *
 *  RawTherapee is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  RawTherapee 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <thumbbrowserbase.h>
#include <glibmm.h>
#include <multilangmgr.h>
#include <options.h>
#include <mytime.h>

ThumbBrowserBase::ThumbBrowserBase () 
    : lastClicked(NULL), previewHeight(options.thumbSize) {
    inTabMode=false;  // corresponding to take thumbSize
    inW = -1; inH = -1;

    Gtk::HBox* hb1 = new Gtk::HBox ();
    Gtk::HBox* hb2 = new Gtk::HBox ();
    Gtk::Frame* frame = new Gtk::Frame ();
    frame->add (internal);
    frame->set_shadow_type (Gtk::SHADOW_IN );
    hb1->pack_start (*frame);
    hb1->pack_end (vscroll, Gtk::PACK_SHRINK, 0);

    pack_start (*hb1);

    hb2->pack_start (hscroll);

    pack_start (*hb2,Gtk::PACK_SHRINK, 0);

    internal.setParent (this);
    
    show_all ();
    
    hscroll.set_update_policy (Gtk::UPDATE_CONTINUOUS);
    vscroll.set_update_policy (Gtk::UPDATE_CONTINUOUS);

    vscroll.signal_value_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::scrollChanged) );
    hscroll.signal_value_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::scrollChanged) );

    internal.signal_size_allocate().connect( sigc::mem_fun(*this, &ThumbBrowserBase::internalAreaResized) );
    signal_style_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::styleChanged) );
}

void ThumbBrowserBase::scrollChanged () {
	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(entryMutex);
	#endif

    for (int i=0; i<fd.size(); i++)
        fd[i]->setOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value()));

    internal.setPosition ((int)(hscroll.get_value()), (int)(vscroll.get_value()));

    if (!internal.isDirty()) {    
        internal.setDirty ();
        internal.queue_draw ();
//        gdk_window_process_updates (get_window()->gobj(), true);
    }
}

void ThumbBrowserBase::scroll (int direction) {
    if (arrangement==TB_Vertical)
        vscroll.set_value (vscroll.get_value() + (direction==GDK_SCROLL_DOWN ? +1 : -1) * vscroll.get_adjustment()->get_step_increment());
    else
        hscroll.set_value (hscroll.get_value() + (direction==GDK_SCROLL_DOWN ? +1 : -1) * hscroll.get_adjustment()->get_step_increment());
}

void ThumbBrowserBase::scrollPage (int direction) {
    if (arrangement==TB_Vertical)
        vscroll.set_value (vscroll.get_value() + (direction==GDK_SCROLL_DOWN ? +1 : -1) * vscroll.get_adjustment()->get_page_increment());
    else
        hscroll.set_value (hscroll.get_value() + (direction==GDK_SCROLL_DOWN ? +1 : -1) * hscroll.get_adjustment()->get_page_increment());
}

void ThumbBrowserBase::resizeThumbnailArea (int w, int h) {

    inW = w;
    inH = h;

    if (hscroll.get_value() + internal.get_width() > inW)
        hscroll.set_value (inW - internal.get_width());
    if (vscroll.get_value() + internal.get_height() > inH)
        vscroll.set_value (inH - internal.get_height());

    configScrollBars ();
}

void ThumbBrowserBase::internalAreaResized (Gtk::Allocation& req) {

  if (inW>0 && inH>0) {
    configScrollBars ();
    redraw ();
  }
}

void ThumbBrowserBase::configScrollBars () {

    if (inW>0 && inH>0) {
  
        int iw = internal.get_width ();
        int ih = internal.get_height ();

        hscroll.get_adjustment()->set_upper (inW);
        vscroll.get_adjustment()->set_upper (inH);
        hscroll.get_adjustment()->set_lower (0);
        vscroll.get_adjustment()->set_lower (0);
        hscroll.get_adjustment()->set_step_increment (32);
        vscroll.get_adjustment()->set_step_increment (32);
        hscroll.get_adjustment()->set_page_increment (iw);
        vscroll.get_adjustment()->set_page_increment (ih);
        hscroll.get_adjustment()->set_page_size (iw);
        vscroll.get_adjustment()->set_page_size (ih);

        if(iw>=inW)
            hscroll.hide();
        else
            hscroll.show();

        if(ih>=inH)
            vscroll.hide();
        else
            vscroll.show();
    }
}

void ThumbBrowserBase::arrangeFiles () {
	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(entryMutex);
	#endif

    int N = fd.size ();
    // apply filter
    for (int i=0; i<N; i++) 
        fd[i]->filtered = !checkFilter (fd[i]);

    int rowHeight = 0;
    // compute size of the items
    for (int i=0; i<N; i++) 
        if (!fd[i]->filtered && fd[i]->getMinimalHeight() > rowHeight)
            rowHeight = fd[i]->getMinimalHeight ();
    
    if (arrangement==TB_Horizontal) {            

        int numOfRows = 1;
//        if (rowHeight>0) {
//            numOfRows = (internal.get_height()+rowHeight/2)/rowHeight;
//            if (numOfRows<1)
//                numOfRows = 1;
//        }

        int ct = 0;
        int currx = 0; int curry = 0;
        while (ct<N) {
            // find widest item in the column
            int maxw = 0;
            for (int i=0; ct+i<N && i<numOfRows; i++)
                if (fd[ct+i]->getMinimalWidth() > maxw)
                    maxw = fd[ct+i]->getMinimalWidth ();

            // arrange items in the column
            curry = 0;
            for (int i=0; ct<N && i<numOfRows; i++, ct++) {
                while (ct<N && fd[ct]->filtered) 
                    fd[ct++]->drawable = false;
                if (ct<N) {
                    fd[ct]->setPosition (currx, curry, maxw, rowHeight);
                    fd[ct]->drawable = true;
                    curry += rowHeight;
                }
            }
            currx += maxw;
        }
        resizeThumbnailArea (currx, numOfRows*rowHeight);
    }
    else {
        int availWidth = internal.get_width();
        // initial number of columns
        int numOfCols = 0;
        int currColNum = 0;
        int colsWidth = 0;
        for (int i=0; i<N; i++)
            if (!fd[i]->filtered && colsWidth + fd[i]->getMinimalWidth() <= availWidth) {
                colsWidth += fd[numOfCols]->getMinimalWidth ();
                numOfCols++;
            }
        if (numOfCols<1)
            numOfCols = 1;
        std::vector<int> colWidths;
        for (; numOfCols>0; numOfCols--) {
            // compute column widths
            colWidths.resize (numOfCols);
            for (int i=0; i<numOfCols; i++)
                colWidths[i] = 0;
            for (int i=0, j=0; i<N; i++) {
                if (!fd[i]->filtered && fd[i]->getMinimalWidth() > colWidths[j%numOfCols])
                    colWidths[j%numOfCols] = fd[i]->getMinimalWidth ();
                if (!fd[i]->filtered)
                    j++;
            }
            // if not wider than the space available, arrange it and we are ready
            colsWidth = 0;
            for (int i=0; i<numOfCols; i++)
                colsWidth += colWidths[i];
            if (numOfCols==1 || colsWidth < availWidth)
                break;
        }
        // arrange files
        int ct = 0;
        int currx = 0; int curry = 0;
        while (ct<N) {
            // arrange items in the row
            currx = 0;
            for (int i=0; ct<N && i<numOfCols; i++, ct++) {
                while (ct<N && fd[ct]->filtered) 
                    fd[ct++]->drawable = false;
                if (ct<N) {
                    fd[ct]->setPosition (currx, curry, colWidths[i%numOfCols], rowHeight);
                    fd[ct]->drawable = true;
                    currx += colWidths[i%numOfCols];
                }
            }
            if (currx>0) // there were thumbnails placed in the row
                curry += rowHeight;
        }
        resizeThumbnailArea (colsWidth, curry);
    }
}


void ThumbBrowserBase::Internal::on_realize()
{
  Cairo::FontOptions cfo;
  cfo.set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
  get_pango_context()->set_cairo_font_options (cfo);

  Gtk::DrawingArea::on_realize();
  Glib::RefPtr<Gdk::Window> window = get_window();
  set_flags (Gtk::CAN_FOCUS);
  add_events(Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK);
  gc_ = Gdk::GC::create(window);
  set_has_tooltip (true);
  signal_query_tooltip().connect( sigc::mem_fun(*this, &ThumbBrowserBase::Internal::on_query_tooltip) );
}

bool ThumbBrowserBase::Internal::on_query_tooltip (int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip) {
	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(parent->entryMutex);
	#endif

    Glib::ustring ttip = "";
    for (int i=0; i<parent->fd.size(); i++) 
        if (parent->fd[i]->drawable && parent->fd[i]->inside (x, y)) {
            ttip = parent->fd[i]->getToolTip (x, y);
            break;
        }
    if (ttip!="") {
        tooltip->set_text (ttip);
        return true;
    }
    else
        return false;
}

void ThumbBrowserBase::styleChanged (const Glib::RefPtr<Gtk::Style>& style) {

  refreshThumbImages ();
}

ThumbBrowserBase::Internal::Internal () : ofsX(0), ofsY(0), parent(NULL), dirty(true) {
}

void ThumbBrowserBase::Internal::setParent (ThumbBrowserBase* p) {
    parent = p;
}

void ThumbBrowserBase::Internal::setPosition (int x, int y) {
    ofsX = x;
    ofsY = y;
}

bool ThumbBrowserBase::Internal::on_key_press_event (GdkEventKey* event) {
    return parent->keyPressed (event);
}

bool ThumbBrowserBase::Internal::on_button_press_event (GdkEventButton* event) {
    grab_focus ();

    parent->eventTime = event->time;

    parent->buttonPressed ((int)event->x, (int)event->y, event->button, event->type, event->state, 0, 0, get_width(), get_height());
    Glib::RefPtr<Gdk::Window> window = get_window();

    GdkRectangle rect;
    rect.x = 0;
    rect.y = 0; 
    window->get_size (rect.width, rect.height);

    gdk_window_invalidate_rect (window->gobj(), &rect, true);
    gdk_window_process_updates (window->gobj(), true);

    return true;
}

void ThumbBrowserBase::buttonPressed (int x, int y, int button, GdkEventType type, int state, int clx, int cly, int clw, int clh) {
    ThumbBrowserEntryBase* fileDescr = NULL;
    bool handled = false;
	
	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(entryMutex);
	#endif

    for (int i=0; i<fd.size(); i++) 
        if (fd[i]->drawable) {
            if (fd[i]->inside (x, y) && fd[i]->insideWindow (clx, cly, clw, clh))            
                fileDescr = fd[i];
            bool b = fd[i]->pressNotify (button, type, state, x, y);
            handled = handled || b;
        }   
        
    if (handled || (fileDescr && fileDescr->processing))
        return;
        
    if (selected.size()==1 && type==GDK_2BUTTON_PRESS && button==1) 
        doubleClicked (selected[0]);
    else if (button==1 && type==GDK_BUTTON_PRESS) {
        if (fileDescr && state & GDK_SHIFT_MASK) {
            if (selected.size()==0) {
                selected.push_back (fileDescr);
                fileDescr->selected = true;
                lastClicked = fileDescr;
                selectionChanged ();
            }
            else {
                // find the start and the end of the selection interval
                int startx = fd.size()-1;
                if (lastClicked) {
                    for (; startx>=0; startx--)
                        if (fd[startx]==lastClicked) 
                            break;
                }
                else {
                    for (; startx>=0; startx--)
                        if (fd[startx]==selected[0]) 
                            break;
                }
                int endx = 0;
                for (; endx<fd.size(); endx++)
                    if (fd[endx]==fileDescr) 
                        break;
                if (endx < startx) {
                    int tmp = endx;
                    endx = startx;
                    startx = tmp;
                }
                // clear current selection
                for (int i=0; i<selected.size(); i++)
                    selected[i]->selected = false;
                selected.clear ();
                // select thumbnails in the interval
                for (int i=startx; i<=endx; i++) {
                    if (!fd[i]->filtered) {
                        fd[i]->selected = true;
                        selected.push_back (fd[i]);
                    }
                }
                selectionChanged ();
            }
        }
        else if (fileDescr && state & GDK_CONTROL_MASK) {
            std::vector<ThumbBrowserEntryBase*>::iterator i = std::find (selected.begin(), selected.end(), fileDescr);
            if (i!=selected.end()) {
                (*i)->selected = false;
                selected.erase (i);
            }
            else {
                selected.push_back (fileDescr);
                fileDescr->selected = true;
            }
            lastClicked = fileDescr;
            selectionChanged ();
        }
        else {
            for (int i=0; i<selected.size(); i++)
                selected[i]->selected = false;
            selected.clear ();
            if (fileDescr) {
                selected.push_back (fileDescr);
                fileDescr->selected = true;
            }
            lastClicked = fileDescr;
            selectionChanged ();
        }
    }
    else if (fileDescr && button==3 && type==GDK_BUTTON_PRESS) {
        if (!fileDescr->selected) {
            for (int i=0; i<selected.size(); i++)
                selected[i]->selected = false;
            selected.clear ();
            fileDescr->selected = true;
            selected.push_back (fileDescr);
            lastClicked = fileDescr;
            selectionChanged ();
        }
        rightClicked (fileDescr);
    }
}

bool ThumbBrowserBase::Internal::on_expose_event(GdkEventExpose* event) {

    dirty = false;

    Glib::RefPtr<Gdk::Window> window = get_window();

	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(parent->entryMutex);
	#endif

    int w = get_width();
    int h = get_height();

    window->clear();
    // draw thumbnails
    Glib::RefPtr<Pango::Context> context = get_pango_context ();
    context->set_font_description (get_style()->get_font());
    for (int i=0; i<parent->fd.size(); i++) {
        if (!parent->fd[i]->drawable || !parent->fd[i]->insideWindow (0, 0, w, h))
            parent->fd[i]->updatepriority = false;
        else {
            parent->fd[i]->updatepriority = true;
            parent->fd[i]->draw ();
        }
    }

    return true;
}

bool ThumbBrowserBase::Internal::on_button_release_event (GdkEventButton* event) {

	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(parent->entryMutex);
	#endif

    int w = get_width();
    int h = get_height();

    for (int i=0; i<parent->fd.size(); i++) 
        if (parent->fd[i]->drawable && parent->fd[i]->insideWindow (0, 0, w, h)) 
            parent->fd[i]->releaseNotify (event->button, event->type, event->state, (int)event->x, (int)event->y);
    return true;
}

bool ThumbBrowserBase::Internal::on_motion_notify_event (GdkEventMotion* event) {
	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(parent->entryMutex);
	#endif

    int w = get_width();
    int h = get_height();

    for (int i=0; i<parent->fd.size(); i++) 
        if (parent->fd[i]->drawable && parent->fd[i]->insideWindow (0, 0, w, h)) 
            parent->fd[i]->motionNotify ((int)event->x, (int)event->y);
    return true;
}

bool ThumbBrowserBase::Internal::on_scroll_event (GdkEventScroll* event) {

    parent->scroll (event->direction);
    return true;
}


void ThumbBrowserBase::redraw () {

    arrangeFiles ();
    queue_draw ();
}

void ThumbBrowserBase::zoomChanged (bool zoomIn) {

    int newHeight;
    int i=0;
    int optThumbSize=getCurrentThumbSize();
    if (zoomIn)
        for (i=0; i<options.thumbnailZoomRatios.size(); i++) {
            newHeight = (int)(options.thumbnailZoomRatios[i] * options.maxThumbnailHeight);
            if (newHeight > optThumbSize)
                break;
        }
    else
        for (i=options.thumbnailZoomRatios.size()-1; i>=0; i--) {
            newHeight = (int)(options.thumbnailZoomRatios[i] * options.maxThumbnailHeight);
            if (newHeight < optThumbSize)
                break;
        }
    previewHeight = newHeight;
    if (inTabMode) options.thumbSizeTab = newHeight; else options.thumbSize = newHeight;

	{
		// TODO: Check for Linux
		#ifdef WIN32
		Glib::Mutex::Lock lock(entryMutex);
		#endif

		for (int i=0; i<fd.size(); i++) fd[i]->resize (previewHeight);
	}

    redraw ();
#ifdef _WIN32
    gdk_window_process_updates (get_window()->gobj(), true);
#endif    
}

int ThumbBrowserBase::getCurrentThumbSize() { return inTabMode ? options.thumbSizeTab : options.thumbSize; }

void ThumbBrowserBase::refreshThumbImages () {
	{
		// TODO: Check for Linux
		#ifdef WIN32
		Glib::Mutex::Lock lock(entryMutex);
		#endif

    for (int i=0; i<fd.size(); i++){
    	previewHeight = getCurrentThumbSize();
    	fd[i]->resize (previewHeight);// TODO!!! Might be performance bottleneck
		/* called if necessary by resize()
        fd[i]->refreshThumbnailImage ();  TODO: This might cause crashes on some installations */
    }
	}

    redraw ();
}

void ThumbBrowserBase::refreshQuickThumbImages () {
	// TODO: Check for Linux
	#ifdef WIN32
	Glib::Mutex::Lock lock(entryMutex);
	#endif

    for (int i=0; i<fd.size(); ++i){
		fd[i]->refreshQuickThumbnailImage ();
    }
}

void ThumbBrowserBase::refreshEditedState (const std::set<Glib::ustring>& efiles) {

    editedFiles = efiles;
	{
		// TODO: Check for Linux
		#ifdef WIN32
		Glib::Mutex::Lock lock(entryMutex);
		#endif

    for (int i=0; i<fd.size(); i++)
        fd[i]->framed = editedFiles.find (fd[i]->filename)!=editedFiles.end();
	}

    queue_draw ();
}
    
void ThumbBrowserBase::setArrangement (Arrangement a) {

    arrangement = a;    
    redraw ();
}

void ThumbBrowserBase::enableTabMode(bool enable) {
    inTabMode = enable;
    arrangement = inTabMode ? ThumbBrowserBase::TB_Horizontal : ThumbBrowserBase::TB_Vertical;
    
    if (options.thumbSizeTab!=options.thumbSize) {
		// TODO: Check for Linux
		#ifdef WIN32
		Glib::Mutex::Lock lock(entryMutex);
		#endif

        for (int i=0; i<fd.size(); i++) 
            fd[i]->resize (getCurrentThumbSize());
    }

    redraw ();

    // Scroll to selected position if going into ribbon mode or back
    // Tab mode is horizontal, file browser is vertical
    if (!selected.empty()) {
        if (inTabMode) {
            int h=selected[0]->getStartX();
            hscroll.set_value (MIN(h, hscroll.get_adjustment()->get_upper()));
        } else {
            int v=selected[0]->getStartY();
            vscroll.set_value (MIN(v, vscroll.get_adjustment()->get_upper()));
        }
    }

 
}

void ThumbBrowserBase::initEntry (ThumbBrowserEntryBase* entry) {
    entry->setOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value()));
}

void ThumbBrowserBase::getScrollPosition (double& h, double& v) {
    h = hscroll.get_value ();
    v = vscroll.get_value ();
}

void ThumbBrowserBase::setScrollPosition (double h, double v) {
    hscroll.set_value (h>hscroll.get_adjustment()->get_upper() ? hscroll.get_adjustment()->get_upper() : h);
    vscroll.set_value (v>vscroll.get_adjustment()->get_upper() ? vscroll.get_adjustment()->get_upper() : v);
}

// needed for auto-height in single tab
int ThumbBrowserBase::getEffectiveHeight() { 
    int h=0;
	{
		// TODO: Check for Linux
		#ifdef WIN32
		Glib::Mutex::Lock lock(entryMutex);
		#endif

    if (fd.size()>0) h=fd[0]->getEffectiveHeight();
	}

    return h;
}  



