/***************************************************************************
 *   Copyright (C) 2000-2010 by Noel Novelli                               *
 *   novelli@lif.univ-mrs.fr                                               *
 *                                                                         *
 *   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 <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>

#ifdef WIN32
#include <windows.h>
#include <psapi.h>  //  Add Psapi.lib in VCProject or in LDFLAGS
#else
#include <malloc.h>
#endif
//
#include "algoCFUN.h"
#include "trace.h"
#include "cfunException.h"
#include "levelCFUN.h"
#include "partitionCFUN.h"
#include "threadTime.h"
#include "relation.h"
//

extern string gbl_sOutputFileName;

static void updateMaxMemoryUsage( double &maxMem )
{
    double memUsed = 0.;

#ifdef WIN32
    DWORD pid = ::GetCurrentProcessId();
    HANDLE hProcess;
    PROCESS_MEMORY_COUNTERS pmc;
    hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,false,pid);
    if (::GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc))) {
        // Link errorhere: add Psapi.lib in the VCProject or LDFLAGS (mingw)
        memUsed = pmc.PagefileUsage / 1024.; // In Ko
    }
    CloseHandle( hProcess );
#else
    struct mallinfo info;
	info = ::mallinfo( );
    	// by mmap and malloc
	memUsed = ((double) info.hblkhd + info.arena) / (1024. * 1024.);
#endif

    if ( memUsed > maxMem ) {
        maxMem = memUsed;
    }
}

static void displayStat( const string &title,
                         unsigned int nbCFD, unsigned int nbSrcOfCFD,
                         double dMaxMem, ostream &os )
{
	os << title << endl;
	os << "==========" << endl;
	os << "NbCFD     : " << setw( 10 ) << nbCFD      << endl;
	os << "NbSrcOfCFD: " << setw( 10 ) << nbSrcOfCFD << endl;
    if ( dMaxMem > 1024 ) { // => Mo
        os << "MaxMemUsage: " << dMaxMem / 1024. << " Mo" << endl << endl;
    } else {
        os << "MaxMemUsage: " << dMaxMem << " Ko" << endl << endl;
    }
}

///////////////////////////////////////////////////////////////////
//
//                          C F U N
//
//
//  L_0 := < \emptyset, Count( \emptyset ), \emptyset, \emptyset >
//  L_1 := { < A, Count( A ), A, A > | A \in R }
//
//  for ( k := 1; L_k \not = \emptyset; k++ )
//      Closure( L_k, L_{k-1} )
//      QuasiClosure( L_{k-1}, L_k )
//      DisplayCFD( L_{k-1} )
//      Prune( L_k )
//      L_{k+1} := GenerateNextLevel( L_k )
//  end for
//  Closure( L_k, L_{k-1} )
//  DisplayCFD( L_{k-1} )
//
///////////////////////////////////////////////////////////////////

void cfun( const Relation &relation,
				unsigned int uMaxLevel,
				bool bPrune, bool bWaitUser, bool bStdOutput )
{
    double dMaxMem = 0.;
    updateMaxMemoryUsage( dMaxMem );

    LevelCFUN::InitClass( relation.getNbCol() );
	LevelCFUN::_bStdOutput = bStdOutput;

    if ( uMaxLevel < 2 ) {
       uMaxLevel = relation.getNbCol() + 2;  // All in RAM [0..#attrbiut+1]
    }
    LevelCFUN *levels = new LevelCFUN[uMaxLevel];

	    //  L_0 := < \emptyset, 0, \emptyset, \emptyset >
	LevelCFUN &level0 = levels[0];
    level0.m_uNbAttr = 1;
    level0.Init( level0.m_uNbAttr );
    level0.m_pListeAttr[0].init0( );
	level0.m_pListeAttr[0].initQuasiAndClosureCFD( level0.m_pListeAttr[0].m_Attr, level0.m_uNbAttr );
    level0.m_pListeAttr[0].m_uCount = 1;
    level0.m_pListeAttr[0].m_bFreeSet = true;
    
		//  L_1 := { < A, Count( A ), A, A > | A \in R }
	LevelCFUN &level1 = levels[1];
	level1.m_uNbAttr = relation.getNbCol();
    level1.Init( level1.m_uNbAttr );
    level1.GetAllPartitionFromRelation( relation );
	 
	ThreadTime threadTime;
    threadTime.ThreadTimeInit( );

    updateMaxMemoryUsage( dMaxMem );

        //  for ( k := 1; L_k \not = \emptyset; k++ )
    unsigned int k;
    for ( k = 1; levels[k % uMaxLevel].m_uNbAttr > 0; k++ ) {
        cerr << "-------------------------- level " << k << " [" << levels[k % uMaxLevel].m_uNbAttr << "] ";
        threadTime.ThreadTimeElapsedTime( );
        cerr << endl;

            //      Closure( L_k, L_{k-1} )
        cerr << "* COMPUTING CLOSURE" << endl;
        levels[(k-1) % uMaxLevel].closure( levels[k % uMaxLevel], relation );
		//
        cerr << "\tClosure           : ";
        threadTime.ThreadTimeElapsedTime( );
        cerr << endl;

            //      QuasiClosure( L_{k-1}, L_k )
        cerr << "* COMPUTING QUASICLOSURE" << endl;
        levels[k % uMaxLevel].quasiClosure( levels[(k-1) % uMaxLevel] );
		//
        cerr << "\tQuasiClosure      : ";
        threadTime.ThreadTimeElapsedTime( );
        cerr << endl;

            //      DisplayCFD( L_{k-1} )
		if ( LevelCFUN::_bStdOutput ) {
            cerr << "* DISPLAYING CFDS" << endl;
			levels[(k-1) % uMaxLevel].displayCFD( );
			//
			cerr << "\tDisplayCFDs       : ";
			threadTime.ThreadTimeElapsedTime( );
			cerr << endl;
		}


            //      Prune( L_k )
		if ( bPrune ) {
            cerr << "* COMPUTING PUREPRUNE" << endl;
			levels[k % uMaxLevel].PurePrune( );
			//
			cerr << "\tPurePrune         : ";
			threadTime.ThreadTimeElapsedTime( );
			cerr << endl;
		}

            //      L_{k+1} := GenerateNextLevel( L_k )
        cerr << "* COMPUTING NEXT LEVEL" << endl;
        levels[(k+1) % uMaxLevel].Init( 10 );
        levels[(k+1) % uMaxLevel].GenerateNextLevel( levels[k % uMaxLevel], bPrune );
		//
        cerr << "\tGenerateNextLevel : ";
        threadTime.ThreadTimeElapsedTime( );
        cerr << endl;

        updateMaxMemoryUsage( dMaxMem );
        stringstream s;
        s << "LEVEL " << k;
        displayStat( s.str(), LevelCFUN::_nbCFD, LevelCFUN::_nbSrcOfCFD, dMaxMem, cerr );

		if ( bWaitUser ) {
			string a;
			cin >> a;
		}
    }
        //  end for

    cerr << "-------------------------- last level [" << levels[k % uMaxLevel].m_uNbAttr << "] ";
    threadTime.ThreadTimeElapsedTime( );
    cerr << endl;

        //  Closure( L_k, L_{k-1} )
    cerr << "* COMPUTING CLOSURE" << endl;
	levels[(k-1) % uMaxLevel].closure( levels[k % uMaxLevel], relation );
	//
	cerr << "\tClosure           : ";
	threadTime.ThreadTimeElapsedTime( );
	cerr << endl;

        //  DisplayCFD( L_{k-1} )
	if ( LevelCFUN::_bStdOutput ) {
        cerr << "* DISPLAYING CFDS" << endl;
		levels[(k-1) % uMaxLevel].displayCFD( );
	}

    updateMaxMemoryUsage( dMaxMem );
    displayStat( "LAST LEVEL ", LevelCFUN::_nbCFD, LevelCFUN::_nbSrcOfCFD, dMaxMem, cout );
	cout << "Global time: ";
	threadTime.ThreadTimeElapsedTime( cout );
	cout << endl;

    updateMaxMemoryUsage( dMaxMem );

    delete [] levels;

    LevelCFUN::FreeAlloc( );

	if ( !gbl_sOutputFileName.empty() ) {
		ofstream os;

		os.open( gbl_sOutputFileName.c_str(), ios_base::out | ios_base::app );
		if ( os.bad() ) {
			cerr << "Open output file error: " << gbl_sOutputFileName.c_str() << endl;
		} else {
			double timeInSec;
			threadTime.getElapsedTimeSec( timeInSec );
			os << '\t' << timeInSec << '\t' << LevelCFUN::_nbCFD << '\t' << dMaxMem << " Ko" <<  endl;
		}
	}
}
