/*
* Copyright (c) 2006-2009 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 "CmdGlobals.h"
#ifdef __WIN__
#pragma warning(disable:4786)
#endif

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <list>
#include "Utils.h"
#include "TChange.h"
#include "HAException.h"

using namespace std;

#include "MacroAnalyser.h"

// ----------------------------------------------------------------------------
// MacroAnalyser::MacroAnalyser
// ----------------------------------------------------------------------------
//
MacroAnalyser::MacroAnalyser(string filebase, string filecurrent, vector<string> basebundle, vector<string> currentbundle)
:iBaseFile(filebase), iCurrentFile(filecurrent), iBaseBundle(basebundle), iCurrentBundle(currentbundle)
{
}

// ----------------------------------------------------------------------------
// MacroAnalyser::~MacroAnalyser
// ----------------------------------------------------------------------------
//
MacroAnalyser::~MacroAnalyser(void)
{
}

// ----------------------------------------------------------------------------
// MacroAnalyser::Analyse
// ----------------------------------------------------------------------------
//
void MacroAnalyser::Analyse(list<string>& aMacroFiles)
{  
    ifstream basefile(iBaseFile.c_str(), ios::in);
    ifstream currentfile(iCurrentFile.c_str(), ios::in);

    map<string, list<pair<pair<string, string>,string> > > baseline;
    map<string, list<pair<pair<string, string>,string> > > current;
    
    baseline = parseMacros(basefile, iBaseBundle, &iRemoved, true);
    current = parseMacros(currentfile, iCurrentBundle);
    
    vector<string>::iterator curbegin = iCurrentBundle.begin();
    vector<string>::iterator curend = iCurrentBundle.end();
    vector<string>::iterator basebegin = iBaseBundle.begin();
    vector<string>::iterator baseend = iBaseBundle.end();
    map<string, TChange<list<pair<string,string> > > >::iterator removedbegin;
    for(;curbegin != curend; curbegin++)
    {
        string curfilename = *curbegin;
        string basefilename;
        if( basebegin != iBaseBundle.end() )
        {
            basefilename = *basebegin;
        }
        else
        {
            continue;
        }
        removedbegin = iRemoved.find(basefilename);
        if (removedbegin != iRemoved.end())
        {
			// serach the macro value elemnts with key value "key_val_CURRENT",defined for Current file
			map<string, list<pair<pair<string, string>,string> > >::iterator cur = current.find(KEY_VAL_CURRENT);
            if (cur == current.end())       // We need to check if there is a list with given filename
                                            // because they aren't created in situation where there isn't any macros
            {                               // specific to that file or the include guard for the file is duplicated.
                list<pair<pair<string, string>,string> > tempvar;
                pair<string, list<pair<pair<string, string>,string> > > tempvar2(curfilename, tempvar);
                pair<map<string, list<pair<pair<string, string>,string> > >::iterator, bool > pos = current.insert(tempvar2);
                if (pos.second == true)
                {
                    cur = pos.first;
                } else
                {                           // this shouldn't be happening
                    throw HAException("Problems ahead.");
                }
            }
            map<string, list<pair<pair<string, string>,string> > >::iterator base = baseline.find(basefilename);
            findDuplicates(basefilename,curfilename,base->second, cur->second, removedbegin->second, iBaseDuplicates, iCurrentDuplicates);
            
			//maintain a cache list of files with macros
			if(base->second.size() > 0)
				aMacroFiles.push_back(toLowerCaseWin(base->first));
			if(cur->second.size() > 0)
				aMacroFiles.push_back(toLowerCaseWin(curfilename));
			
			// After next loop we will have in list only those which has been either removed or changed
            list<pair<pair<string, string>,string> >::iterator begin = cur->second.begin();
            list<pair<pair<string, string>,string> >::iterator end = cur->second.end();
            for(;begin != end; begin++)
            {
				list<pair<string,string> >::iterator place = FindFromList(begin->first.first, removedbegin->second.GetValue());
                if (place != removedbegin->second.GetValue().end())
                {
                    removedbegin->second.GetValue().erase(place);  // we found macro with same name so we can remove it
                    list<pair<pair<string, string>,string> > baselist = base->second;
					list<pair<pair<string, string>,string> >::iterator found = FindFromList(begin->first.first, baselist, ELeftValue);
                    if (found != baselist.end())
                    {
						if (found->first.second != begin->first.second)
                        {   // Contents of macro are different so we need to add the macro to changed list
                            pair<pair<string,string>,string> changes;
							changes.first.first = found->first.second;
							changes.first.second = begin->first.second;
							changes.second = begin->second;
							pair<string, pair<pair<string, string>,string> > param(begin->first.first,changes);
                            map<string, TChange<map<string, pair<pair<string, string>,string> > > >::iterator file = iChanged.find(basefilename);
                            if (file != iChanged.end())
                            {
                                file->second.GetValue().insert(param);
                            } else
                            {
                                map<string, pair<pair<string, string>,string> > tempvar;
                                tempvar.insert(param);
                                TChange<map<string, pair<pair<string, string>,string> > > tempvar2(basefilename, curfilename, tempvar);
                                pair<string, TChange<map<string, pair<pair<string, string>,string> > > > tempvar3(basefilename, tempvar2);
                                iChanged.insert(tempvar3);
                            }
                        }
                    }
                }
            }
            removedbegin->second.SetCurrent(curfilename);
        }
        basebegin++;
    }  
}

// ----------------------------------------------------------------------------
// MacroAnalyser::FindMacro
// ----------------------------------------------------------------------------
//
pair<string, string> MacroAnalyser::FindMacro(string aLine)
{
    pair<string, string> ret;
    string::size_type pos = aLine.find_first_of(" \t");
    while(pos != string::npos && (aLine.at(pos) == ' ' || aLine.at(pos) == '\t') )
        pos++;
    string::size_type param_pos = pos ;
    pos = aLine.find_first_of(" \t(",param_pos);
    string value;
    if (pos != string::npos && aLine.at(pos) == '(')
    {
        pos = aLine.find(')',pos);
        if(pos !=string::npos )
            pos++;
    }
    if (pos != string::npos && pos < aLine.size())
    {   // only if there is stuff after name of macro we put it to value
        size_t value_pos = (unsigned int)pos + 1;
        value = aLine.substr(value_pos);
        value = trimWhiteSpace(value);
        if (value.size() != 0)
        {
            value_pos = value.find_first_not_of(" \t");
            if (value_pos != string::npos && value_pos != 0)
            {
                value = value.substr(value_pos);
            }
        }
    } else
    {   // otherwise we put empty value
        value = "";
    }

    string param = aLine.substr(param_pos, pos - param_pos);
    ret = pair<string,string>(param,value);
    return ret;
}

// ----------------------------------------------------------------------------
// MacroAnalyser::getRemoved
// ----------------------------------------------------------------------------
//
map<string, TChange<list<pair<string,string> > > >& MacroAnalyser::getRemoved()
{
    return iRemoved;
}

// ----------------------------------------------------------------------------
// MacroAnalyser::FindMacro
// readParameters should be called so that first is called baseline command
// and after that current command, otherwise it doesn't work
// ----------------------------------------------------------------------------
//
map<string, TChange<map<string, pair<pair<string, string>,string> > > >& MacroAnalyser::getChanged()
{
    return iChanged;
}

// ----------------------------------------------------------------------------
// MacroAnalyser::getBaseDuplicates
// ----------------------------------------------------------------------------
//
map<string, vector<pair<string,string> > >& MacroAnalyser::getBaseDuplicates()
{
    return iBaseDuplicates;
}

// ----------------------------------------------------------------------------
// MacroAnalyser::getCurrentDuplicates
// ----------------------------------------------------------------------------
//
map<string, vector<pair<string,string> > >& MacroAnalyser::getCurrentDuplicates()
{
    return iCurrentDuplicates;
}

// ----------------------------------------------------------------------------
// MacroAnalyser::parseMacros
// ----------------------------------------------------------------------------
//
map<string, list<pair<pair<string, string>,string> > > MacroAnalyser::parseMacros(ifstream& aFile, vector<string>& bundlefiles, map<string, TChange<list<pair<string,string> > > >* aRemoved, bool isbaseline)
{
    // ret, is the file and related set of macros returned by the function
    map<string, list<pair<pair<string, string>,string> > > ret;
    vector<pair<string, string> > files;
    vector<string>::iterator bundlefile = bundlefiles.begin();
    vector<string>::iterator bundleend = bundlefiles.end();
    for(; bundlefile != bundleend; bundlefile++)
    {
        pair<string, string> bothcase(toLowerCaseWin(*bundlefile), *bundlefile);
        files.push_back(bothcase);
    }
    // Read macrofiles as lines
    string cline;
    string levelname = "";
    bool isFileMatched = false;
    while((cline = getLine(aFile)) != KEmpty)
    {
        string line = cline;
		char tempNo[6];
		string lineNo;
        unsigned int length = (unsigned int)line.length();
        if (length > 6 && line.at(0) == '#' && line.at(1) == ' ')
        {   // we may have found a file descriptor: '# xx "path/to/file.h"' or
            // '# xx "path/to/file.h" 1' or '# xx "path/to/file.h" 2'
            unsigned int index;
			int count = 0;
            for(index = 2; index < length; index++)
            {
                char ch = line.at(index);
                if (ch < '0' || ch > '9')
					break;
				else
				{
					tempNo[count] = ch;
					count++;
				}
            }
			tempNo[count] = '\0';
			lineNo.copy(tempNo,strlen(tempNo));
            index++;
            if (index < length)
            {
                unsigned int index2 = index + 1;
                for(; index2 < length; index2++)
                {
                    char ch = line.at(index2);
                    if (ch == '"') break;
                }
                if (length > index2 + 1)
                {
                    levelname = line.substr(index + 1, index2 - index - 1);
                } else
                {
                    levelname = line.substr(index + 1, length - index - 2);
                }
                string correctedname = "";
                string::size_type start = 0;
                string::size_type pos = levelname.find_first_of("\\/");
                while(pos != string::npos)
                {
                    correctedname += levelname.substr(start, pos - start);
                    correctedname += DIR_SEPARATOR;
                    start = pos + 1;
                    pos = levelname.find_first_of("\\/", start);
                }
                // found the levelname; filename
                correctedname += levelname.substr(start);
                levelname = toLowerCaseWin(correctedname);
				isFileMatched = false;

				// For baseline, need to list only those macros,which are belonging to bundle files. 
				 if (isbaseline == true)
				 {
					 vector<pair<string, string> >::iterator begin = files.begin();
					 vector<pair<string, string> >::iterator end = files.end();					 
					 for(; begin != end; begin++)
					 {
						 if (begin->first == levelname)
						 {
							 isFileMatched = true;
							 levelname = begin->second;
							 break;
						 }
					 }
				 }
				 else // current macro file, so search macros in the entire file
				 {
					 isFileMatched = true;
					 levelname = KEY_VAL_CURRENT; // set a generic key_val for all current macros
				 }
            }
        }
        else if (isFileMatched == true && length > 8 && line.substr(0, 8) == "#define ")
        {   // we found preprocessor directive #define
            pair<string,string> macro = FindMacro(line);
            // 'ret' contains the set of files and associated macros to be returned from function
            map<string, list<pair<pair<string, string>,string> > >::iterator found = ret.find(levelname);
            bool exist = false;
            if( found != ret.end())
            {
				list< pair<pair<string, string>,string> >::iterator start = found->second.begin();
				list< pair<pair<string, string>,string> >::iterator end = found->second.end();
				
                // check if this macro is already defined, within this file(levelname)
                while(start != end)
                {
					if( macro.first == start->first.first )
                    exist = true;
                    start++;
                }
            }
            // if macro not found, add it to the list of macros associated with the current file
            if( !exist )
            {
				pair<pair<string,string>,string> tempVal(macro,tempNo);
				// if file already defined, only add the macro
                if (found != ret.end())
                {
					found->second.push_back(tempVal);
                }
                // else add both filename and asssociated macro
                else
                {				
                    list<pair<pair<string, string>,string> > values;
					values.push_back(tempVal);
                    pair<string, list<pair<pair<string, string>,string> > > newfile(levelname, values);
                    ret.insert(newfile);
                }
                // perform the same test as above on aRemoved data structure
                // this will be used for finding any duplicate macro definitions
                if (isbaseline == true)
                {
					pair<string,string> temp(macro.first,tempNo);
                    map<string, TChange<list<pair<string,string> > > >::iterator found2 = aRemoved->find(levelname);
                    if (found2 != aRemoved->end())
                    {
                        found2->second.GetValue().push_back(temp);
                    } else
                    {
                        list<pair<string,string> > values;
                        values.push_back(temp);
                        TChange<list<pair<string,string> > > tempvar(levelname, values);
                        pair<string, TChange<list<pair<string,string> > > > newfile(levelname, tempvar);
                        aRemoved->insert(newfile);
                    }
                }
             } //if(!exist)
        }
    }
    return ret;
}

// ----------------------------------------------------------------------------
// MacroAnalyser::findDuplicates
// ----------------------------------------------------------------------------
//
bool MacroAnalyser::findDuplicates(const string& basefilename, const string& currentfilename, list<pair<pair<string, string>,string> >& aBaseline, list<pair<pair<string, string>,string> >& aCurrent, TChange<list<pair<string,string> > >& aRemovedList, map<string, vector<pair<string,string> > >& baselinedup, map<string, vector<pair<string,string> > >& currentdup)
{
    bool ret = false;
    map<pair<string,string>, int> dups;
    list<pair<pair<string, string>,string> >::iterator begin = aBaseline.begin();
    list<pair<pair<string, string>,string> >::iterator end = aBaseline.end();
    bool found = false;
    while(begin != end)
    {
        found = false;
        list<pair<pair<string, string>,string> >::iterator duplicates = begin;
        duplicates++;
		string original = begin->first.first;
		pair<string,string> original1(original,begin->second);
        while(duplicates != end)
        {
			if (original == duplicates->first.first)
            {
                found = true;
                pair<pair<string,string>, int> tempvar(original1, 1);
                dups.insert(tempvar);
                duplicates = aBaseline.erase(duplicates);
            } else
            {
                duplicates++;
            }
        }
        if(found)
        {
            ret = true;
            aRemovedList.GetValue().remove(original1);
            begin = aBaseline.erase(begin);
        } 
		    begin++;
    }

    if (dups.size() != 0)
    {
        vector<pair<string,string> > values;
        values.reserve(dups.size());
        map<pair<string,string>, int>::iterator dupsbegin = dups.begin();
        map<pair<string,string>, int>::iterator dupsend = dups.end();
        for(; dupsbegin != dupsend; dupsbegin++)
        {
            values.push_back(dupsbegin->first);
        }
        pair<string, vector<pair<string,string> > > tempvar(basefilename, values);
        baselinedup.insert(tempvar);
    }

    dups.clear();
    begin = aCurrent.begin();
    end = aCurrent.end();
    while(begin != end)
    {
        found = false;
        list<pair<pair<string, string>,string> >::iterator duplicates = begin;
        duplicates++;
		string original = begin->first.first;
		pair<string,string> original1(original,begin->second);
        while(duplicates != end)
        {
			if (original == duplicates->first.first)
            {
                found = true;
                pair<pair<string,string>, int> tempvar(original1, 1);
                dups.insert(tempvar);
                duplicates = aCurrent.erase(duplicates);
            } else
            {
                duplicates++;
            }
        }
        if (found == true)
        {
            ret = true;
            begin = aCurrent.erase(begin);
        } else
        {
            begin++;
        }
    }

    if (dups.size() != 0)
    {
        vector<pair<string,string> > values;
        values.reserve(dups.size());
        map<pair<string,string>, int>::iterator dupsbegin = dups.begin();
        map<pair<string,string>, int>::iterator dupsend = dups.end();
        for(; dupsbegin != dupsend; dupsbegin++)
        {
			string macroname = dupsbegin->first.first;
			pair<string,string> tempVal (macroname,dupsbegin->first.second);
            begin = aBaseline.begin();
            end = aBaseline.end();
            for(; begin != end; begin++)
            {   // we have to remove all macros, which have duplicates in current, from baseline
                // so that there won't be unnecessary warnings of removed macros
                if (begin->first.first == macroname)
                {
                    aBaseline.erase(begin);
                    break;
                }
            }
            values.push_back(tempVal);
        }
        pair<string, vector<pair<string,string> > > tempvar(currentfilename, values);
        currentdup.insert(tempvar);
    }
    return ret;
}
