/* dircompareop.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2017-2023 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 "dircompareop.hh"
#include "listermode.h"
#include "worker.h"
#include "aguix/awindow.h"
#include "aguix/text.h"
#include "aguix/button.h"
#include "aguix/cyclebutton.h"
#include "aguix/acontainerbb.h"
#include "aguix/textview.h"
#include "aguix/choosebutton.h"
#include "datei.h"
#include "worker_locale.h"
#include "virtualdirmode.hh"
#include "stringcomparator.hh"
#include "dircomparewin.hh"
#include "nwc_path.hh"
#include "commonprefix.hh"
#include "aguix/stringgadget.h"

int DirCompareOp::s_vdir_number = 1;

const char *DirCompareOp::name = "DirCompareOp";

DirCompareOp::DirCompareOp() : FunctionProto()
{
    m_category = FunctionProto::CAT_OTHER;

    m_stop = false;
}

DirCompareOp::~DirCompareOp()
{
}

DirCompareOp*
DirCompareOp::duplicate() const
{
    DirCompareOp *ta = new DirCompareOp();
    return ta;
}

bool
DirCompareOp::isName( const char *str )
{
    if ( strcmp( str, name ) == 0 ) return true; else return false;
}

const char *
DirCompareOp::getName()
{
    return name;
}

int DirCompareOp::compareFile( NWC::FSEntry *my_entry,
                               NWC::FSEntry *other_entry )
{
    Datei my_fh;
    Datei other_fh;

    if ( my_fh.open( my_entry->getFullname().c_str(),
                     "r" ) != 0 ) return 1;
    if ( other_fh.open( other_entry->getFullname().c_str(),
                        "r" ) != 0 ) return 1;

    char *my_buf = (char*)_allocsafe( 128 * 1024 );
    char *other_buf = (char*)_allocsafe( 128 * 1024 );

    int res = 0;

    ssize_t my_read;
    ssize_t other_read;

    size_t total_bytes = my_entry->stat_size();
    size_t total_bytes_read = 0;

    for (;;) {
        size_t bufsize = 128 * 1024;

        if ( m_file_size_limit > 0 && (loff_t)total_bytes_read <= m_file_size_limit ) {
            loff_t remaining = m_file_size_limit - total_bytes_read;

            if ( remaining == 0 ) {
                break;
            }

            if ( remaining < (loff_t)bufsize ) {
                bufsize = remaining;
            }
        }

        my_read = my_fh.read( my_buf, bufsize );
        other_read = other_fh.read( other_buf, bufsize );

        if ( my_read != other_read ) {
            res = 1;
            break;
        }

        if ( my_read < 0 ) {
            res = 1;
            break;
        }

        if ( my_read == 0 ) {
            break;
        }

        if ( memcmp( my_buf, other_buf, my_read ) != 0 ) {
            res = 1;
            break;
        }

        total_bytes_read += my_read;

        if ( total_bytes > 0 ) {
            m_progress_win->setEntryPercent( total_bytes_read * 100 / total_bytes );
        }

        if ( m_progress_win->check() ) {
            m_stop = true;
            res = 1;
            break;
        }
    }
    
    _freesafe( my_buf );
    _freesafe( other_buf );

    return res;
}

int DirCompareOp::compareEntry( NWC::FSEntry *my_entry,
                                NWC::FSEntry *other_entry,
                                std::unique_ptr< NWC::Dir > &my_res_vdir,
                                std::unique_ptr< NWC::Dir > &other_res_vdir,
                                enum compare_key cmp_key )
{
    NWC::Dir *my_dir = dynamic_cast< NWC::Dir * >( my_entry );
    NWC::Dir *other_dir = dynamic_cast< NWC::Dir * >( other_entry );

    m_progress_win->setCurrentEntry( my_entry->getFullname() );
        
    if ( my_dir && other_dir ) {
        auto my_sdir = std::make_shared<NWC::Dir>( *my_dir );
        auto other_sdir = std::make_shared<NWC::Dir>( *other_dir );

        my_sdir->readDir( false );
        other_sdir->readDir( false );

        m_progress_win->setCurrentDir( my_entry->getBasename() );
        
        int res = compareDirs( my_sdir,
                               other_sdir,
                               NULL,
                               NULL,
                               my_res_vdir,
                               other_res_vdir );

        m_progress_win->setCurrentDir( "" );

        if ( m_compare_mode != COMPARE_FILENAME_FILESIZE_MDATE ) return res;

        if ( res != 0 ) return res;

        // directories seem to be equal but check the modification time
        if ( my_entry->stat_lastmod() != other_entry->stat_lastmod() ) {
            return 1;
        }
        return 0;
    }

    if ( my_dir && ! other_dir ) return 1;
    if ( ! my_dir && other_dir ) return 1;

    // both are not dirs so try to compare

    if ( cmp_key == FILESIZE ) {
        return 0;
    } else if ( cmp_key == FILESIZE_CHECKSUM ) {
        return 0;
    }

    if ( ( my_entry->stat_mode() & S_IFMT ) != ( other_entry->stat_mode() & S_IFMT ) ) {
        return 1;
    }

    if ( my_entry->isLink() ) {
        std::string my_dest;
        std::string other_dest;

        if ( my_entry->getDestination( my_dest ) == false ) return 1;
        if ( other_entry->getDestination( other_dest ) == false ) return 1;

        if ( my_dest != other_dest ) return 1;

        return 0;
    } else if ( ! my_entry->isReg() ) {
        if ( my_entry->stat_rdev() != other_entry->stat_rdev() ) {
            return 1;
        }

        return 0;
    }

    // regular file

    if ( m_compare_mode == COMPARE_FILENAME ) {
        return 0;
    }

    if ( my_entry->stat_size() != other_entry->stat_size() ) {
        return 1;
    }

    if ( m_compare_mode == COMPARE_FILENAME_CONTENT ) {
        return compareFile( my_entry, other_entry );
    } else if ( m_compare_mode == COMPARE_FILENAME_FILESIZE_MDATE ) {
        if ( my_entry->stat_lastmod() != other_entry->stat_lastmod() ) {
            return 1;
        }
    }
    
    return 0;
}


int DirCompareOp::key_compare( const compare_element &lhs,
                               const compare_element &rhs,
                               enum compare_key key )
{
    if ( ! lhs.entry->isReg() && rhs.entry->isReg() ) {
        return -1;
    } else if ( lhs.entry->isReg() && ! rhs.entry->isReg() ) {
        return 1;
    } else if ( ! lhs.entry->isReg() && ! rhs.entry->isReg() ) {
        if ( key == FILENAME_IGNORE_CASE ) {
            return StringComparator::compare( lhs.basename,
                                              rhs.basename,
                                              StringComparator::STRING_COMPARE_STRICT_NOCASE);
        } else {
            return StringComparator::compare( lhs.basename,
                                              rhs.basename,
                                              StringComparator::STRING_COMPARE_REGULAR );
        }
    }

    // both are regular files

    if ( key == FILESIZE ) {
        return rhs.entry->stat_size() - lhs.entry->stat_size();
    } else if ( key == FILESIZE_CHECKSUM ) {
        if ( lhs.entry->stat_size() < rhs.entry->stat_size() ) {
            return -1;
        } else if ( lhs.entry->stat_size() > rhs.entry->stat_size() ) {
            return 1;
        } else {
#if defined(HAVE_OPENSSL_EVP_SHA256) || defined(HAVE_OPENSSL_SHA256)
            return memcmp( &lhs.chksum, &rhs.chksum, sizeof( lhs.chksum ) );
#else
            return 0;
#endif
        }
    } else {
        if ( key == FILENAME_IGNORE_CASE ) {
            return StringComparator::compare( lhs.basename,
                                              rhs.basename,
                                              StringComparator::STRING_COMPARE_STRICT_NOCASE);
        } else {
            return StringComparator::compare( lhs.basename,
                                              rhs.basename,
                                              StringComparator::STRING_COMPARE_REGULAR );
        }
    }
}

int DirCompareOp::calc_checksum( compare_element &element ) const
{
    if ( ! element.entry->isReg() ) {
        return 1;
    }

#if defined(HAVE_OPENSSL_EVP_SHA256) || defined(HAVE_OPENSSL_SHA256)
#if defined(HAVE_OPENSSL_EVP_SHA256)
    EVP_MD_CTX *mdctx;
    const EVP_MD *md;
    md = EVP_sha256();

    if ( ! md ) {
        return 1;
    }

    mdctx = EVP_MD_CTX_new();
    if ( ! mdctx ) {
        return 1;
    }

    int res = EVP_DigestInit_ex( mdctx, md, NULL );
#elif defined(HAVE_OPENSSL_SHA256)
    SHA256_CTX c;

    int res = SHA256_Init( &c );
#endif

    if ( res != 1 ) {
        fprintf( stderr, "Failed to init checksum\n" );
#if defined(HAVE_OPENSSL_EVP_SHA256)
        EVP_MD_CTX_free( mdctx );
#endif
        return 1;
    }

    Datei fh;

    if ( fh.open( element.entry->getFullname().c_str(),
                  "r" ) != 0 ) {
#if defined(HAVE_OPENSSL_EVP_SHA256)
        EVP_MD_CTX_free( mdctx );
#endif
        return 1;
    }

    char buf[128 * 1024];

    size_t total_bytes_read = 0;
    size_t total_bytes = element.entry->stat_size();

    bool stopped = false;

    for (;;) {
        size_t bufsize = sizeof( buf );

        if ( m_file_size_limit > 0 && (loff_t)total_bytes_read <= m_file_size_limit ) {
            loff_t remaining = m_file_size_limit - total_bytes_read;

            if ( remaining == 0 ) {
                break;
            }

            if ( remaining < (loff_t)bufsize ) {
                bufsize = remaining;
            }
        }

        ssize_t bytes_read = fh.read( buf, bufsize );

        if ( bytes_read < 0 ) {
            break;
        }

        if ( bytes_read == 0 ) {
            break;
        }

#if defined(HAVE_OPENSSL_EVP_SHA256)
        res = EVP_DigestUpdate( mdctx, buf, bytes_read );
#elif defined(HAVE_OPENSSL_SHA256)
        res = SHA256_Update( &c,
                             buf, bytes_read );
#endif
        if ( res != 1 ) {
            fprintf( stderr, "Failed to update checksum\n" );
        }

        total_bytes_read += bytes_read;

        if ( total_bytes > 0 ) {
            m_progress_win->setEntryPercent( total_bytes_read * 100 / total_bytes );
        }

        if ( m_progress_win->check() ) {
            stopped = true;
            break;
        }
    }

#if defined(HAVE_OPENSSL_EVP_SHA256)
    res = EVP_DigestFinal_ex(mdctx, &element.chksum[0], NULL);
    EVP_MD_CTX_free( mdctx );
#elif defined(HAVE_OPENSSL_SHA256)
    res = SHA256_Final( &element.chksum[0], &c );
#endif
    if ( res != 1 ) {
        fprintf( stderr, "Failed to get checksum\n" );
    }

    if ( stopped ) {
        return -1;
    }
#endif
    return 0;
}

int DirCompareOp::compareDirs( std::shared_ptr< NWC::Dir > my_dir,
                               std::shared_ptr< NWC::Dir > other_dir,
                               std::vector< bool > *my_equal,
                               std::vector< bool > *other_equal,
                               std::unique_ptr< NWC::Dir > &my_res_vdir,
                               std::unique_ptr< NWC::Dir > &other_res_vdir )
{
    size_t my_pos = 0;
    size_t other_pos = 0;
    int unequal_count = 0;

    if ( my_equal ) {
        my_equal->clear();
        my_equal->resize( my_dir->size() );
    }

    if ( other_equal ) {
        other_equal->clear();
        other_equal->resize( other_dir->size() );
    }

    m_progress_win->addEntriesToDo( my_dir->size() );
    m_progress_win->addEntriesToDo( other_dir->size() );

    bool my_is_virtual = false;
    bool other_is_virtual = false;
    std::string my_common_prefix;
    std::string other_common_prefix;

    if ( my_dir->isVirtual() ) {
        my_is_virtual = true;

        CommonPrefix cp;

        for ( my_pos = 0; my_pos < my_dir->size() && m_stop == false; my_pos++ ) {
            cp.updateCommonPrefix( my_dir->getEntryAtPos( my_pos )->getFullname() );
        }

        my_common_prefix = cp.getCommonPrefix();
    }

    if ( other_dir->isVirtual() ) {
        other_is_virtual = true;

        CommonPrefix cp;

        for ( other_pos = 0; other_pos < other_dir->size() && m_stop == false; other_pos++ ) {
            cp.updateCommonPrefix( other_dir->getEntryAtPos( other_pos )->getFullname() );
        }

        other_common_prefix = cp.getCommonPrefix();
    }

    std::vector< compare_element > my_dir_elements;
    std::vector< compare_element > other_dir_elements;

    for ( my_pos = 0; my_pos < my_dir->size(); my_pos++ ) {
        compare_element ce( my_dir->getEntryAtPos( my_pos ),
                            my_pos );
        if ( my_is_virtual ) {
            ce.basename = NWC::Path::get_extended_basename( my_common_prefix,
                                                            ce.entry->getFullname() );
        } else {
            ce.basename = ce.entry->getBasename();
        }
        my_dir_elements.push_back( ce );
    }
    for ( other_pos = 0; other_pos < other_dir->size(); other_pos++ ) {
        compare_element ce( other_dir->getEntryAtPos( other_pos ),
                            other_pos );
        if ( other_is_virtual ) {
            ce.basename = NWC::Path::get_extended_basename( other_common_prefix,
                                                            ce.entry->getFullname() );
        } else {
            ce.basename = ce.entry->getBasename();
        }
        other_dir_elements.push_back( ce );
    }

    enum compare_key cmp_key;

    switch ( m_compare_mode ) {
        case COMPARE_FILESIZE:
            cmp_key = FILESIZE;
            break;
        case COMPARE_CHECKSUM:
            cmp_key = FILESIZE_CHECKSUM;
            break;
        default:
            cmp_key = m_ignore_file_name_case ? FILENAME_IGNORE_CASE : FILENAME;
    }

    if ( m_compare_mode == COMPARE_CHECKSUM ) {
        for ( auto &e : my_dir_elements ) {
            if ( calc_checksum( e ) < 0 ) {
                m_stop = true;
                break;
            }
            m_progress_win->finishedEntry();
        }
        if ( ! m_stop ) { 
            for ( auto &e : other_dir_elements ) {
                if ( calc_checksum( e ) < 0 ) {
                    m_stop = true;
                    break;
                }
                m_progress_win->finishedEntry();
            }
        }
    }

    std::sort( my_dir_elements.begin(),
               my_dir_elements.end(),
               [cmp_key]( auto &lhs, auto &rhs ) {
                   return key_compare(lhs, rhs, cmp_key) < 0;
               });
    std::sort( other_dir_elements.begin(),
               other_dir_elements.end(),
               [cmp_key]( auto &lhs, auto &rhs ) {
                   return key_compare(lhs, rhs, cmp_key) < 0;
               });

    other_pos = 0;

    for ( my_pos = 0; my_pos < my_dir_elements.size() && m_stop == false; ) {
        while ( other_pos < other_dir_elements.size() &&
                key_compare( my_dir_elements[my_pos], other_dir_elements[other_pos], cmp_key ) > 0 ) {
            if ( other_equal ) {
                other_equal->at( other_dir_elements[other_pos].orig_pos ) = false;
            }
            if ( other_res_vdir ) {
                other_res_vdir->add( *other_dir_elements[other_pos].entry );
            }
            other_pos++;
            unequal_count++;

            if ( m_compare_mode != COMPARE_CHECKSUM ) {
                m_progress_win->finishedEntry();
            }
        };

        if ( other_pos >= other_dir->size() ) {
            // unequal since there is no corresponding element in the other dir
            if ( my_equal ) {
                my_equal->at( my_dir_elements[my_pos].orig_pos ) = false;
            }
            unequal_count++;

            my_pos++;
            
            if ( m_compare_mode != COMPARE_CHECKSUM ) {
                m_progress_win->finishedEntry();
            }
        } else {
            if ( key_compare( my_dir_elements[my_pos],
                              other_dir_elements[other_pos], cmp_key ) == 0 ) {
                size_t my_start_pos = my_pos;
                size_t my_end_pos = my_pos + 1;

                for ( ; my_end_pos < my_dir_elements.size() &&
                          key_compare( my_dir_elements[my_start_pos],
                                       my_dir_elements[my_end_pos], cmp_key ) == 0; my_end_pos++ ) {
                }

                size_t other_start_pos = other_pos;
                size_t other_end_pos = other_pos + 1;
                
                for ( ; other_end_pos < other_dir_elements.size() &&
                          key_compare( other_dir_elements[other_start_pos],
                                       other_dir_elements[other_end_pos], cmp_key ) == 0; other_end_pos++ ) {
                }

                std::vector< bool > other_matched;
                other_matched.resize( other_end_pos - other_start_pos );

                for ( size_t my_block_pos = my_start_pos; my_block_pos < my_end_pos; my_block_pos++ ) {
                    bool my_matched = false;
                    for ( size_t other_block_pos = other_start_pos; other_block_pos < other_end_pos; other_block_pos++ ) {
                        bool cmp_res = false;

                        // it's the same entry so check for compare mode
                        if ( compareEntry( my_dir_elements[my_block_pos].entry,
                                           other_dir_elements[other_block_pos].entry,
                                           my_res_vdir,
                                           other_res_vdir,
                                           cmp_key ) == 0 ) {
                            cmp_res = true;
                        } else {
                            unequal_count++;
                        }

                        if ( cmp_res ) {
                            if ( my_equal ) {
                                my_equal->at( my_dir_elements[my_block_pos].orig_pos ) = cmp_res;
                            }
                            if ( other_equal ) {
                                other_equal->at( other_dir_elements[other_block_pos].orig_pos ) = cmp_res;
                            }

                            if ( my_res_vdir && ! my_matched ) {
                                my_res_vdir->add( *my_dir_elements[my_block_pos].entry );
                            }

                            other_matched.at( other_block_pos - other_start_pos ) = true;

                            my_matched = true;
                        }
                    }

                    if ( m_compare_mode != COMPARE_CHECKSUM ) {
                        m_progress_win->finishedEntry();
                    }
                }

                // if any of the other elements of same key are
                // "equal", put them in the virtual dir
                for ( size_t other_block_pos = other_start_pos; other_block_pos < other_end_pos; other_block_pos++ ) {
                    if ( ! other_matched.at( other_block_pos - other_start_pos )  ) {
                        if ( other_res_vdir ) {
                            other_res_vdir->add( *other_dir_elements[other_block_pos].entry );
                        }

                        if ( m_compare_mode != COMPARE_CHECKSUM ) {
                            m_progress_win->finishedEntry();
                        }
                    }
                }

                my_pos = my_end_pos;
                other_pos = other_end_pos;
            } else {
                // the next element in other dir is of a larger name so it's unequal
                if ( my_equal ) {
                    my_equal->at( my_dir_elements[my_pos].orig_pos ) = false;
                }
                unequal_count++;

                my_pos++;

                if ( m_compare_mode != COMPARE_CHECKSUM ) {
                    m_progress_win->finishedEntry();
                }
            }
        }

        if ( m_progress_win->check() ) {
            m_stop = true;
        }
    }

    for ( ; other_pos < other_dir_elements.size() && m_stop == false; other_pos++ ) {
        if ( other_equal ) {
            other_equal->at( other_dir_elements[other_pos].orig_pos ) = false;
        }
        if ( other_res_vdir ) {
            other_res_vdir->add( *other_dir_elements[other_pos].entry );
        }
        unequal_count++;

        if ( m_compare_mode != COMPARE_CHECKSUM ) {
            m_progress_win->finishedEntry();
        }

        if ( m_progress_win->check() ) {
            m_stop = true;
        }
    }

    return unequal_count == 0 ? 0 : 1;
}

int
DirCompareOp::run( std::shared_ptr< WPUContext > wpu, ActionMessage *msg )
{
    if ( msg->mode == msg->AM_MODE_DNDACTION ) {
        return 0;
    }

    if ( doConfigure() != 0 ) return 1;

    VirtualDirMode *this_vdm = nullptr;
    VirtualDirMode *other_vdm = nullptr;

    Lister *l1 = msg->getWorker()->getActiveLister();
    if ( l1 ) {
        ListerMode *lm1 = l1->getActiveMode();
        if ( lm1 ) {
            if ( auto vdm = dynamic_cast< VirtualDirMode *>( lm1 ) ) {
                this_vdm = vdm;
            }
        }

        l1 = msg->getWorker()->getOtherLister( l1 );
        if ( l1 ) {
            ListerMode *lm1 = l1->getActiveMode();
            if ( lm1 ) {
                if ( auto vdm = dynamic_cast< VirtualDirMode *>( lm1 ) ) {
                    other_vdm = vdm;
                }
            }
        }
    }

    const bool slow_compare = m_compare_mode == COMPARE_FILENAME_CONTENT || m_compare_mode == COMPARE_CHECKSUM;

    m_progress_win = new DirCompareWin( Worker::getAGUIX(),
                                        slow_compare );
    m_progress_win->open();

    m_stop = false;
    
    if ( this_vdm && other_vdm ) {
        auto this_dir = this_vdm->getCurrentDir();
        auto other_dir = other_vdm->getCurrentDir();

        if ( this_dir && other_dir ) {
            std::shared_ptr< NWC::Dir > this_sdir( this_dir.release() );
            std::shared_ptr< NWC::Dir > other_sdir( other_dir.release() );

            std::vector< bool > this_equal;
            std::vector< bool > other_equal;

            std::unique_ptr< NWC::Dir > this_res_dir;
            std::unique_ptr< NWC::Dir > other_res_dir;

            if ( m_result_mode == SHOW_AS_VIRTUAL_DIR ) {
                std::string name = AGUIXUtils::formatStringToString( "dircompare%d", s_vdir_number++ );

                if ( s_vdir_number < 0 ) {
                    // avoid negative and zero number
                    s_vdir_number = 1;
                }

                this_res_dir = std::unique_ptr< NWC::Dir >( new NWC::VirtualDir( name ) );

                name = AGUIXUtils::formatStringToString( "dircompare%d", s_vdir_number++ );

                if ( s_vdir_number < 0 ) {
                    // avoid negative and zero number
                    s_vdir_number = 1;
                }

                other_res_dir = std::unique_ptr< NWC::Dir >( new NWC::VirtualDir( name ) );
            }

            compareDirs( this_sdir,
                         other_sdir,
                         &this_equal,
                         &other_equal,
                         this_res_dir,
                         other_res_dir );

            if ( ! m_stop ) {
                if ( m_result_mode == SHOW_AS_VIRTUAL_DIR ) {
                    this_vdm->showDir( this_res_dir );
                    other_vdm->showDir( other_res_dir );
                } else {
                    for ( size_t this_pos = 0; this_pos < this_sdir->size(); this_pos++ ) {
                        this_vdm->setEntrySelectionState( this_sdir->getEntryAtPos( this_pos )->getFullname(),
                                                          this_equal.at( this_pos ) );
                    }

                    for ( size_t other_pos = 0; other_pos < other_sdir->size(); other_pos++ ) {
                        other_vdm->setEntrySelectionState( other_sdir->getEntryAtPos( other_pos )->getFullname(),
                                                           ! other_equal.at( other_pos ) );
                    }
                }
            }
        }
    }

    delete m_progress_win;

    m_progress_win = NULL;

    return 0;
}

const char *
DirCompareOp::getDescription()
{
    return catalog.getLocale( 1307 );
}

static void check_visibility( int option,
                              AContainer *limit_cont,
                              ChooseButton *ignorecase_cb )
{
    switch ( option ) {
        case 3:
        case 5:
            limit_cont->show();
            break;
        default:
            limit_cont->hide();
            break;
    }

    switch ( option ) {
        case 0:
        case 1:
        case 2:
        case 3:
            ignorecase_cb->show();
            break;
        default:
            ignorecase_cb->hide();
            break;
    }
}

int
DirCompareOp::doConfigure()
{
    AGUIX *aguix = Worker::getAGUIX();
    AGMessage *msg;
    int endmode = -1;

    auto title = AGUIXUtils::formatStringToString( catalog.getLocale( 293 ),
                                                   getDescription() );
    AWindow *win = new AWindow( aguix, 10, 10, 10, 10, title.c_str(), AWindow::AWINDOW_DIALOG );
    win->create();
    
    AContainer *ac0 = win->setContainer( new AContainer( win, 1, 6 ), true );
    ac0->setMinSpace( 5 );
    ac0->setMaxSpace( 5 );
    ac0->setBorderWidth( 5 );

    RefCount<AFontWidth> lencalc( new AFontWidth( aguix, NULL ) );
    auto help_ts = std::make_shared< TextStorageString >( catalog.getLocale( 1180 ), lencalc );
    TextView *help_tv = (TextView*)ac0->add( new TextView( aguix,
                                                           0, 0, 400, 50, "", help_ts ),
                                             0, 0, AContainer::CO_MIN );
    help_tv->setLineWrap( true );
    help_tv->showFrame( false );
    help_tv->maximizeYLines( 15, help_tv->getWidth() );
    ac0->readLimits();
    help_tv->show();
    help_tv->setAcceptFocus( false );

    TextView::ColorDef tv_cd = help_tv->getColors();
    tv_cd.setBackground( 0 );
    tv_cd.setTextColor( 1 );
    help_tv->setColors( tv_cd );

    AContainer *ac1_1 = ac0->add( new AContainer( win, 2, 1 ), 0, 1 );
    ac1_1->setMinSpace( 5 );
    ac1_1->setMaxSpace( 5 );
    ac1_1->setBorderWidth( 0 );
    ac1_1->addWidget( new Text( aguix, 0, 0, catalog.getLocale( 1366 ), 1, 0 ),
                      0, 0, AContainer::CO_FIX );
    CycleButton *comparemode_cb = ac1_1->addWidget( new CycleButton( aguix, 0, 0, 10, 0 ),
                                                    1, 0, AContainer::CO_INCW );
    comparemode_cb->addOption( catalog.getLocale( 1367 ) );
    comparemode_cb->addOption( catalog.getLocale( 1368 ) );
    comparemode_cb->addOption( catalog.getLocale( 1369 ) );
    comparemode_cb->addOption( catalog.getLocale( 1361 ) );
    comparemode_cb->addOption( catalog.getLocale( 1362 ) );
    comparemode_cb->addOption( catalog.getLocale( 1363 ) );
    comparemode_cb->resize( comparemode_cb->getMaxSize(),
                           comparemode_cb->getHeight() );

    ChooseButton *ignorecase_cb = ac0->addWidget( new ChooseButton( aguix, 0, 0,
                                                                    false, catalog.getLocale( 1441 ),
                                                                    LABEL_RIGHT, 0 ),
                                                  0, 2, AContainer::CO_INCWNR );

    AContainer *ac1_2 = ac0->add( new AContainer( win, 2, 1 ), 0, 3 );
    ac1_2->setMinSpace( 5 );
    ac1_2->setMaxSpace( 5 );
    ac1_2->setBorderWidth( 0 );
    ac1_2->addWidget( new Text( aguix, 0, 0, catalog.getLocale( 1185 ), 1, 0 ),
                      0, 0, AContainer::CO_FIX );
    CycleButton *resultmode_cb = ac1_2->addWidget( new CycleButton( aguix, 0, 0, 10, 0 ),
                                                   1, 0, AContainer::CO_INCW );
    resultmode_cb->addOption( catalog.getLocale( 1186 ) );
    resultmode_cb->addOption( catalog.getLocale( 1187 ) );
    resultmode_cb->resize( resultmode_cb->getMaxSize(),
                           resultmode_cb->getHeight() );
    ac0->readLimits();
    
    AContainer *ac1_4 = ac0->add( new AContainer( win, 2, 1 ), 0, 4 );
    ac1_4->setMinSpace( 5 );
    ac1_4->setMaxSpace( 5 );
    ac1_4->setBorderWidth( 0 );
    ac1_4->addWidget( new Text( aguix, 0, 0, catalog.getLocale( 1364 ), 1, 0 ),
                      0, 0, AContainer::CO_FIX );
    StringGadget *limit_sg = ac1_4->addWidget( new StringGadget( aguix, 0, 0, 50, "", 0 ),
                                               1, 0, AContainer::CO_INCW );

    AContainer *ac1_3 = ac0->add( new AContainer( win, 2, 1 ), 0, 5 );
    ac1_3->setMinSpace( 5 );
    ac1_3->setMaxSpace( -1 );
    ac1_3->setBorderWidth( 0 );
    Button *okb = ac1_3->addWidget( new Button( aguix,
                                                0,
                                                0,
                                                catalog.getLocale( 11 ),
                                                0 ), 0, 0, AContainer::CO_FIX );
    Button *cb = ac1_3->addWidget( new Button( aguix,
                                               0,
                                               0,
                                               catalog.getLocale( 8 ),
                                               0 ), 1, 0, AContainer::CO_FIX );

    win->setDoTabCycling( true );
    win->contMaximize( true );
    win->show();

    check_visibility( comparemode_cb->getSelectedOption(),
                      ac1_4,
                      ignorecase_cb );

    for ( ; endmode == -1; ) {
        msg = aguix->WaitMessage( win );
        if ( msg != NULL ) {
            switch ( msg->type ) {
                case AG_CLOSEWINDOW:
                    if ( msg->closewindow.window == win->getWindow() ) endmode = 1;
                    break;
                case AG_BUTTONCLICKED:
                    if ( msg->button.button == okb ) endmode = 0;
                    else if ( msg->button.button == cb ) endmode = 1;
                case AG_CYCLEBUTTONCLICKED:
                    if ( msg->cyclebutton.cyclebutton == comparemode_cb ) {
                        check_visibility( msg->cyclebutton.option,
                                          ac1_4,
                                          ignorecase_cb );
                    }
                default:
                    break;
            }
            aguix->ReplyMessage( msg );
        }
    }

    if ( endmode == 0 ) {
        // ok

        switch ( comparemode_cb->getSelectedOption() ) {
            case 1:
                m_compare_mode = COMPARE_FILENAME_FILESIZE;
                break;
            case 2:
                m_compare_mode = COMPARE_FILENAME_FILESIZE_MDATE;
                break;
            case 3:
                m_compare_mode = COMPARE_FILENAME_CONTENT;
                break;
            case 4:
                m_compare_mode = COMPARE_FILESIZE;
                break;
            case 5:
                m_compare_mode = COMPARE_CHECKSUM;
                break;
            default:
                m_compare_mode = COMPARE_FILENAME;
                break;
        }

        switch ( resultmode_cb->getSelectedOption() ) {
            case 1:
                m_result_mode = SHOW_AS_VIRTUAL_DIR;
                break;
            default:
                m_result_mode = CHANGE_SELECTION_STATE;
                break;
        }

        m_ignore_file_name_case = ignorecase_cb->getState();

        if ( strlen( limit_sg->getText() ) > 0 ) {
            m_file_size_limit = AGUIXUtils::convertHumanStringToNumber( limit_sg->getText() );
        } else {
            m_file_size_limit = 0;
        }
    }

    delete win;

#if !defined(HAVE_OPENSSL_EVP_SHA256) && !defined(HAVE_OPENSSL_SHA256)
    if ( m_compare_mode == COMPARE_CHECKSUM ) {
        Worker::getRequester()->request( catalog.getLocale( 124 ),
                                         catalog.getLocale( 1365 ),
                                         catalog.getLocale( 8 ) );
        return 1;
    }
#endif

    return endmode;
}
