/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/


#include "sampleaddin.hxx"

#include <cppuhelper/factory.hxx>
#include <osl/diagnose.h>

#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/drawing/XDrawPage.hpp>
#include <com/sun/star/chart/XChartDataArray.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/chart/X3DDisplay.hpp>

using namespace com::sun::star;
using namespace rtl;

// code for creating instances of SampleAddIn

extern "C" {

void SAL_CALL component_getImplementationEnvironment(
	const sal_Char ** ppEnvTypeName, uno_Environment ** /*ppEnv*/ )
{
	*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}

sal_Bool SAL_CALL component_writeInfo(
	void * /*pServiceManager*/, registry::XRegistryKey * pRegistryKey )
{
	if( pRegistryKey )
	{
		try
		{
			OUString aImpl = OUString::createFromAscii( "/" );
			aImpl += SampleAddIn::getImplementationName_Static();
			aImpl += OUString::createFromAscii( "/UNO/SERVICES" );

			uno::Reference< registry::XRegistryKey> xNewKey(
				reinterpret_cast<registry::XRegistryKey*>( pRegistryKey )->createKey( aImpl ) );

			uno::Sequence< OUString > aSequ = SampleAddIn::getSupportedServiceNames_Static();
			const OUString * pArray = aSequ.getConstArray();
			for( sal_Int32 i = 0; i < aSequ.getLength(); i++ )
				xNewKey->createKey( pArray[i] );

			return sal_True;
		}
		catch( registry::InvalidRegistryException& )
		{
			OSL_ENSURE( sal_False, "### InvalidRegistryException!" );
		}
	}
	return sal_False;
}

void * SAL_CALL component_getFactory(
	const sal_Char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ )
{
	void* pRet = 0;

	if ( pServiceManager &&
			OUString::createFromAscii( pImplName ) == SampleAddIn::getImplementationName_Static() )
	{
		uno::Reference< lang::XSingleServiceFactory> xFactory( cppu::createSingleFactory(
				reinterpret_cast<lang::XMultiServiceFactory*>( pServiceManager ),
				SampleAddIn::getImplementationName_Static(),
				SampleAddIn_CreateInstance,
				SampleAddIn::getSupportedServiceNames_Static() ) );

		if( xFactory.is())
		{
			xFactory->acquire();
			pRet = xFactory.get();
		}
	}

	return pRet;
}

}	// extern C


// --------------------
// class SampleAddIn
// --------------------

SampleAddIn::SampleAddIn()
{
	
}

SampleAddIn::~SampleAddIn()
{}


// this functionality should be provided by the chart API some day
sal_Bool SampleAddIn::getLogicalPosition( uno::Reference< drawing::XShape >& xAxis,
										  double fValue,
										  sal_Bool bVertical,
										  awt::Point& aOutPosition )
{
	sal_Bool bRet = sal_False;

	if( xAxis.is())
	{
		awt::Size aSize = xAxis->getSize();
		sal_Int32 nLength = bVertical? aSize.Height: aSize.Width;

		uno::Reference< beans::XPropertySet > xProp( xAxis, uno::UNO_QUERY );
		if( xProp.is())
		{
			try
			{
				double fMin(0.0), fMax(0.0);
				uno::Any aAny = xProp->getPropertyValue( OUString::createFromAscii( "Min" ));
				aAny >>= fMin;
				aAny = xProp->getPropertyValue( OUString::createFromAscii( "Max" ));
				aAny >>= fMax;

				double fRange = fMax - fMin;
				if( fMin <= fValue && fValue <= fMax &&
					fRange != 0.0 )
				{
					double fPercentage = (fValue - fMin) / fRange;
					awt::Point aPos = xAxis->getPosition();

					if( bVertical )
					{
						aOutPosition.X = aPos.X;
						aOutPosition.Y = static_cast<sal_Int32>(aPos.Y + nLength * (1.0 - fPercentage));	// y scale goes from top to bottom
					}
					else
					{
						aOutPosition.X = static_cast<sal_Int32>(aPos.X + nLength * fPercentage);
						aOutPosition.Y = aPos.Y;
					}
					bRet = sal_True;
				}
			}
			catch( beans::UnknownPropertyException )
			{
				// the shape xAxis was no chart axis
			}
		}
	}

	return bRet;
}

OUString SampleAddIn::getImplementationName_Static()
{
	return OUString::createFromAscii( "SampleAddIn" );
}

uno::Sequence< ::rtl::OUString > SampleAddIn::getSupportedServiceNames_Static()
{
	uno::Sequence< OUString > aSeq( 4 );

	aSeq[ 0 ] = OUString::createFromAscii( "com.sun.star.chart.ChartAxisXSupplier" );
	aSeq[ 1 ] = OUString::createFromAscii( "com.sun.star.chart.ChartAxisYSupplier" );
	aSeq[ 2 ] = OUString::createFromAscii( "com.sun.star.chart.Diagram" );
	aSeq[ 3 ] = OUString::createFromAscii( "com.sun.star.chart.SampleAddIn" );

	return aSeq;
}

uno::Reference< uno::XInterface > SAL_CALL SampleAddIn_CreateInstance(
	const uno::Reference< lang::XMultiServiceFactory >& )
{
	uno::Reference< uno::XInterface > xInst = (cppu::OWeakObject*)new SampleAddIn();

	return xInst;
}

// implementation of interface methods

// XInitialization
void SAL_CALL SampleAddIn::initialize( const uno::Sequence< uno::Any >& aArguments )
	throw( uno::Exception, uno::RuntimeException )
{
	// first argument should be the XChartDocument
	OSL_ENSURE( aArguments.getLength() > 0, "Please initialize Chart AddIn with ChartDocument!" );

	if( aArguments.getLength())
	{
		aArguments[ 0 ] >>= mxChartDoc;
		OSL_ENSURE( mxChartDoc.is(), "First argument in initialization is not an XChartDocument!" );

		// set XY chart as base type to be drawn
		uno::Reference< beans::XPropertySet > xDocProp( mxChartDoc, uno::UNO_QUERY );
		if( xDocProp.is())
		{
			uno::Any aBaseType;
			aBaseType <<= rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.chart.XYDiagram" ));
			try
			{
				xDocProp->setPropertyValue( rtl::OUString::createFromAscii( "BaseDiagram" ), aBaseType );
			}
			catch( ... )
			{}
		}

		// change background of plot area to light blue
		uno::Reference< chart::X3DDisplay > xWallSupplier( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xWallSupplier.is())
		{
			uno::Reference< beans::XPropertySet > xDiaProp( xWallSupplier->getWall(), uno::UNO_QUERY );
			uno::Reference< beans::XPropertySet > xLegendProp( mxChartDoc->getLegend(), uno::UNO_QUERY );
			if( xDiaProp.is() &&
				xLegendProp.is())
			{
				uno::Any aAny;
				aAny <<= (sal_Int32)( 0xe0e0f0 );
				xDiaProp->setPropertyValue( OUString::createFromAscii( "FillColor" ), aAny );
				xLegendProp->setPropertyValue( OUString::createFromAscii( "FillColor" ), aAny );
			}
		}
	}
}

// XRefreshable
/********************************************************************************
 *
 * The method refresh is the most important method - here all objects that
 * are necessary for the chart are created
 *
 * in the first implementation you will have to insert everything in this
 * routine - all old objects are deleted beforehand
 *
 ********************************************************************************/
void SAL_CALL SampleAddIn::refresh() throw( uno::RuntimeException )
{
	if( ! mxChartDoc.is())
		return;

	// first of all get the draw page
	uno::Reference< drawing::XDrawPageSupplier > xPageSupp( mxChartDoc, uno::UNO_QUERY );
	uno::Reference< lang::XMultiServiceFactory > xFactory( mxChartDoc, uno::UNO_QUERY );
	if( xPageSupp.is() &&
		xFactory.is() )
	{
		uno::Reference< drawing::XDrawPage > xPage = xPageSupp->getDrawPage();
		if( xPage.is())
		{
			// now we have the page to insert objects

			// add a horizontal line at the middle value of the first series
			// -------------------------------------------------------------


			// get the logical position from the coordinate
			// get x- and y-axis
			uno::Reference< drawing::XShape > xYAxisShape( getYAxis(), uno::UNO_QUERY );
			uno::Reference< drawing::XShape > xXAxisShape( getXAxis(), uno::UNO_QUERY );

			if( xXAxisShape.is() &&
				xYAxisShape.is() )
			{
				// create line first time
				if( ! mxMyRedLine.is())
				{
					mxMyRedLine = uno::Reference< drawing::XShape >(
						xFactory->createInstance( OUString::createFromAscii( "com.sun.star.drawing.LineShape" )),
						uno::UNO_QUERY );
					xPage->add( mxMyRedLine );

					// make line red and thick
					uno::Reference< beans::XPropertySet > xShapeProp( mxMyRedLine, uno::UNO_QUERY );
					if( xShapeProp.is())
					{
						uno::Any aColor, aWidth;
						aColor <<= (sal_Int32)(0xe01010);
						aWidth <<= (sal_Int32)(50);			// 0.5 mm
						try
						{
							xShapeProp->setPropertyValue( OUString::createFromAscii( "LineColor" ), aColor );
							xShapeProp->setPropertyValue( OUString::createFromAscii( "LineWidth" ), aWidth );
						}
						catch( ... )
						{}
					}
				}
				// create text object first time
				if( ! mxMyText.is())
				{
					mxMyText = uno::Reference< drawing::XShape >(
						xFactory->createInstance( OUString::createFromAscii( "com.sun.star.drawing.TextShape" )),
						uno::UNO_QUERY );
					xPage->add( mxMyText );

					// change text
					OUString aText;
// 					if( maLocale.Language.equalsIgnoreCase( OUString::createFromAscii("DE")))
// 						aText = OUString::createFromAscii( "Kleines Beispiel" );
// 					else
						aText = OUString::createFromAscii( "Little Example" );

					uno::Reference< beans::XPropertySet > xTextProp( mxMyText, uno::UNO_QUERY );
					if( xTextProp.is())
					{
						uno::Any aTrueAny;
						aTrueAny <<= (sal_Bool)(sal_True);
						try
						{
							xTextProp->setPropertyValue( rtl::OUString::createFromAscii( "TextAutoGrowWidth" ), aTrueAny );
						}
						catch( ... )
						{}
					}

					uno::Reference< text::XTextRange > xTextRange( mxMyText, uno::UNO_QUERY );
					if( xTextRange.is())
					{
						xTextRange->setString( aText );
					}
				}


				// position line and text

				// get the array. Note: the first dimension is the length
				// of each series and the second one is the number of series
				// this should be changed in the future
				uno::Sequence< uno::Sequence< double > > aData;
				uno::Reference< chart::XChartData > xData = mxChartDoc->getData();
				uno::Reference< chart::XChartDataArray > xDataArray( xData, uno::UNO_QUERY );
				if( xDataArray.is())
					aData = xDataArray->getData();

				// get row count == length of each series
				sal_Int32 nSize = aData.getLength();
				sal_Int32 nMiddle = nSize / 2;
				// get value for first series 
				double fMiddleVal = xData->getNotANumber();		// set to NaN
				if( aData[ nMiddle ].getLength())				// we have at least one series
					fMiddleVal = aData[ nMiddle ][ 0 ];

				awt::Point aPos;
				getLogicalPosition( xYAxisShape, fMiddleVal, sal_True, aPos );
				awt::Size aSize = xXAxisShape->getSize();

				if( mxMyRedLine.is())
				{
					awt::Point aEnd = aPos;
					aEnd.X += aSize.Width;

					uno::Sequence< uno::Sequence< awt::Point > > aPtSeq( 1 );
					aPtSeq[ 0 ].realloc( 2 );
					aPtSeq[ 0 ][ 0 ] = aPos;
					aPtSeq[ 0 ][ 1 ] = aEnd;

					uno::Reference< beans::XPropertySet > xShapeProp( mxMyRedLine, uno::UNO_QUERY );
					if( xShapeProp.is())
					{
						uno::Any aAny;
						aAny <<= aPtSeq;
						xShapeProp->setPropertyValue( rtl::OUString::createFromAscii( "PolyPolygon" ), aAny );
					}
				}
				if( mxMyText.is())
				{
					// put the text centered below the red line
					aPos.X += ( aSize.Width - mxMyRedLine->getPosition().X ) / 2;
					aPos.Y += 1000;
					aPos.Y += static_cast<sal_Int32>(0.1 * xYAxisShape->getSize().Height);
					mxMyText->setPosition( aPos );
				}
			}
		}
	}

	// set axis scale to 200
//  	uno::Reference< beans::XPropertySet > xXAxis( getXAxis(), uno::UNO_QUERY );
//  	if( xXAxis.is())
//  	{
//  		uno::Any aAny;
//  		aAny <<= (sal_Bool)(sal_False);
//  		xXAxis->setPropertyValue( rtl::OUString::createFromAscii( "AutoStepMain" ),
//  								  aAny );
//  		aAny <<= (double)(200.0);
//  		xXAxis->setPropertyValue( rtl::OUString::createFromAscii( "StepMain" ),
//  								  aAny );
//  	}

// try setting symbols
//  	uno::Reference< beans::XPropertySet > xProp = getDataRowProperties( 0 );
//  	if( xProp.is())
//  	{
//  		uno::Any aAny;
//  		aAny <<= (sal_Int32)(-1);
//  		xProp->setPropertyValue( OUString::createFromAscii( "SymbolType" ), aAny );
//  		aAny <<= rtl::OUString::createFromAscii( "http://mib-1168/www/images/go.gif" );
//  		xProp->setPropertyValue( OUString::createFromAscii( "SymbolBitmapURL" ), aAny );
//  	}
}

void SAL_CALL SampleAddIn::addRefreshListener( const uno::Reference< util::XRefreshListener >&  )
	throw( uno::RuntimeException )
{
	// not implemented - this is not necessary
	// (this method exists just because the interface requires it)
}

void SAL_CALL SampleAddIn::removeRefreshListener( const uno::Reference< util::XRefreshListener >&  )
	throw( uno::RuntimeException )
{
	// not implemented - this is not necessary
	// (this method exists just because the interface requires it)
}

// XDiagram
OUString SAL_CALL SampleAddIn::getDiagramType() throw( uno::RuntimeException )
{
	return OUString::createFromAscii( "com.sun.star.chart.SampleDiagram" );
}

// the following methods just delegate to the "parent diagram" (which in the future might no longer exist)

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getDataRowProperties( sal_Int32 nRow )
	throw( lang::IndexOutOfBoundsException,
		   uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XDiagram > xDia = mxChartDoc->getDiagram();
		if( xDia.is())
			return xDia->getDataRowProperties( nRow );
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getDataPointProperties( sal_Int32 nCol, sal_Int32 nRow )
	throw( lang::IndexOutOfBoundsException,
		   uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XDiagram > xDia = mxChartDoc->getDiagram();
		if( xDia.is())
			return xDia->getDataPointProperties( nCol, nRow );
	}

	return uno::Reference< beans::XPropertySet >();
}

// XShape ( ::XDiagram )
awt::Size SAL_CALL SampleAddIn::getSize()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< drawing::XShape > xShape( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xShape.is())
			return xShape->getSize();
	}

	return awt::Size();
}

void SAL_CALL SampleAddIn::setSize( const awt::Size& aSize )
	throw( beans::PropertyVetoException, uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< drawing::XShape > xShape( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xShape.is())
			xShape->setSize( aSize );
	}
}

awt::Point SAL_CALL SampleAddIn::getPosition()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< drawing::XShape > xShape( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xShape.is())
			return xShape->getPosition();
	}

	return awt::Point();
}

void SAL_CALL SampleAddIn::setPosition( const awt::Point& aPos )
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< drawing::XShape > xShape( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xShape.is())
			xShape->setPosition( aPos );
	}
}

// XShapeDescriptor ( ::XShape ::XDiagram )
rtl::OUString SAL_CALL SampleAddIn::getShapeType() throw( com::sun::star::uno::RuntimeException )
{
	return OUString::createFromAscii( "com.sun.star.chart.SampleAddinShape" );
}

// XAxisXSupplier
uno::Reference< drawing::XShape > SAL_CALL SampleAddIn::getXAxisTitle()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisXSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getXAxisTitle();
	}

	return uno::Reference< drawing::XShape >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getXAxis()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisXSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getXAxis();
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getXMainGrid()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisXSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getXMainGrid();
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getXHelpGrid()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisXSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getXHelpGrid();
	}

	return uno::Reference< beans::XPropertySet >();
}

// XAxisYSupplier
uno::Reference< drawing::XShape > SAL_CALL SampleAddIn::getYAxisTitle()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisYSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getYAxisTitle();
	}

	return uno::Reference< drawing::XShape >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getYAxis()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisYSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getYAxis();
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getYMainGrid()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisYSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getYMainGrid();
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getYHelpGrid()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XAxisYSupplier > xAxisSupp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xAxisSupp.is())
			return xAxisSupp->getYHelpGrid();
	}

	return uno::Reference< beans::XPropertySet >();
}

// XStatisticDisplay
uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getUpBar()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XStatisticDisplay > xStatDisp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xStatDisp.is())
			return xStatDisp->getUpBar();
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getDownBar()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XStatisticDisplay > xStatDisp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xStatDisp.is())
			return xStatDisp->getDownBar();
	}

	return uno::Reference< beans::XPropertySet >();
}

uno::Reference< beans::XPropertySet > SAL_CALL SampleAddIn::getMinMaxLine()
	throw( uno::RuntimeException )
{
	if( mxChartDoc.is())
	{
		uno::Reference< chart::XStatisticDisplay > xStatDisp( mxChartDoc->getDiagram(), uno::UNO_QUERY );
		if( xStatDisp.is())
			return xStatDisp->getMinMaxLine();
	}

	return uno::Reference< beans::XPropertySet >();
}

// XServiceName
OUString SAL_CALL SampleAddIn::getServiceName() throw( uno::RuntimeException )
{
	return OUString::createFromAscii( "com.sun.star.chart.SampleAddIn" );
}

// XServiceInfo
OUString SAL_CALL SampleAddIn::getImplementationName() throw( uno::RuntimeException )
{
	return getImplementationName_Static();
}

sal_Bool SAL_CALL SampleAddIn::supportsService( const OUString& ServiceName )
	throw( uno::RuntimeException )
{
	uno::Sequence< OUString > aServiceSeq = getSupportedServiceNames_Static();

	sal_Int32 nLength = aServiceSeq.getLength();
	for( sal_Int32 i=0; i < nLength; i++ )
	{
		if( ServiceName.equals( aServiceSeq[ i ] ))
			return sal_True;
	}

	return sal_False;
}

uno::Sequence< OUString > SAL_CALL SampleAddIn::getSupportedServiceNames()
	throw( uno::RuntimeException )
{
	return getSupportedServiceNames_Static();
}

// XLocalizable
void SAL_CALL SampleAddIn::setLocale( const lang::Locale& eLocale )
	throw( uno::RuntimeException )
{
	maLocale = eLocale;
}

lang::Locale SAL_CALL SampleAddIn::getLocale()
	throw( uno::RuntimeException )
{
	return maLocale;
}
