/* fileentry_typecheck.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2010-2020 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 "fileentry_typecheck.hh"
#include "fileentry.hh"
#include "aguix/lowlevelfunc.h"
#include "pdatei.h"
#include "condparser.h"
#include "stringbuf.h"
#include "wcfiletype.hh"
#include "magic_db.hh"
#include "simplelist.hh"

FileEntryTypeCheck::FileEntryTypeCheck()
{
}

FileEntryTypeCheck::~FileEntryTypeCheck()
{
}

FileEntryTypeCheck::typecandidate_t FileEntryTypeCheck::runTests( FileEntry &fe,
                                                                  WCFiletype *type,
                                                                  bool skipcontent,
                                                                  CondParser *condparser,
                                                                  unsigned char fbuffer[64],
                                                                  int reads,
                                                                  typecheck_temp_results_t &temp_results )
{
    typecandidate_t erg;
    int minsize, x, y;
    char *tstr1 = NULL;
    
    if ( type == NULL ) return erg;
    if ( condparser == NULL ) return erg;
    
    if ( skipcontent == false ) {
        short *filedesc;
        if ( type->getinternID() == NORMALTYPE ) {
            if ( type->getUseFiledesc() == true ) {
                erg.tests_done++;
                if ( reads > 0 ) {
                    filedesc = type->getFiledesc();
                    minsize = 0;
                    for ( x = 0; x < 64; x++ ) {
                        if ( filedesc[x] > -1 ) minsize = x + 1;
                    }
                    if ( ( reads >= minsize ) && ( minsize > 0 ) ) {
                        y = 0;
                        for ( x = 0; x < reads; x++ ) {
                            if ( filedesc[x] >= 0 ) {
                                if ( filedesc[x] != (short)fbuffer[x] ) {
                                    y = 1;
                                    break;
                                }
                            }
                        }
                        if ( y == 0 ) {
                            erg.accuracy = 3;
                            erg.type = type;
                        }
                    }
                }
            }
            
            if ( erg.type == NULL && type->getUseMagic() == true ) {
                erg.tests_done++;

                int array_pos = 0;

                if ( type->getMagicCompressed() == true ) array_pos += 1;
                if ( type->getMagicMime() == true ) array_pos += 2;

                if ( temp_results.magic_avail[array_pos] == false ) {
                    int flags = 0;
                    if ( type->getMagicCompressed() ) {
                        flags |= MagicDB::DECOMPRESS;
                    }
                    if ( type->getMagicMime() ) {
                        flags |= MagicDB::USE_MIME;
                    }

                    if ( worker_islocal( fe.fullname ) == true ) {
                        temp_results.magic_result[array_pos] = MagicDB::getInstance().getInfo( fe.fullname, flags );
                    } else {
                        temp_results.magic_result[array_pos] = MagicDB::getInstance().getInfo( fbuffer, reads, flags );
                    }
                    temp_results.magic_avail[array_pos] = true;
                }
                
                if ( type->magicMatch( temp_results.magic_result[array_pos] ) == true ) {
                    erg.accuracy = 3;
                    erg.type = type;
                }
            }
        }
    }
    
    /* if no type found then check extended pattern */
    if ( erg.type == NULL ) {
        if ( type->getinternID() == NORMALTYPE ) {
            if ( type->getUseExtCond() == true ) {
                erg.tests_done++;
                condparser->setIgnoreContent( skipcontent );
                if ( condparser->parse( type->getExtCond(), &fe ) == 1 ) {
                    erg.accuracy = 2;
                    erg.type = type;
                }
            }
        }
    }
    
    /* if no type found then check the pattern */
    if ( erg.type == NULL ) {
        if ( type->getinternID() == NORMALTYPE ) {
            if ( type->getUsePattern() == true ) {
                erg.tests_done++;
                if ( type->getPatternUseFullname() == true ) {
                    tstr1 = fe.fullname;
                } else {
                    tstr1 = fe.name;
                }
                if ( type->patternMatchString( tstr1 ) == true ) {
                    erg.accuracy = 1;
                    erg.type = type;
                }
            }
        }
    }

    if ( erg.type ) {
        erg.priority = type->priority();
    }

    return erg;
}

FileEntryTypeCheck::typecandidate_t FileEntryTypeCheck::checkFiletype( FileEntry &fe,
                                                                       const std::list<WCFiletype*> *ftlist,
                                                                       bool skipcontent,
                                                                       CondParser *condparser,
                                                                       unsigned char fbuffer[64],
                                                                       int reads,
                                                                       typecheck_temp_results_t &temp_results )
{
    WCFiletype *ft = NULL;
    typecandidate_t erg;
    const std::list<WCFiletype*> *subtypes = NULL;
    std::list<WCFiletype*>::const_iterator it1;

    if ( ftlist == NULL ) return erg;
    if ( condparser == NULL ) return erg;
    if ( fbuffer == NULL ) return erg;
  
    if ( ftlist->size() < 1 ) {
        return erg;
    }
    if ( ( S_ISDIR( fe.statbuf.mode ) ) && ( fe.isCorrupt == false ) ) {
        // Wirklich ein Verzeichnis
        return erg;
    }

    if ( fe.isLink == false && fe.isCorrupt == true ) {
        return erg;
    }

    for ( it1 = ftlist->begin(); it1 != ftlist->end(); it1++ ) {
        ft = *it1;
        typecandidate_t cand1 = runTests( fe, ft, skipcontent, condparser, fbuffer, reads, temp_results );
        if ( ( cand1.type != NULL ) || ( cand1.tests_done < 1 ) ) {
            // check subtypes if we had a hit or did no tests
            subtypes = ft->getSubTypeList();
            if ( subtypes != NULL ) {
                if ( subtypes->size() > 0 ) {
                    typecandidate_t  cand2 = checkFiletype( fe, subtypes, skipcontent, condparser, fbuffer, reads, temp_results );

                    // don't check against priority of parent since
                    // children will always win, or otherwise would
                    // never win
                    if ( cand2.accuracy > 0 ) {
                        // subtypes are always more important
                        cand1.accuracy = cand2.accuracy;
                        cand1.type = cand2.type;
                        cand1.priority = ft->priority() + cand2.priority;
                    }
                }
            }
        }
        if ( cand1.type != NULL ) {
            if ( erg.type == NULL ||
                 cand1.priority < erg.priority ||
                 ( cand1.priority == erg.priority &&
                   cand1.accuracy > erg.accuracy ) ) {
                erg.accuracy = cand1.accuracy;
                erg.type = cand1.type;
                erg.priority = cand1.priority;
            }
        }
    }

    return erg;
}

WCFiletype* FileEntryTypeCheck::checkFiletype( FileEntry &fe,
                                               List *ftlist, bool skipcontent, CondParser *condparser )
{
    unsigned char fbuffer[64];
    int reads = 0;
    int id;
    WCFiletype *ft = NULL, *unknownft = NULL;
    CondParser *localparser = NULL;
    typecandidate_t erg;
    const std::list<WCFiletype*> *subtypes = NULL;
    typecheck_temp_results_t temp_results;

    if ( ftlist == NULL ) return NULL;
  
    // TODO: Momentan durchsuche ich jedesmal die Liste nach unknowntyp
    //       Da ich nun dazuübergegangen bin, die Position nicht mehr zu
    //       garantieren, muss ich suchen
    //       Vielleicht wäre eine Variable möglich die bei Änderung der Liste 
    //       geändert wird und so Sofortzugriff erlaubt
    //       wconfig->getunknowntype() geht AUF JEDEN FALL nicht, die ftlist
    //       NICHT die gleiche wie im wconfig ist (wegen Thread-behandlung)
    //       (zumindest im Moment nicht, bei einer thread-safe liste und FTs
    //        könnte dies wieder zurückgenommen werden)
    id = ftlist->initEnum();
    unknownft = (WCFiletype*)ftlist->getFirstElement( id );
    while ( unknownft != NULL ) {
        if ( unknownft->getinternID() == UNKNOWNTYPE ) break;
        unknownft = (WCFiletype*)ftlist->getNextElement( id );
    }
    ftlist->closeEnum( id );

    // Vorbesetzen, damit immer unknown typ kommt, selbst wenn Datei nicht den Anforderungen
    //  fuer Dateityperkennung genuegt
    fe.filetype = unknownft;
    if ( ftlist->size() < 4 ) {
        return fe.filetype;
    }
    if ( ( S_ISDIR( fe.statbuf.mode ) ) && ( fe.isCorrupt == false ) ) {
        // Wirklich ein Verzeichnis
        return fe.filetype;
    }
  
    if ( fe.isReg() == false ) skipcontent = true;
    if ( fe.statbuf.size < 1 ) skipcontent = true;

    if ( skipcontent == false ) {
        reads = -1;
        PDatei pfp;
        if ( pfp.open( fe.fullname ) == 0 ) {
            reads = pfp.read( fbuffer, 64 );
        }
    }

    if ( condparser == NULL ) {
        localparser = new CondParser();
        condparser = localparser;
    }

    id = ftlist->initEnum();
    ft = (WCFiletype*)ftlist->getFirstElement( id );
    while ( ft != NULL ) {
        typecandidate_t cand1 = runTests( fe, ft, skipcontent, condparser, fbuffer, reads, temp_results );
        if ( ( cand1.type != NULL ) || ( cand1.tests_done < 1 ) ) {
            // check subtypes if we had a hit or did no tests
            subtypes = ft->getSubTypeList();
            if ( subtypes != NULL ) {
                if ( subtypes->size() > 0 ) {
                    typecandidate_t cand2 = checkFiletype( fe, subtypes, skipcontent, condparser, fbuffer, reads, temp_results );

                    // don't check against priority of parent since
                    // children will always win, or otherwise would
                    // never win
                    if ( cand2.accuracy > 0 ) {
                        // subtypes are always more important
                        cand1.accuracy = cand2.accuracy;
                        cand1.type = cand2.type;
                        cand1.priority = ft->priority() + cand2.priority;
                    }
                }
            }
        }
        if ( cand1.type != NULL ) {
            if ( erg.type == NULL ||
                 cand1.priority < erg.priority ||
                 ( cand1.priority == erg.priority &&
                   cand1.accuracy > erg.accuracy ) ) {
                erg.accuracy = cand1.accuracy;
                erg.type = cand1.type;
                erg.priority = cand1.priority;
            }
        }
        ft = (WCFiletype*)ftlist->getNextElement( id );
    }

    if ( erg.type  == NULL ) {
        fe.filetype = unknownft;
    } else {
        fe.filetype = erg.type;
    }
    ftlist->closeEnum( id );
  
    if ( fe.filetype != NULL ) {
        ft = fe.filetype;
        while ( ft->getColorMode() == ft->PARENTCOL ) {
            ft = ft->getParentType();
            if ( ft == NULL ) break;
        }
        if ( ft != NULL ) {
            if ( ft->getColorMode() == ft->CUSTOMCOL ) {
                fe.setCustomColors( ft->getCustomColor( 0 ),
                                    ft->getCustomColor( 1 ) );
            } else if ( ft->getColorMode() == ft->EXTERNALCOL ) {
                char *o1 = condparser->getOutput( ft->getColorExt(), &fe );
                int tfg[4], tbg[4], z;

                if ( o1 != NULL ) {
                    z = sscanf( o1, "%d %d %d %d %d %d %d %d",
                                &tfg[0], &tbg[0], &tfg[1], &tbg[1],
                                &tfg[2], &tbg[2], &tfg[3], &tbg[3] );
                    if ( z == 8 ) {
                        fe.setCustomColors( tfg, tbg );
                    }
                    _freesafe( o1 );
                }
            }
        }
    }

    if ( ! temp_results.magic_avail[0] ) {
        if ( worker_islocal( fe.fullname ) == true ) {
            temp_results.magic_result[0] = MagicDB::getInstance().getInfo( fe.fullname, 0 );
        } else {
            temp_results.magic_result[0] = MagicDB::getInstance().getInfo( fbuffer, reads, 0 );
        }
        temp_results.magic_avail[0] = true;
    }
    fe.setFiletypeFileOutput( temp_results.magic_result[0] );

    std::string mime_type;
    if ( worker_islocal( fe.fullname ) == true ) {
        mime_type = MagicDB::getInstance().getInfo( fe.fullname, MagicDB::USE_MIME_TYPE );
    } else {
        mime_type = MagicDB::getInstance().getInfo( fbuffer, reads, MagicDB::USE_MIME_TYPE );
    }

    fe.setMimeType( mime_type );
    
    // free temporary buffers
    if ( localparser != NULL ) delete localparser;

    fe.freeContentBuf();
    fe.freeOutputBuf();
  
    return fe.filetype;
}
