/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/

#include <wchar.h>
#include <stddef.h>

#include <iostream>
#include <set>
#include <sstream>

#include "cpixtools.h"

#include "itk.h"

#include "cpixidxdb.h"
#include "cpixsearch.h"

#include "config.h"
#include "testutils.h"
#include "setupsentry.h"


#define VATTUNIEMENRANTA_2_GPS L"60.154023,24.887724"



struct GpsLocation
{
    char                    gpsLatitudeRef_;
    Cpt::ExifGpsCoord       gpsLatitude_;
    char                    gpsLongitudeRef_;
    Cpt::ExifGpsCoord       gpsLongitude_;
};


const GpsLocation GPSLOC_DATA[] = {
    {
        Cpt::QNr::NORTH, 
        Cpt::ExifGpsCoord(Cpt::ExifRational(60,1),
                          Cpt::ExifRational(9,1),
                          Cpt::ExifRational(3529,100)),
        Cpt::QNr::EAST,
        Cpt::ExifGpsCoord(Cpt::ExifRational(24,1),
                          Cpt::ExifRational(53,1),
                          Cpt::ExifRational(2508,100)),
    },

    
};


class JpgFileIdxUtil : public FileIdxUtil
{
public:
    virtual ~JpgFileIdxUtil() throw ()
    {
        ;
    }

protected:
    virtual void printHit(cpix_Document * doc,
                          Itk::TestMgr   * testMgr)
    {
        cpix_DocFieldEnum
            * dfe = cpix_Document_fields(doc);
        if (cpix_Succeeded(doc))
            {
                cpix_Field
                    field;

                printf("DOCUMENT:\n");

                while (cpix_DocFieldEnum_hasMore(dfe))
                    {
                        cpix_DocFieldEnum_next(dfe,
                                               &field);
                        const wchar_t
                            * name = cpix_Field_name(&field);

                        ITK_EXPECT(testMgr,
                                   cpix_Succeeded(&field),
                                   "Getting field name failed");

                        printf(" o %S: ",
                               name);

                        bool
                            binary = 
                            static_cast<bool>(cpix_Field_isBinary(&field));

                        /* OBS
                        printf("%S\n",
                               (binary ? 
                                L"(binary)" 
                                : cpix_Field_stringValue(&field)
                                )
                               );
                        */
                        if (binary)
                            {
                                printf("(binary)\n");
                            }
                        else
                            {
                                const wchar_t
                                    * val = cpix_Field_stringValue(&field);
                                
                                ITK_EXPECT(testMgr,
                                           cpix_Succeeded(&field),
                                           "Getting field value failed");

                                bool
                                    docuid = wcscmp(LCPIX_DOCUID_FIELD,
                                                    name) == 0;

                                if (docuid)
                                    {
                                        // docuid is the path that may
                                        // differ in its drive letter
                                        // in emulator and in target
                                        printf("!%S\n",
                                               (wcslen(val) > 0 ? val+1 : val));
                                    }
                                else
                                    {
                                        printf("%S\n",
                                               val);
                                    }
                            }
                    }

                cpix_DocFieldEnum_destroy(dfe);
            }
        else
            {
                ITK_EXPECT(testMgr,
                           false,
                           "Could not get doc enum field");
            }
    }
};




class GeoContext : public Itk::ITestContext, public Cpt::IFileVisitor
{
protected:
    //
    // protected members
    //
    JpgFileIdxUtil       * util_;
    cpix_Analyzer        * analyzer_;
    cpix_QueryParser     * queryParser_;
    cpix_Query           * query_;


    Itk::TestMgr   * testMgr_;

public:

    //
    // From ITestContext
    //
    virtual void setup() throw (Itk::PanicExc)
    {
        SetupSentry
            ss(*this);

        cpix_Result
            result;

        cpix_IdxDb_dbgScrapAll(&result);

        if (cpix_Failed(&result))
            {
                ITK_PANIC("Could not scrap all");
            }

        util_ = new JpgFileIdxUtil;
        util_->init();
        
        analyzer_ = cpix_CreateSimpleAnalyzer(&result);
        if (analyzer_ == NULL)
            {
                ITK_PANIC("Could not create analyzer");
            }
        
        queryParser_ = cpix_QueryParser_create(&result,
                                               QNR_FIELD,
                                               analyzer_);
        if (queryParser_ == NULL)
            {
                ITK_PANIC("Could not create query parser");
            }

        ss.setupComplete();
    }


    virtual void tearDown() throw()
    {
        cleanup();
    }


    virtual ~GeoContext()
    {
        cleanup();
    }


    //
    // from Cpt::IFileVisitor
    //
    virtual bool visitFile(const char * path)
    {
        bool
            goOn = true;

        const char
            * extensionChar = path + strlen(path) - 1;
        while (extensionChar >= path && *extensionChar != '.')
            {
                --extensionChar;
            }

        if (strcmp(".jpg", extensionChar) == 0)
            {
                util_->indexFile(path,
                                 analyzer_,
                                 testMgr_);
            }

        return goOn;
    }
    
    
    virtual DirVisitResult visitDirPre(const char * /*path*/)
    {
        return IFV_CONTINUE;
    }


    virtual bool visitDirPost(const char * /*path*/)
    {
        return true;
    }


    //
    // public operations
    //
    GeoContext()
        : util_(NULL),
          analyzer_(NULL),
          queryParser_(NULL),
          query_(NULL),
          testMgr_(NULL)
    {
        ;
    }


    //
    // test methods
    // 

    void testHarvestJpg(Itk::TestMgr * testMgr)
    {
        testMgr_ = testMgr;
        Cpt::traverse(JPG_TEST_CORPUS_PATH,
                      this);
        util_->flush();
    }


    void testSearchJpg(Itk::TestMgr * testMgr)
    {
        testMgr_ = testMgr;

        cpix_Hits
            * hits = search(GPSLOC_DATA,
                            sizeof(GPSLOC_DATA) / sizeof(GpsLocation),
                            NULL,
                            4.0, 
                            10);

        cpix_Hits_destroy(hits);
    }

private:
    void cleanup()
    {
        delete util_;
        util_ = NULL;

        cpix_Analyzer_destroy(analyzer_);
        analyzer_ = NULL;

        cpix_QueryParser_destroy(queryParser_);
        queryParser_ = NULL;

        cpix_Query_destroy(query_);
        query_ = NULL;
    }



    /**
     * This function is the sample code for any client performing
     * location based search on anything that has QNR_FIELD defined.
     *
     * TODO RANKING of hits based on actual distance is not
     * implemented yet
     *
     * @param gpsLocs gps locations around which to search - MUST NOT
     * be NULL
     *
     * @param gpsLocsCount the number of gps locations given by
     * gpsLocs
     *
     * @param additionalTerm any additional term to include in the
     * query ("AND" query operation), if necessary, may be NULL
     *
     * @param radiusKm the radius, in kilometers, within which all
     * hits should be found (no false negatives are allowed). NOTE
     * that we may get false positives (hits that are beyond the
     * radius)
     *
     * @param maxHits the number of hits requested (within the given
     * radius), if possible
     */
    cpix_Hits * search(const GpsLocation   gpsLocs[],
                       size_t              gpsLocsCount,
                       const wchar_t     * additionalTerm,
                       double              radiusKm,
                       size_t              maxHits)
    {
		wchar_t qryStr[512];
		
		int loc = 
			swprintf( qryStr,
					  sizeof(qryStr),
					  L"$quad<");
		
		loc += 
			Cpt::wsnprintdouble( qryStr + loc,
								 sizeof(qryStr)-loc,
								 radiusKm, 
								 4 );  

		
		loc += 
			swprintf( qryStr+loc,
					  sizeof(qryStr)-loc,
					  L",%d,%S",
					  maxHits, 
					  VATTUNIEMENRANTA_2_GPS); 
    
        for (size_t i = 0; i < gpsLocsCount; ++i)
            {
                double
                    latitude,
                    longitude;

                Cpt::QNr::convertGps(gpsLocs[i].gpsLatitudeRef_,
                                     gpsLocs[i].gpsLatitude_,
                                     gpsLocs[i].gpsLongitudeRef_,
                                     gpsLocs[i].gpsLongitude_,
                                     &latitude,
                                     &longitude);
// FIXME: Get rid of Cpt::wsnprintdouble, when printf("%lf", ....) starts to work
                qryStr[loc++]= ',';
                loc +=
					Cpt::wsnprintdouble( qryStr + loc,
										 sizeof(qryStr)-loc,
										 latitude, 
										 10 );  

                qryStr[loc++]= ',';
                loc +=
					Cpt::wsnprintdouble( qryStr + loc,
										 sizeof(qryStr)-loc,
										 longitude, 
										 10 );  

/* FIXME: This DOES NOT work. 
                loc += 
					swprintf( qryStr + loc, 
							  sizeof(qryStr)-loc,
							  L",%lf,%lf", 
							  latitude,
							  longitude );
			    */ 
            }

        loc += 
			swprintf(qryStr + loc, sizeof(qryStr)-loc, L">");

        if (additionalTerm != NULL)
            {
				loc += 
					swprintf(qryStr + loc, 
							 sizeof(qryStr)-loc,
							 L"(%S)", 
							 additionalTerm);
            }
        
        cpix_Query_destroy(query_);
        query_ = NULL;

        query_ = cpix_QueryParser_parse(queryParser_,
                                        qryStr);
        ITK_ASSERT(testMgr_,
                   cpix_Succeeded(queryParser_),
                   "Failed to parse: %S\n",
                   qryStr);
        
        cpix_Hits
            * hits = cpix_IdxDb_search(util_->idxDb(),
                                       query_);
        ITK_ASSERT(testMgr_,
                   cpix_Succeeded(util_->idxDb()),
                   "Failed to search (geo)");
        
        int32_t
            hitCount = cpix_Hits_length(hits);
        
        if (hitCount > 0)
            {
                util_->printHits(hits,
                                 testMgr_);
            }
        else
            {
                printf("  Nothing is found\n");
            }

        return hits;
    }


};




Itk::TesterBase * CreateGeoTests()
{
    using namespace Itk;

    GeoContext
        * geoContext = new GeoContext;
    ContextTester
        * contextTester = new ContextTester("geo",
                                            geoContext);

#define TEST "harvestJpg"
    contextTester->add(TEST,
                       geoContext,
                       &GeoContext::testHarvestJpg,
                       TEST);
#undef TEST

    // TODO perhaps a dump-all test case too?

#define TEST "searchJpg"
    contextTester->add(TEST,
                       geoContext,
                       &GeoContext::testSearchJpg,
                       TEST);
#undef TEST

    // TODO add more tests to suite
        
    return contextTester;
}
