/***************************************************************
 * Name:      ThreadSearchThread
 *
 * Purpose:   This class implements the search worker thread.
 *            It sends events to the view when one or more
 *            search pattern is (are) found in a file.
 *            It derives from wxDirTraverser to test thread
 *            cancel when searching in directory.
 *            One event/file is sent.
 *
 * Author:    Jerome ANTOINE
 * Created:   2007-10-08
 * Copyright: Jerome ANTOINE
 * License:   GPL
 **************************************************************/

#if defined(CB_PRECOMP)
#include "sdk.h"
#endif
//#include "sdk.h"
#ifndef CB_PRECOMP
    #include "cbeditor.h"
#endif

#include "cbauibook.h"

#include "ThreadSearchThread.h"
#include "ThreadSearchEvent.h"
#include "TextFileSearcher.h"
#include "snippetsconfig.h"

#include "scbeditor.h"
#include "seditormanager.h"

ThreadSearchThread::ThreadSearchThread(ThreadSearchView*           pThreadSearchView,
                                       const ThreadSearchFindData& findData)
{
    m_pThreadSearchView = pThreadSearchView;
    m_FindData          = findData;

    // If wxDIR_IGNORE is used, we don't recurse in sub directories during directory search
    m_DefaultDirResult  = (findData.GetRecursiveSearch() == true) ? wxDIR_CONTINUE : wxDIR_IGNORE;

    // File patterns separator is ';'
    m_Masks             = GetArrayFromString(m_FindData.GetSearchMask());
    if ( m_Masks.GetCount() == 0 )
    {
        m_Masks.Add(_T("*"));
    }
    m_pTextFileSearcher = TextFileSearcher::BuildTextFileSearcher(findData.GetFindText(),
                                                                  findData.GetMatchCase(),
                                                                  findData.GetStartWord(),
                                                                  findData.GetMatchWord(),
                                                                  findData.GetRegEx());
    if (!m_pTextFileSearcher)
    {
        sThreadSearchEvent event(wxEVT_S_THREAD_SEARCH_ERROR, -1);
        event.SetString(_T("TextFileSearcher could not be instantiated."));

        // Using wxPostEvent, we avoid multi-threaded memory violation.
        wxPostEvent( m_pThreadSearchView,event);
    }
}


ThreadSearchThread::~ThreadSearchThread()
{
    //dtor
    if ( m_pTextFileSearcher != NULL )
    {
        delete m_pTextFileSearcher;
    }
}
// ----------------------------------------------------------------------------
void *ThreadSearchThread::Entry()
// ----------------------------------------------------------------------------
{
    // Tests if we have a working searcher object.
    // Cancel search if it is not the case
    if ( m_pTextFileSearcher == NULL )
        return 0;

    size_t i = 0;

    // For now, we look for all paths for the different search scopes
    // and store them in a sorted array to avoid passing several times
    // the same file.
    // This will be changed to avoid consuming a lot of memory (parsing
    // form C:\ and storing all paths...). Aim is to avoid the use of the
    // array for storing items.

    // Search in directory files ?
    if ( m_FindData.MustSearchInDirectory() == true )
    {
        int flags = wxDIR_FILES | wxDIR_DIRS | wxDIR_DOTDOT;
        flags    |= m_FindData.GetHiddenSearch() ? wxDIR_HIDDEN : 0;

        wxDir Dir(m_FindData.GetSearchPath());
        Dir.Traverse(*(static_cast<wxDirTraverser*>(this)), wxEmptyString, flags);

        // Tests thread stop (cancel search, app shutdown)
        if ( TestDestroy() == true ) return 0;
    }

    // Search in workspace files ?
    if ( m_FindData.MustSearchInWorkspace() == true )
    {
        ProjectsArray* pProjectsArray = Manager::Get()->GetProjectManager()->GetProjects();
        for ( size_t i=0; i < pProjectsArray->GetCount(); ++i )
        {
            AddSnippetFiles(m_FilePaths, *pProjectsArray->Item(i));
            if ( TestDestroy() == true ) return 0;
        }
    }
    else if ( m_FindData.MustSearchInProject() == true ) do
    {
        //(pecan 2008/4/11)
        // Search in "Snippets Tree" has replaced old Search in "Project Tree" check box
        wxString codeSnippetsIndex = GetThreadSearchView()->GetThreadSearchPlugin()->GetCodeSnippetsIndex();
        if (not codeSnippetsIndex.IsEmpty() )
            AddNewItem(m_FilePaths, codeSnippetsIndex);

        //// Tell CodeSnippetsTreeCtrl to fill global fileLink array with FileLinks
        //CodeSnippetsEvent evt(wxEVT_CODESNIPPETS_GETFILELINKS, 0);
        //evt.SetSnippetString(_T("Testing GetFileLinks"));
        //evt.ProcessCodeSnippetsEvent(evt);
        //if ( TestDestroy() == true ) return 0;

        // Add CodeSnippet FileLinks array to searched files
        FileLinksMapArray& hash = GetConfig()->GetFileLinksMapArray();
        for (FileLinksMapArray::iterator it = hash.begin(); it != hash.end(); it++)
            AddNewItem(m_FilePaths, it->first);
        break;
        // ----- removed --- notice the break; statement above --- removed ----- //(pecan 2008/4/11)
        // Search in project files ?
        // Necessary only if not already parsed in workspace part
        cbProject* pProject = Manager::Get()->GetProjectManager()->GetActiveProject();
        if ( pProject != NULL )
        {
            AddSnippetFiles(m_FilePaths, *pProject);
            if ( TestDestroy() == true ) return 0;
        }
    }while(false);

    // Tests thread stop (cancel search, app shutdown)
    if ( TestDestroy() == true ) return 0;

    // Open files
    if ( m_FindData.MustSearchInOpenFiles() == true )
    {
        //-EditorManager* pEdManager = Manager::Get()->GetEditorManager();
        SEditorManager* pEdManager = GetConfig()->GetEditorManager(m_pThreadSearchView);
        for (i = 0; i < (size_t)pEdManager->GetNotebook()->GetPageCount(); ++i)
        {
            ScbEditor* pEditor = pEdManager->GetBuiltinEditor(i);
            if ( pEditor != NULL )
            {
                AddNewItem(m_FilePaths, pEditor->GetFilename());
            }
        }
    }

    // Tests thread stop (cancel search, app shutdown)
    if ( TestDestroy() == true ) return 0;

    // if the list is empty, leave
    if (m_FilePaths.GetCount() == 0)
    {
        //-cbMessageBox(wxT("No files to search in!"), wxT("Error"), wxICON_WARNING);
        ////(pecan 2008/4/26)
        // DO NOT issue graphics calls from this thread !!!!!!
        sThreadSearchEvent event(wxEVT_S_THREAD_SEARCH_ERROR, -1);
        event.SetString(_T("No files to search.\nCheck options "));
        // Using wxPostEvent, we avoid multi-threaded memory violation.
        wxPostEvent( m_pThreadSearchView,event);
        return 0;
    }

    for ( i = 0; i < m_FilePaths.GetCount(); ++i )
    {
        FindInFile(m_FilePaths[i]);

        // Tests thread stop (cancel search, app shutdown)
        if ( TestDestroy() == true ) return 0;
    }

    return 0;
}

void ThreadSearchThread::OnExit()
{
    // Method is called automatically by wxWidgets framework
    // We inform thread caller about its termination.
    m_pThreadSearchView->OnThreadExit();
}

wxDirTraverseResult ThreadSearchThread::OnDir(const wxString& WXUNUSED(dirName))
{
    // Method is just used to test thread termination (user cancelled) and
    // stop recursive dir traversing if it is not required.
    if ( TestDestroy() == true )
    {
        return wxDIR_STOP;
    }
    return m_DefaultDirResult;
}

wxDirTraverseResult ThreadSearchThread::OnFile(const wxString& fileName)
{
    // Tests thread termination (user cancelled)
    if ( TestDestroy() == true )
    {
        return wxDIR_STOP;
    }

    // Looks if current file matches one of the file patterns
    for (size_t i = 0; i < m_Masks.GetCount(); ++i)
    {
        if ( fileName.Matches(m_Masks[i].c_str() ) )
        {
            // Adds it to list of files to parse
            m_FilePaths.Add(fileName);
            break;
        }
    }

    return wxDIR_CONTINUE;
}

void ThreadSearchThread::FindInFile(const wxString& path)
{
    m_LineTextArray.Empty();

    switch ( m_pTextFileSearcher->FindInFile(path, m_LineTextArray) )
    {
        case TextFileSearcher::idStringFound:
        {
            sThreadSearchEvent event(wxEVT_S_THREAD_SEARCH, -1);
            event.SetString(path);
            event.SetLineTextArray(m_LineTextArray);

            // Using wxPostEvent, we avoid multi-threaded memory violation.
            m_pThreadSearchView->PostThreadSearchEvent(event);
            break;
        }
        case TextFileSearcher::idStringNotFound:
        {
            break;
        }
        case TextFileSearcher::idFileNotFound:
        {
            sThreadSearchEvent event(wxEVT_S_THREAD_SEARCH_ERROR, -1);
            event.SetString(path + _T(" does not exist."));

            // Using wxPostEvent, we avoid multi-threaded memory violation.
            wxPostEvent( m_pThreadSearchView,event);
            break;
        }
        case TextFileSearcher::idFileOpenError:
        {
            sThreadSearchEvent event(wxEVT_S_THREAD_SEARCH_ERROR, -1);
            event.SetString(_T("Failed to open ") + path);

            // Using wxPostEvent, we avoid multi-threaded memory violation.
            wxPostEvent( m_pThreadSearchView,event);
            break;
        }
        default:
        {
        }
    }
}

bool ThreadSearchThread::AddNewItem(wxSortedArrayString& sortedArrayString, const wxString& newItem)
{
    // Adds item to array only if it does not exist
    bool added = false;
    if ( sortedArrayString.Index(newItem.c_str()) == wxNOT_FOUND )
    {
        sortedArrayString.Add(newItem);
        added = true;
    }
    return added;
}

void ThreadSearchThread::AddSnippetFiles(wxSortedArrayString& sortedArrayString, cbProject& project)
{
    // Adds project file paths to array only if they do not already exist.
    // Same path may exist if we parse both open files and project files
    // for examle.
    for (FilesList::iterator it = project.GetFilesList().begin(); it != project.GetFilesList().end(); ++it)
    {
//-        ProjectFile* pf = *it;   //(pecan 2011/12/14)
        AddNewItem(sortedArrayString, (*it)->file.GetFullPath());
        if ( TestDestroy() == true ) return;
    }
}
