/* nm_filetype_thread.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2011,2013 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "nm_filetype_thread.hh"
#include "simplelist.hh"
#include "fileentry.hh"
#include "aguix/lowlevelfunc.h"
#include "datei.h"
#include "fileentry_typecheck.hh"
#include "wcfiletype.hh"
#include "worker.h"
#include "wconfig.h"

NM_Filetype_Thread::NM_Filetype_Thread() : m_worker( NULL ), m_running( 0 ),
                                           m_order( THREAD_STOP ), m_status( THREAD_STOP )
{
    m_filetype_copy.filetypes = new List();
}

NM_Filetype_Thread::~NM_Filetype_Thread()
{
    result_list_clear();

    clear_filetypes();
    delete m_filetype_copy.filetypes;
}

void NM_Filetype_Thread::setWorker( Worker *worker )
{
    m_worker = worker;
}

int NM_Filetype_Thread::slave_checkFiletype( const check_entry_t &entry,
                                             check_filetype_result &res_return )
{
    FileEntry *fe1;
    int pos = -1;
    WCFiletype *ft, *parentft;
    std::vector<unsigned int> v;
    check_filetype_result res;

    res.entry = entry;

    m_filetype_copy.filetype_ex.lock();

    fe1 = new FileEntry();
    fe1->fullname = dupstring( entry.fullname.c_str() );
    fe1->name = Datei::getFilenameFromPath( entry.fullname.c_str() );

    //TODO start check even for corrupt entries without info?
    if( fe1->readInfos() == 0 ) {
        ft = FileEntryTypeCheck::checkFiletype( *fe1, m_filetype_copy.filetypes, entry.dont_check_content, &condparser );
        if ( ft != NULL ) {
            // first get vector with filetype position for each child
            std::vector<unsigned int> *tv = ft->getTypePos();

            if ( tv ) {
                v = *tv;

                // now find root filetype
      
                for ( parentft = ft; parentft->getParentType() != NULL; parentft = parentft->getParentType() );
                pos = m_filetype_copy.filetypes->getIndex( parentft );
                if ( pos >= 0 ) {
                    v.push_back( (unsigned int)pos );
                } else {
                    v.clear();
                }

                delete tv;
            }
        }
    }
    res.ft_index = v;

    if ( fe1->getCustomColorSet() != 0 ) {
        res.custom_color = fe1->getCustomColor();
    }

    if ( ! fe1->getFiletypeFileOutput().empty() ) {
        res.filetype_file_output = fe1->getFiletypeFileOutput();
        res.mime_type = fe1->getMimeType();
    }
    
    delete fe1;

    res_return = res;

    m_filetype_copy.filetype_ex.unlock();
    return 0;
}

int NM_Filetype_Thread::switchOrder( thread_order_t neworder )
{
    if ( m_order != neworder ) {
        m_ordervar_cv.lock();
        m_order = neworder;
        m_ordervar_cv.signal();
        m_ordervar_cv.unlock();

        m_statusvar_cv.lock();
        while ( m_status != neworder ) {
            m_statusvar_cv.wait();
        }
        m_statusvar_cv.unlock();
    }
    return 0;
}

int NM_Filetype_Thread::switchStatus( thread_order_t newstatus )
{
    m_statusvar_cv.lock();
    m_status = newstatus;
    m_statusvar_cv.signal();
    m_statusvar_cv.unlock();
    return 0;
}

int NM_Filetype_Thread::run()
{
    bool ende;
    int erg;

    if ( m_running != 0 ) {
        fprintf( stderr, "Worker: another thread already running!\n");
        return 1;
    }
    m_running = 1;

#ifdef DEBUG
    printf("entering slave handler\n");
#endif

    for( ende = false; ende == false; ) {

        switch ( m_status ) {
            case NM_Filetype_Thread::THREAD_RUN:
#ifdef DEBUG
                printf("waiting for element\n");
#endif

                m_ordervar_cv.lock();
                // wait for next element or stop
                while ( m_entries_to_check.empty() && m_order == NM_Filetype_Thread::THREAD_RUN )
                    m_ordervar_cv.wait();

#ifdef DEBUG
                printf("wait finished\n");
#endif

                if ( m_order != NM_Filetype_Thread::THREAD_RUN ) {
                    // new command
                    // switch mode for new command
                    if ( m_order == NM_Filetype_Thread::THREAD_EXIT ) {
                        switchStatus( NM_Filetype_Thread::THREAD_EXIT );
                        ende = true;
                    } else if ( m_order == NM_Filetype_Thread::THREAD_STOP ) {
                        switchStatus( NM_Filetype_Thread::THREAD_STOP );
                    }
                    m_ordervar_cv.unlock();
                } else {
                    // an element to check
                    check_entry_t entry;

                    if ( ! m_entries_to_check.empty() ) {
                        entry = m_entries_to_check.front();
                        m_entries_to_check.pop_front();
                        erg = 0;
                    } else {
                        erg = 1;
                    }
                    m_ordervar_cv.unlock();

                    if ( erg == 0 && ! entry.fullname.empty() ) {
                        NM_Filetype_Thread::check_filetype_result res;
#ifdef DEBUG
                        //            printf("erkenne %s\n",filename);
#endif
                        slave_checkFiletype( entry, res );
                        m_result_list.lock();
    
                        m_result_list.put_locked( res );
                        m_result_list.unlock();

                        if ( m_worker ) m_worker->wakeupMainLoop();
                    }
                }
                break;
            case NM_Filetype_Thread::THREAD_STOP:
                m_ordervar_cv.lock();
                while ( m_order == NM_Filetype_Thread::THREAD_STOP ) {
                    m_ordervar_cv.wait();
                }
                m_ordervar_cv.unlock();
                switchStatus( NM_Filetype_Thread::THREAD_RUN );
                break;
            default:
                break;
        }
    }
    
#ifdef DEBUG
    printf("leaving slave handler\n");
#endif
    return 0;
}

NM_Filetype_Thread::result_list::result_list()
{
}

NM_Filetype_Thread::result_list::~result_list()
{
    lock();
    while ( isEmpty_locked() == false ) {
        remove_locked();
    }
    unlock();
}

bool NM_Filetype_Thread::result_list::isEmpty_locked()
{
    if ( entries.empty() ) return true;
    return false;
}

int NM_Filetype_Thread::result_list::put_locked( check_filetype_result elem )
{
    entries.push_back( elem );
    return 0;
}

NM_Filetype_Thread::check_filetype_result NM_Filetype_Thread::result_list::remove_locked()
{
    if ( entries.empty() ) {
        abort();
    }

    auto te = entries.front();
    entries.pop_front();
    return te;
}

void NM_Filetype_Thread::result_list::lock()
{
    ex.lock();
}

void NM_Filetype_Thread::result_list::unlock()
{
    ex.unlock();
}

void NM_Filetype_Thread::result_list_clear()
{
    m_result_list.lock();
    while ( m_result_list.isEmpty_locked() == false ) {
        m_result_list.remove_locked();
    }
    m_result_list.unlock();
}

void NM_Filetype_Thread::result_list_lock()
{
    m_result_list.lock();
}

void NM_Filetype_Thread::result_list_unlock()
{
    m_result_list.unlock();
}

NM_Filetype_Thread::check_filetype_result NM_Filetype_Thread::result_list_remove_locked()
{
    if ( m_result_list.isEmpty_locked() ) {
        abort();
    }

    return m_result_list.remove_locked();
}

bool NM_Filetype_Thread::result_list_is_empty_locked()
{
    return m_result_list.isEmpty_locked();
}

void NM_Filetype_Thread::putRequest( const check_entry_t &req )
{
    m_ordervar_cv.lock();

    m_entries_to_check.push_back( req );

    m_ordervar_cv.signal();
    m_ordervar_cv.unlock();
}

void NM_Filetype_Thread::clearRequests()
{
    m_ordervar_cv.lock();
    m_entries_to_check.clear();
    m_ordervar_cv.signal();
    m_ordervar_cv.unlock();
}

void NM_Filetype_Thread::copy_filetypes( List *filetypes )
{
    WCFiletype *ft;
    int id;
  
    m_filetype_copy.filetype_ex.lock();

    clear_filetypes();

    id = filetypes->initEnum();
    ft = (WCFiletype*)filetypes->getFirstElement( id );
    while ( ft != NULL ) {
        m_filetype_copy.filetypes->addElement( ft->duplicate() );
        ft = (WCFiletype*)filetypes->getNextElement( id );
    }
    filetypes->closeEnum( id );
  
    m_filetype_copy.filetype_ex.unlock();
}

void NM_Filetype_Thread::clear_filetypes()
{
    WCFiletype *ft;
    int id;

    m_filetype_copy.filetype_ex.lock();

    id = m_filetype_copy.filetypes->initEnum();
    ft = (WCFiletype*)m_filetype_copy.filetypes->getFirstElement( id );
    while ( ft != NULL ) {
        delete ft;
        m_filetype_copy.filetypes->removeFirstElement();
        ft = (WCFiletype*)m_filetype_copy.filetypes->getFirstElement( id );
    }
    m_filetype_copy.filetypes->closeEnum( id );

    m_filetype_copy.filetype_ex.unlock();
}

WCFiletype *NM_Filetype_Thread::findFiletype( WConfig *config,
                                              const std::vector<unsigned int> &v )
{
    unsigned int in1, p, tp;
    const std::list<WCFiletype*> *subtype;
    std::list<WCFiletype*>::const_iterator it1;
    WCFiletype *ft;
  
    ft = NULL;

    // check tree depth
    p = v.size();
    if ( p > 0 ) {
        // we need at least the root type
        in1 = v[p - 1];  // get position
    
        ft =  (WCFiletype*)config->getFiletypes()->getElementAt( in1 );
        // root type, no problem if it is NULL
    
        // now go for each tree depth
        p = v.size();
        if ( p > 1 ) {
            // we need one more depth at least
      
            p--; // skip root index
      
            do {
                // now for each depth

                p--; // jump to next level
                // p is correct entry in "v"

                if ( ft == NULL ) break;

                // get position in current tree depth
                in1 = v[p];

                // now find subtype
                // because of std::list we need to iterate
                subtype = ft->getSubTypeList();
                if ( subtype == NULL ) ft = NULL;  // oops, index but no subtype
                else {
                    // iterate through subtype list and find "in1" position
                    for ( it1 = subtype->begin(), tp = 0; it1 != subtype->end(); it1++, tp++ ) {
                        if ( tp == in1 ) break;
                    }
	  
                    if ( it1 != subtype->end() ) {
                        // found subtype at index "in1"
                        ft = *it1;
                    } else {
                        ft = NULL;
                    }
                }
                // stop when we processed last depth
            } while ( p > 0 );
        }
    }
    return ft;
}
