/* AbiWord
 * Copyright (c) 2003 Martin Sevior <msevior@physics.unimelb.edu.au>
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "fv_Selection.h"
#include "fl_DocLayout.h"
#include "pd_Document.h"
#include "ut_units.h"
#include "fl_BlockLayout.h"
#include "fp_Line.h"
#include "fp_Run.h"
#include "pf_Frag.h"
#include "pf_Frag_Strux.h"
#include "fl_TableLayout.h"
#include "pd_Document.h"
#include "ie_exp.h"
#include "ie_exp_RTF.h"
#include "fl_TOCLayout.h"
#include "ie_imp.h"
#include "ie_imp_RTF.h"

#include "ut_bytebuf.h"
#include "fv_View.h"

////////////////////////////////////////////////////////////////////////////////
/////		CONSTRUCTOR AND DESTRUCTOR					     				////
////////////////////////////////////////////////////////////////////////////////

FV_Selection::FV_Selection (FV_View * pView)
	: m_pView (pView), 
	  m_iSelectionMode(FV_SelectionMode_NONE),
	  m_iPrevSelectionMode(FV_SelectionMode_NONE),
	  m_iSelectAnchor(0),
	  m_iSelectLeftAnchor(0),
	  m_iSelectRightAnchor(0),
	  m_pSelectedTOC(NULL),
	  m_bSelectAll(false),
	  m_iLeftTableRect(-1),
	  m_iRightTableRect(-1),
	  m_iTopTableRect(-1),
	  m_iBottomTableRect(-1)
{
	UT_ASSERT (pView);
	m_vecSelRanges.clear();
}

FV_Selection::~FV_Selection()
{
	m_pSelectedTOC = NULL;
	UT_VECTOR_PURGEALL(PD_DocumentRange *,m_vecSelRanges);
}




////////////////////////////////////////////////////////////////////////////////
/////		ANCHOR METHODS          					     				////
////////////////////////////////////////////////////////////////////////////////

/*!
 *	Sets the selection anchor.
 */
void FV_Selection::setSelectionAnchor(PT_DocPosition pos)
{
	 m_iSelectAnchor = pos;
	 checkSelectAll();
}


/*
 *	Sets the left selection anchor to the given DocPosition
 */
void FV_Selection::setSelectionLeftAnchor(PT_DocPosition pos)
{
    if(pos == 0) return;
	m_iSelectLeftAnchor = pos;
	_checkSelectAll(m_iSelectLeftAnchor, m_iSelectRightAnchor);
}


/*!
 *	Sets the right selection anchor to the given DocPosition
 */
void FV_Selection::setSelectionRightAnchor(PT_DocPosition pos)
{
    if(pos == 0) return;
	m_iSelectRightAnchor = pos;
	_checkSelectAll(m_iSelectLeftAnchor, m_iSelectRightAnchor);
}


/*!
 *	Returns the selection anchor.
 */
PT_DocPosition FV_Selection::getSelectionAnchor(void) const
{
	if((m_iSelectionMode < FV_SelectionMode_Multiple) ||  (m_vecSelRanges.getItemCount() == 0))
		return m_iSelectAnchor;
	
	PD_DocumentRange * pDocRange = m_vecSelRanges.getNthItem(0);
	return pDocRange->m_pos1;
}


/*!
 *	Return the most left selection anchor.
 */
PT_DocPosition FV_Selection::getSelectionLeftAnchor(void) const
{
	// Only one range in the selection
	if((m_iSelectionMode < FV_SelectionMode_Multiple) || (m_vecSelRanges.getItemCount() == 0))
		return m_iSelectLeftAnchor;

	// Multiple ranges selected
	PD_DocumentRange * pDocRange = m_vecSelRanges.getNthItem(0);
	return pDocRange->m_pos1;
}


/*!
 *	Return the most right selection anchor.
 */
PT_DocPosition FV_Selection::getSelectionRightAnchor(void) const
{
	if((m_iSelectionMode < FV_SelectionMode_Multiple) || (m_vecSelRanges.getItemCount() == 0) )
		return m_iSelectRightAnchor;
	
	PD_DocumentRange * pDocRange = m_vecSelRanges.getNthItem(0);
	return pDocRange->m_pos2;
}




////////////////////////////////////////////////////////////////////////////////
/////		TABLE METHODS           					     				////
////////////////////////////////////////////////////////////////////////////////


/*! 
 *	 Return the current attach values of the table selection rectangle 
 *  \return False if none were set yet, else true.
 */
bool FV_Selection::getRectTableSel
(UT_sint32* left, UT_sint32* right, UT_sint32* top, UT_sint32* bottom)
{
    // if one isn't set they all aren't
    /*if( m_iLeftTableRect < 0 )
        return false;*/
        
    *left = m_iLeftTableRect;
	*right = m_iRightTableRect;
	*top = m_iTopTableRect;
	*bottom = m_iBottomTableRect;	
	return true;                               
}


/*! 
 * 	Set the attach values of the table selection rectangle
 *  This won't add cells to a selection properly, one should use
 *	addCellToSelection(..) for that.
 */
void FV_Selection::setRectTableSel 
(UT_sint32 left, UT_sint32 right, UT_sint32 top, UT_sint32 bottom)
{
    m_iLeftTableRect = left;
	m_iRightTableRect = right;
	m_iTopTableRect = top;
	m_iBottomTableRect = bottom;
}


/*! 
 *	Return true if only a single row is selected 
 */
bool FV_Selection::isSingleTableRowSelected(void) const
{
    return ( m_iTopTableRect == m_iBottomTableRect-1);
}


/*! 
 *	Return true if only a single column is selected 
 */
bool FV_Selection::isSingleTableColumnSelected(void) const
{
    return ( m_iLeftTableRect == m_iRightTableRect-1);
}


/*!
 * Returns a vector of the selected ranges. This will result in a range for each
 * row selected.
 */
 bool FV_Selection::getTableSelAsRangesVector(std::vector<PD_DocumentRange>& ranges){

	
	// check to see if we are in a table
	if( getSelectionMode() != FV_SelectionMode_InTable ) return false;
	if( m_iLeftTableRect < 0 )	return false;
	if( ! m_pView->isInTable(getSelectionAnchor()) ) return false; 
	
	// get table strux
	fl_TableLayout* tableLayout = m_pView->getTableAtPos(getSelectionAnchor());
	fp_TableContainer* table = static_cast<fp_TableContainer *>(tableLayout->getFirstContainer());
	 
	//
	// Run through rows and make 1 PD_DocumentRange for each row
	//
	PL_StruxDocHandle sdh, sdhEnd;
	fl_CellLayout* tmpCell;
	for( int i= m_iTopTableRect; i<m_iBottomTableRect; ++i)
	{	
	 	PT_DocPosition pos1, pos2;
		
		// Start point, first cell left
		tmpCell = static_cast<fl_CellLayout *>(table->getCellAtRowColumn(i,m_iLeftTableRect)->getSectionLayout());
		sdh = tmpCell->getStruxDocHandle();
		pos1 = getDoc()->getStruxPosition(sdh);
		
		// End point
	    tmpCell = static_cast<fl_CellLayout *>(table->getCellAtRowColumn(i,m_iRightTableRect-1)->getSectionLayout());
		sdh = tmpCell->getStruxDocHandle();
		sdhEnd = NULL;
		getDoc()->getNextStruxOfType(sdh,PTX_EndCell,&sdhEnd);
		pos2 = getDoc()->getStruxPosition(sdhEnd) + 1;	
		// VERY VERY IMPORTANT +1 !!!! -> -> -> -> -^
		// Dzan - GSoC if left out we will never select the cellend strux and
		// the exporter will produce wrong => importer will freak

		UT_DEBUGMSG(("\nCellAtRowColumn %d , %d  en %d , %d resulteert in pos1: %d en pos2: %d",i,m_iLeftTableRect,i,m_iRightTableRect,pos1,pos2));
		
		// adding to vector
		PD_DocumentRange linerange(getDoc(), pos1, pos2);
		ranges.push_back(linerange);		
	}
	 
	return true;
}


/*!
 * Remove a cell from the selection. This is the inverse of the addCellToSelection.
 * \return True if cell was selected, false if it wasn't.
 */
bool FV_Selection::removeCellFromSelection(fl_CellLayout* pCell){
    
    UT_ASSERT(m_iSelectionMode == FV_SelectionMode_InTable);
			  
    // seeking a DocPoint inside our cell
	PL_StruxDocHandle sdhCell = pCell->getStruxDocHandle();
	PT_DocPosition posInCell = getDoc()->getStruxPosition(sdhCell)+1;
	
	// iterating the ranges vector checking if it was in it
	UT_sint32 i = 0;
	PD_DocumentRange* pDocRange = NULL;
	bool bFound = false;
	
	while( !bFound && i < m_vecSelRanges.getItemCount() ){
	    pDocRange = m_vecSelRanges.getNthItem(i);  
	    if ((posInCell >= pDocRange->m_pos1) && (posInCell <= pDocRange->m_pos2+1))
	        bFound = true;  
	    ++i;
	}
	
	// removing the range if it was in it, otherwise return false
	if( bFound ){
	    m_vecSelRanges.deleteNthItem(i-1); // if found our 'i' will be 1 to large
	    delete pDocRange;
	} else return false;

}


/*!
 * Add a cell to the list of selected regions.
 */
void FV_Selection::addCellToSelection(fl_CellLayout * pCell)
{
	UT_ASSERT(m_iSelectionMode == FV_SelectionMode_InTable);
	
	// getting beginning and end Document Position of the cell	  
	PL_StruxDocHandle sdhEnd = NULL;
	PL_StruxDocHandle sdhStart = pCell->getStruxDocHandle();
	PT_DocPosition posLow = getDoc()->getStruxPosition(sdhStart) +1; // First block
	bool bres = getDoc()->getNextStruxOfType(sdhStart,PTX_EndCell,&sdhEnd);
	PT_DocPosition posHigh = getDoc()->getStruxPosition(sdhEnd) -1;

	// creating and adding a DocumentRange based on the DocPosition's
	UT_ASSERT(bres && sdhEnd);
	PD_DocumentRange * pDocRange = new PD_DocumentRange(getDoc(),posLow,posHigh);
	m_vecSelRanges.addItem(pDocRange);
}




////////////////////////////////////////////////////////////////////////////////
/////		OTHER METHODS           					     				////
////////////////////////////////////////////////////////////////////////////////

/*!
 *	Checks to see if the whole document is selected. If it is m_bSelectAll is set.
 */
void  FV_Selection::checkSelectAll(void)
{
	 fl_SectionLayout * pSL = m_pView->m_pLayout->getLastSection();
	
	 if(pSL == NULL) 								return;
	 if(m_pView->m_pDoc->isPieceTableChanging()) 	return;
	 if(m_pView->m_pLayout->isLayoutFilling()) 		return;
	
	 PT_DocPosition posLow = m_iSelectAnchor;
	 PT_DocPosition posHigh = m_pView->getPoint();
	 if(posHigh < posLow)
	 {
	      posHigh = m_iSelectAnchor;
	      posLow = m_pView->getPoint();
	 }

	_checkSelectAll(posLow, posHigh);
}


/*!
 *	Checks to see if entire document is selected using the given anchors.
 */
void FV_Selection::_checkSelectAll(PT_DocPosition low, PT_DocPosition high){
	 PT_DocPosition posBeg,posEnd=0;
	 m_pView->getEditableBounds(false,posBeg);
	 m_pView->getEditableBounds(true,posEnd);
	
	 bool bSelAll = ((low <= posBeg) && (high >= posEnd));
	 _setSelectAll(bSelAll);	
}


/*!
 *	Simple wrapper for setting the SelectAll boolean.
 */
void  FV_Selection::_setSelectAll(bool bSelectAll)
{
    xxx_UT_DEBUGMSG(("Select All = %d \n",bSelectAll));
	m_bSelectAll = bSelectAll;	      
}


/*!
 *	We set the SelectionMode to the requested one. 
 *  Dzan - GSoC: Maybe think about doing this automaticly when selection changes.
 *				 Scan where the cursor is, get the strux and check the type...?
 */
void FV_Selection::setMode(FV_SelectionMode iSelMode)
{
    // WE SHOULD NO LONGER USE THE ROW AND COL MODES, ALL SHOULD BE IN_TABLE
    if( iSelMode == FV_SelectionMode_TableColumn || iSelMode == FV_SelectionMode_TableRow )
        UT_DEBUGMSG(("USING A DEPRACATED SELECTION MODE USED!!! FIX THIS!!!"));
    
    // Save the current mode to the previousmode
	if( (m_iSelectionMode != FV_SelectionMode_NONE) && (iSelMode !=  FV_SelectionMode_NONE))
		m_iPrevSelectionMode = m_iSelectionMode;
	
	// Deal with table of content selections
	if((m_iSelectionMode == FV_SelectionMode_TOC) && (m_iSelectionMode != iSelMode))
	{
		if(m_pSelectedTOC) m_pSelectedTOC->setSelected(false);
		m_pSelectedTOC = NULL;
	}

	// If mode set to NONE, no selection is made! All needs to be reset
	if(m_iSelectionMode == FV_SelectionMode_NONE)
	{
		UT_VECTOR_PURGEALL(PD_DocumentRange *,m_vecSelRanges);
		m_vecSelRanges.clear();
		m_iLeftTableRect = -1;
	    m_iRightTableRect = -1;
	    m_iTopTableRect = -1;
	    m_iBottomTableRect = -1;
	}

	// SET THE CURRENT SELECTION MODE AND UNSET SELECTALL
	m_iSelectionMode = iSelMode;
	_setSelectAll(false);
}


/*!
 *	Sets some stuff when a Table of Content is selected.
 *  DZan - GSoC: Maybe this doesn't belong here but in the TOC class..?
 */
void FV_Selection::setTOCSelected(fl_TOCLayout * pTOCL)
{
	UT_return_if_fail(pTOCL);
	setMode(FV_SelectionMode_TOC);
	m_pSelectedTOC = pTOCL;
	m_iSelectAnchor = pTOCL->getPosition();
	pTOCL->setSelected(true);
	_setSelectAll(false);
}


/*!
 *	Important method! This is used by the drawing classes to determine if something
 *  should be drawn as being selected or not!
 */
bool FV_Selection::isPosSelected(PT_DocPosition pos) const
{
	if(m_iSelectionMode == FV_SelectionMode_NONE)
	{
		return false;
	}
	if( m_iSelectionMode == FV_SelectionMode_InTable ){
	    //
	    // This is the vector/range-way of testing
		//
		
	    /*UT_sint32 i =0;
	    PD_DocumentRange * pDocRange = NULL;
	    for(i=0; i < m_vecSelRanges.getItemCount(); i++){
	        pDocRange = m_vecSelRanges.getNthItem(i);           
	        if ((pos >= pDocRange->m_pos1) && (pos <= pDocRange->m_pos2+1)) 
	            return true;
	    }
	    // no cell found around the pos
	    return false;*/

		//
		// I'm going to use the attach values for now to check, seems faster
		//
		
	    // if one rect border isn't set yet they all aren't
		if( m_iLeftTableRect < 0 || !m_pView->isInTable(pos) )
		    return false;
		  
		// make sure we are in the same table the anchor is in
		if( m_pView->getTableAtPos(getSelectionAnchor()) != 
		    m_pView->getTableAtPos(pos) )
		    return false;
		
	    UT_sint32 iLeft, iRight, iTop, iBot;
	    m_pView->getCellParams(pos,&iLeft,&iRight,&iTop,&iBot);
		
		// see if our cell is in the selection
	    if( iLeft >= m_iLeftTableRect &&
	        iRight <= m_iRightTableRect &&
	        iTop >= m_iTopTableRect &&
	        iBot <= m_iBottomTableRect )
	        return true;
	        
	    else
	        return false;
	}
	else if(m_iSelectionMode < FV_SelectionMode_Multiple)
	{
		if(m_iSelectAnchor == m_pView->getPoint())
		{
			return false;
		}
		xxx_UT_DEBUGMSG(("m_iSelectAnchor %d \n",m_iSelectAnchor));
		PT_DocPosition posLow = m_iSelectAnchor;
		PT_DocPosition posHigh = m_pView->getPoint();
		if(posHigh < posLow)
		{
			posHigh = m_iSelectAnchor;
			posLow = m_pView->getPoint();
		}
		return ((pos >= posLow) && (pos <=posHigh));
	}

	//
	// Dzan - Should look into this, all should be replaced by this part:
	//
	/*UT_sint32 i =0;
	for(i=0; i < m_vecSelRanges.getItemCount(); i++)
	{
		PD_DocumentRange * pDocRange = m_vecSelRanges.getNthItem(i);
		xxx_UT_DEBUGMSG(("Looking at pos %d low %d high %d \n",pos, pDocRange->m_pos1,pDocRange->m_pos2 ));
		if ((pos >= pDocRange->m_pos1) && (pos <= pDocRange->m_pos2+1))
		{
			return true;
		}
	}*/
	return false;
		
}


/*! 
 *	Returns true if a selection is made at the current moment.
 */
bool FV_Selection::isSelected(void) const
{
	return FV_SelectionMode_NONE != m_iSelectionMode;
}


/*!
 *	Clears the selections.
 */
void FV_Selection::clearSelection(void)
{
	setMode(FV_SelectionMode_NONE);
	_setSelectAll(false);
}


/*!
 * Add a range to the list of selected regions as defined by posLow, posHigh.
 * If bAddData is true also make a copy of the selected text in RTF format.
 */
void FV_Selection::addSelectedRange(PT_DocPosition /*posLow*/, PT_DocPosition /*posHigh*/, bool /*bAddData*/)
{

}


/*!
 *	Return the ith selection.
 */
PD_DocumentRange * FV_Selection::getNthSelection(UT_sint32 i) const
{
	if(i >= getNumSelections())
	{
		return NULL;
	}
	PD_DocumentRange * pDocRange = m_vecSelRanges.getNthItem(i);
	return pDocRange;
}


/*!
 *	Return the number of active selections.
 */
UT_sint32 FV_Selection::getNumSelections(void) const
{
	return m_vecSelRanges.getItemCount();
}


/*!
 *	Return the PT_Document to which the selection belongs.
 */
PD_Document * FV_Selection::getDoc(void) const
{
	return m_pView->getDocument();
}


/*!
 *	Return the FL_DocLayout to which the selection belongs.
 */
FL_DocLayout * FV_Selection::getLayout(void) const
{
	return m_pView->getLayout();
}