/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_extensions.hxx"
#include <grid.hrc>
#include <cstdio>
#include <math.h> // for M_LN10 and M_E

#define _USE_MATH_DEFINES
#include <cmath>
#undef _USE_MATH_DEFINES

#include <grid.hxx>

// for ::std::sort
#include <algorithm>

ResId SaneResId( sal_uInt32 );

/***********************************************************************
 *
 *	GridWindow
 *
 ***********************************************************************/

// ---------------------------------------------------------------------

GridWindow::GridWindow(double* pXValues, double* pYValues, int nValues, Window* pParent, sal_Bool bCutValues ) 
:	ModalDialog( pParent, SaneResId( GRID_DIALOG ) ),
	m_aGridArea( 50, 15, 100, 100 ),
	m_pXValues( pXValues ),
	m_pOrigYValues( pYValues ),
	m_nValues( nValues ),
	m_pNewYValues( NULL ),
	m_bCutValues( bCutValues ),
	m_aHandles(),
	m_nDragIndex( 0xffffffff ),
	m_aMarkerBitmap( Bitmap( SaneResId( GRID_DIALOG_HANDLE_BMP ) ), Color( 255, 255, 255 ) ),
	m_aOKButton( this, SaneResId( GRID_DIALOG_OK_BTN ) ),
	m_aCancelButton( this, SaneResId( GRID_DIALOG_CANCEL_BTN ) ),
	m_aResetTypeBox( this, SaneResId( GRID_DIALOG_TYPE_BOX ) ),
	m_aResetButton( this, SaneResId( GRID_DIALOG_RESET_BTN ) )
{
	sal_uInt16 nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_LINEAR_ASCENDING ) ) );
	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_LINEAR_ASCENDING );

	nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_LINEAR_DESCENDING ) ) );
	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_LINEAR_DESCENDING );

	nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_RESET ) ) );
	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_RESET );

	nPos = m_aResetTypeBox.InsertEntry( String( SaneResId( RESET_TYPE_EXPONENTIAL ) ) );
	m_aResetTypeBox.SetEntryData( nPos, (void *)RESET_TYPE_EXPONENTIAL );

	m_aResetTypeBox.SelectEntryPos( 0 );

	m_aResetButton.SetClickHdl( LINK( this, GridWindow, ClickButtonHdl ) );

	SetMapMode( MapMode( MAP_PIXEL ) );
	Size aSize = GetOutputSizePixel();
	Size aBtnSize = m_aOKButton.GetOutputSizePixel();
	m_aGridArea.setWidth( aSize.Width() - aBtnSize.Width() - 80 );
	m_aGridArea.setHeight( aSize.Height() - 40 );

	if( m_pOrigYValues && m_nValues )
	{
		m_pNewYValues = new double[ m_nValues ];
		memcpy( m_pNewYValues, m_pOrigYValues, sizeof( double ) * m_nValues );
	}

	setBoundings( 0, 0, 1023, 1023 );
	computeExtremes();

	// create left and right marker as first and last entry
	m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1);
	m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1);
	m_aHandles.push_back(impHandle(transform(findMinX(), findMinY()), m_BmOffX, m_BmOffY));
	m_aHandles.push_back(impHandle(transform(findMaxX(), findMaxY()), m_BmOffX, m_BmOffY));

	FreeResource();
}

// ---------------------------------------------------------------------

GridWindow::~GridWindow()
{
	if( m_pNewYValues )
		delete [] m_pNewYValues;
}

// ---------------------------------------------------------------------

double GridWindow::findMinX()
{
	if( ! m_pXValues )
		return 0.0;
	double fMin = m_pXValues[0];
	for( int i = 1; i < m_nValues; i++ )
		if( m_pXValues[ i ] < fMin )
			fMin = m_pXValues[ i ];
	return fMin;
}

// ---------------------------------------------------------------------

double GridWindow::findMinY()
{
	if( ! m_pNewYValues )
		return 0.0;
	double fMin = m_pNewYValues[0];
	for( int i = 1; i < m_nValues; i++ )
		if( m_pNewYValues[ i ] < fMin )
			fMin = m_pNewYValues[ i ];
	return fMin;
}

// ---------------------------------------------------------------------

double GridWindow::findMaxX()
{
	if( ! m_pXValues )
		return 0.0;
	double fMax = m_pXValues[0];
	for( int i = 1; i < m_nValues; i++ )
		if( m_pXValues[ i ] > fMax )
			fMax = m_pXValues[ i ];
	return fMax;
}

// ---------------------------------------------------------------------

double GridWindow::findMaxY()
{
	if( ! m_pNewYValues )
		return 0.0;
	double fMax = m_pNewYValues[0];
	for( int i = 1; i < m_nValues; i++ )
		if( m_pNewYValues[ i ] > fMax )
			fMax = m_pNewYValues[ i ];
	return fMax;
}

// ---------------------------------------------------------------------

void GridWindow::computeExtremes()
{
	if( m_nValues && m_pXValues && m_pOrigYValues )
	{
		m_fMaxX = m_fMinX = m_pXValues[0];
		m_fMaxY = m_fMinY = m_pOrigYValues[0];
		for( int i = 1; i < m_nValues; i++ )
		{
			if( m_pXValues[ i ] > m_fMaxX )
				m_fMaxX = m_pXValues[ i ];
			else if( m_pXValues[ i ] < m_fMinX )
				m_fMinX = m_pXValues[ i ];
			if( m_pOrigYValues[ i ] > m_fMaxY )
				m_fMaxY = m_pOrigYValues[ i ];
			else if( m_pOrigYValues[ i ] < m_fMinY )
				m_fMinY = m_pOrigYValues[ i ];
		}
		setBoundings( m_fMinX, m_fMinY, m_fMaxX, m_fMaxY );
	}
}

// ---------------------------------------------------------------------

Point GridWindow::transform( double x, double y )
{
	Point aRet;

	aRet.X() = (long)( ( x - m_fMinX ) *
		(double)m_aGridArea.GetWidth() / ( m_fMaxX - m_fMinX )
		+ m_aGridArea.Left() );
	aRet.Y() = (long)(
		m_aGridArea.Bottom() -
		( y - m_fMinY ) *
		(double)m_aGridArea.GetHeight() / ( m_fMaxY - m_fMinY ) );
	return aRet;
}

// ---------------------------------------------------------------------

void GridWindow::transform( const Point& rOriginal, double& x, double& y )
{
	x = ( rOriginal.X() - m_aGridArea.Left() ) * (m_fMaxX - m_fMinX) / (double)m_aGridArea.GetWidth() + m_fMinX;
	y = ( m_aGridArea.Bottom() - rOriginal.Y() ) * (m_fMaxY - m_fMinY) / (double)m_aGridArea.GetHeight() + m_fMinY;
}

// ---------------------------------------------------------------------

void GridWindow::drawLine( double x1, double y1, double x2, double y2 )
{
	DrawLine( transform( x1, y1 ), transform( x2, y2 ) );
}

// ---------------------------------------------------------------------

void GridWindow::computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut )
{
	// get a nice chunk size like 10, 100, 25 or such
	fChunkOut = ( fMax - fMin ) / 6.0;
	int logchunk = (int)std::log10( fChunkOut );
	int nChunk = (int)( fChunkOut / std::exp( (double)(logchunk-1) * M_LN10 ) );
	if( nChunk >= 75 )
		nChunk = 100;
	else if( nChunk >= 35 )
		nChunk = 50;
	else if ( nChunk > 20 )
		nChunk = 25;
	else if ( nChunk >= 13 )
		nChunk = 20;
	else if( nChunk > 5 )
		nChunk = 10;
	else
		nChunk = 5;
	fChunkOut = (double) nChunk * exp( (double)(logchunk-1) * M_LN10 );
	// compute whole chunks fitting into fMin
	nChunk = (int)( fMin / fChunkOut );
	fMinChunkOut = (double)nChunk * fChunkOut;
	while( fMinChunkOut < fMin )
		fMinChunkOut += fChunkOut;
}

// ---------------------------------------------------------------------

void GridWindow::computeNew()
{
	if(2L == m_aHandles.size())
	{
		// special case: only left and right markers
		double xleft, yleft;
		double xright, yright;
		transform(m_aHandles[0L].maPos, xleft, yleft);
		transform(m_aHandles[1L].maPos, xright, yright );
		double factor = (yright-yleft)/(xright-xleft);
		for( int i = 0; i < m_nValues; i++ )
		{
			m_pNewYValues[ i ] = yleft + ( m_pXValues[ i ] - xleft )*factor;
		}
	}
	else
	{
		// sort markers
		std::sort(m_aHandles.begin(), m_aHandles.end());
		const int nSorted = m_aHandles.size();
		int i;
	
		// get node arrays
		double* nodex = new double[ nSorted ];
		double* nodey = new double[ nSorted ];

		for( i = 0L; i < nSorted; i++ )
			transform( m_aHandles[i].maPos, nodex[ i ], nodey[ i ] );

		for( i = 0; i < m_nValues; i++ )
		{
			double x = m_pXValues[ i ];
			m_pNewYValues[ i ] = interpolate( x, nodex, nodey, nSorted );
			if( m_bCutValues )
			{
				if( m_pNewYValues[ i ] > m_fMaxY )
					m_pNewYValues[ i ] = m_fMaxY;
				else if( m_pNewYValues[ i ] < m_fMinY )
					m_pNewYValues[ i ] = m_fMinY;
			}
		}

		delete [] nodex;
		delete [] nodey;
	}
}

// ---------------------------------------------------------------------

double GridWindow::interpolate(
	double x,
	double* pNodeX,
	double* pNodeY,
	int nNodes )
{
	// compute Lagrange interpolation
	double ret = 0;
	for( int i = 0; i < nNodes; i++ )
	{
		double sum = pNodeY[ i ];
		for( int n = 0; n < nNodes; n++ )
		{
			if( n != i )
			{
				sum *= x - pNodeX[ n ];
				sum /= pNodeX[ i ] - pNodeX[ n ];
			}
		}
		ret += sum;
	}
	return ret;
}

// ---------------------------------------------------------------------

void GridWindow::setBoundings( double fMinX, double fMinY, double fMaxX, double fMaxY )
{
	m_fMinX = fMinX;
	m_fMinY = fMinY;
	m_fMaxX = fMaxX;
	m_fMaxY = fMaxY;

	computeChunk( m_fMinX, m_fMaxX, m_fChunkX, m_fMinChunkX );
	computeChunk( m_fMinY, m_fMaxY, m_fChunkY, m_fMinChunkY );
}

// ---------------------------------------------------------------------

void GridWindow::drawGrid()
{
	char pBuf[256];
	SetLineColor( Color( COL_BLACK ) );
	// draw vertical lines
	for( double fX = m_fMinChunkX; fX < m_fMaxX; fX += m_fChunkX )
	{
		drawLine( fX, m_fMinY, fX, m_fMaxY );
		// draw tickmarks
		Point aPt = transform( fX, m_fMinY );
        std::sprintf( pBuf, "%g", fX );
		String aMark( pBuf, gsl_getSystemTextEncoding() );
		Size aTextSize( GetTextWidth( aMark ), GetTextHeight() );
		aPt.X() -= aTextSize.Width()/2;
		aPt.Y() += aTextSize.Height()/2;
		DrawText( aPt, aMark );
	}
	// draw horizontal lines
	for( double fY = m_fMinChunkY; fY < m_fMaxY; fY += m_fChunkY )
	{
		drawLine( m_fMinX, fY, m_fMaxX, fY );
		// draw tickmarks
		Point aPt = transform( m_fMinX, fY );
        std::sprintf( pBuf, "%g", fY );
		String aMark( pBuf, gsl_getSystemTextEncoding() );
		Size aTextSize( GetTextWidth( aMark ), GetTextHeight() );
		aPt.X() -= aTextSize.Width() + 2;
		aPt.Y() -= aTextSize.Height()/2;
		DrawText( aPt, aMark );
	}

	// draw boundings
	drawLine( m_fMinX, m_fMinY, m_fMaxX, m_fMinY );
	drawLine( m_fMinX, m_fMaxY, m_fMaxX, m_fMaxY );
	drawLine( m_fMinX, m_fMinY, m_fMinX, m_fMaxY );
	drawLine( m_fMaxX, m_fMinY, m_fMaxX, m_fMaxY );
}

// ---------------------------------------------------------------------

void GridWindow::drawOriginal()
{
	if( m_nValues && m_pXValues && m_pOrigYValues )
	{
		SetLineColor( Color( COL_RED ) );
		for( int i = 0; i < m_nValues-1; i++ )
		{
			drawLine( m_pXValues[ i   ], m_pOrigYValues[ i   ],
					  m_pXValues[ i+1 ], m_pOrigYValues[ i+1 ] );
		}
	}
}

// ---------------------------------------------------------------------

void GridWindow::drawNew()
{
	if( m_nValues && m_pXValues && m_pNewYValues )
	{
		SetClipRegion( m_aGridArea );
		SetLineColor( Color( COL_YELLOW ) );
		for( int i = 0; i < m_nValues-1; i++ )
		{
			drawLine( m_pXValues[ i   ], m_pNewYValues[ i   ],
					  m_pXValues[ i+1 ], m_pNewYValues[ i+1 ] );
		}
		SetClipRegion();
	}
}

// ---------------------------------------------------------------------

void GridWindow::drawHandles()
{
	for(sal_uInt32 i(0L); i < m_aHandles.size(); i++)
	{
		m_aHandles[i].draw(*this, m_aMarkerBitmap);
	}
}

// ---------------------------------------------------------------------

void GridWindow::Paint( const Rectangle& rRect )
{
	ModalDialog::Paint( rRect );
	drawGrid();
	drawOriginal();
	drawNew();
	drawHandles();
}

// ---------------------------------------------------------------------

void GridWindow::MouseMove( const MouseEvent& rEvt )
{
	if( rEvt.GetButtons() == MOUSE_LEFT && m_nDragIndex != 0xffffffff )
	{
		Point aPoint( rEvt.GetPosPixel() );

		if( m_nDragIndex == 0L || m_nDragIndex == m_aHandles.size() - 1L)
		{
			aPoint.X() = m_aHandles[m_nDragIndex].maPos.X();
		}
		else
		{
			if(aPoint.X() < m_aGridArea.Left())
				aPoint.X() = m_aGridArea.Left();
			else if(aPoint.X() > m_aGridArea.Right())
				aPoint.X() = m_aGridArea.Right();
		}

		if( aPoint.Y() < m_aGridArea.Top() )
			aPoint.Y() = m_aGridArea.Top();
		else if( aPoint.Y() > m_aGridArea.Bottom() )
			aPoint.Y() = m_aGridArea.Bottom();

		if( aPoint != m_aHandles[m_nDragIndex].maPos )
		{
			m_aHandles[m_nDragIndex].maPos = aPoint;
			Invalidate( m_aGridArea );
		}
	}

	ModalDialog::MouseMove( rEvt );
}

// ---------------------------------------------------------------------

void GridWindow::MouseButtonUp( const MouseEvent& rEvt )
{
	if( rEvt.GetButtons() == MOUSE_LEFT )
	{
		if( m_nDragIndex != 0xffffffff )
		{
			m_nDragIndex = 0xffffffff;
			computeNew();
			Invalidate( m_aGridArea );
			Paint( m_aGridArea );
		}
	}

	ModalDialog::MouseButtonUp( rEvt );
}

// ---------------------------------------------------------------------

void GridWindow::MouseButtonDown( const MouseEvent& rEvt )
{
	Point aPoint( rEvt.GetPosPixel() );
	sal_uInt32 nMarkerIndex = 0xffffffff;

	for(sal_uInt32 a(0L); nMarkerIndex == 0xffffffff && a < m_aHandles.size(); a++)
	{
		if(m_aHandles[a].isHit(*this, aPoint))
		{
			nMarkerIndex = a;
		}
	}

	if( rEvt.GetButtons() == MOUSE_LEFT )
	{
		// user wants to drag a button
		if( nMarkerIndex != 0xffffffff )
		{
			m_nDragIndex = nMarkerIndex;
		}
	}
	else if( rEvt.GetButtons() == MOUSE_RIGHT )
	{
		// user wants to add/delete a button
		if( nMarkerIndex != 0xffffffff )
		{
			if( nMarkerIndex != 0L && nMarkerIndex != m_aHandles.size() - 1L)
			{
				// delete marker under mouse
				if( m_nDragIndex == nMarkerIndex )
					m_nDragIndex = 0xffffffff;

				m_aHandles.erase(m_aHandles.begin() + nMarkerIndex);
			}
		}
		else
		{
			m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1);
			m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1);
			m_aHandles.push_back(impHandle(aPoint, m_BmOffX, m_BmOffY));
		}

		computeNew();
		Invalidate( m_aGridArea );
		Paint( m_aGridArea );
	}

	ModalDialog::MouseButtonDown( rEvt );
}

// ---------------------------------------------------------------------

IMPL_LINK( GridWindow, ClickButtonHdl, Button*, pButton )
{
	if( pButton == &m_aResetButton )
	{
		int nType = (int)(sal_IntPtr)m_aResetTypeBox.GetEntryData( m_aResetTypeBox.GetSelectEntryPos() );
		switch( nType )
		{
			case RESET_TYPE_LINEAR_ASCENDING:
			{
				for( int i = 0; i < m_nValues; i++ )
				{
					m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX);
				}
			}
			break;
			case RESET_TYPE_LINEAR_DESCENDING:
			{
				for( int i = 0; i < m_nValues; i++ )
				{
					m_pNewYValues[ i ] = m_fMaxY - (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX);
				}
			}
			break;
			case RESET_TYPE_RESET:
			{
				if( m_pOrigYValues && m_pNewYValues && m_nValues )
					memcpy( m_pNewYValues, m_pOrigYValues, m_nValues*sizeof(double) );
			}
			break;
			case RESET_TYPE_EXPONENTIAL:
			{
				for( int i = 0; i < m_nValues; i++ )
				{
					m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)*(std::exp((m_pXValues[i]-m_fMinX)/(m_fMaxX-m_fMinX))-1.0)/(M_E-1.0);
				}
			}
			break;

			default:
				break;
		}

		for(sal_uInt32 i(0L); i < m_aHandles.size(); i++)
		{
			// find nearest xvalue
			double x, y;
			transform( m_aHandles[i].maPos, x, y );
			int nIndex = 0;
			double delta = std::fabs( x-m_pXValues[0] );
			for( int n = 1; n < m_nValues; n++ )
			{
				if( delta > std::fabs( x - m_pXValues[ n ] ) )
				{
					delta = std::fabs( x - m_pXValues[ n ] );
					nIndex = n;
				}
			}
			if( 0 == i )
				m_aHandles[i].maPos = transform( m_fMinX, m_pNewYValues[ nIndex ] );
			else if( m_aHandles.size() - 1L == i )
				m_aHandles[i].maPos = transform( m_fMaxX, m_pNewYValues[ nIndex ] );
			else
				m_aHandles[i].maPos = transform( m_pXValues[ nIndex ], m_pNewYValues[ nIndex ] );
		}

		Invalidate( m_aGridArea );
		Paint(Rectangle());
	}
	return 0;
}
