/* filenameshrinker.cc
 * This file belongs to Worker, a filemanager for UNIX/X11.
 * Copyright (C) 2007 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 "filenameshrinker.hh"
#include "datei.h"
#include "aguix/lowlevelfunc.h"
#include "aguix/awidth.h"
#include "aguix/utf8.hh"

FileNameShrinker::FileNameShrinker()
{
}

FileNameShrinker::~FileNameShrinker()
{
}

static char *shrinkFilename( const char *str, int maxlength, AWidth &lencalc )
{
    char *tstr = NULL;
    int len, len1;
    bool stopnow;
    const char *lastl, *lastr, *startl, *startr, *str1;
    const char *filename;
    int dddlen;
    int l1, l2, l3, real_width;
    std::string tstr1;
  
    if ( str == NULL ) return NULL;
  
    // test if the str will fit in maxlength
    len = strlen( str );
    if( ( lencalc.getWidth( str, len ) <= maxlength ) || ( len <= 5 ) ) {
        return dupstring( str );
    }
  
    dddlen = lencalc.getWidth( "..."  );

    // now we really have to do something
  
    // first find the most important part, the filename
    // we will also take the beginning slash (if exists) to show the user
    // the filename is not shorten
    // first test: is there any slash
    // if not then shorten the str with lowlevelfunc shrinkstring
    // if:
    //   filename starts at the last slash or the next to last 
    //     if the str ends with a slash
    //   if this is longer then maxlength-5 (atleast 2 chars at the beginning and "...")
    //     then just do a shringstring because the filename will be shorten in any case
    //   otherwise:
    //     find the first dirname from left
    //     if it will fit into the string, add it
    //     otherwise take the previous string and stop
    //     do the same with the first dirname from right
    //   specialcase: filename is shorter then maxlength-5
    //     but the first dir doesnt fit, then use as much chars from it
    //     to fill maxlength

    int right_pos = len; // this is the 0 byte
    UTF8::movePosToPrevChar( str, right_pos );  // to last character
    UTF8::movePosToPrevChar( str, right_pos );  /* to skip a final / (if exists)
                                                   since a filename is atleast 1 char this would
                                                   point in the worst case at the beginning slash */
    str1 = str + right_pos;
  
    // now search for the slash backwards
  
    while ( *str1 != '/' ) {
        str1--;
        if ( str1 < str ) break;
    }
  
    if ( str1 < str ) {
        // no slash (or only a final one)
        return shrinkstring( str, maxlength, lencalc );
    } else {
        // found filename
        // we will also take the slash into it
        filename = str1;
    
        if ( lencalc.getWidth( filename ) + lencalc.getWidth( "/.../" ) > maxlength ) {
            return shrinkstring( str, maxlength, lencalc );
        }
    
        // enough room for some additional chars
        startl = str;
        startr = filename;
        lastl = str;      // this are the last positions that will fit into maxlength
        lastr = filename;
    
        if ( startl[0] == '/' )  // starting slash => skip
            startl++;
    
        // startr is at the start filename and so points to a slash
        // but because we want to prefer the first dir from left, leave it so
    
        for ( stopnow = false; ; ) {
            // remember: there is a slash before the filename
            // so this will stop there (or before)
            while ( startl < ( str + len ) && *startl != '/' ) {
                startl++;
            }
            // this could run over the left
            while ( startr >= str && *startr != '/' ) {
                startr--;
            }
      
            if ( startl > startr ) {
                // since startr is before startl both found the same mid dir
                // this dir will not fit in maxlength because then everything would fit
                // but this cannot be because of previous checks
                // just take lastl und lastr to build new string
                stopnow = true;
            } else {
                // test if startl and/or startr will fit
                // otherwise stop

                // get the number of bytes up to including startl
                right_pos = startl - str;
                UTF8::movePosToNextChar( str, right_pos );
                len1 = right_pos;
                /* len1 is actually ( startl - str + 1 ) since startl
                   points to the slash but this it's generic
                   the same goes for the next two lastl calculations */

                l1 = lencalc.getWidth( str, len1 );
                l2 = lencalc.getWidth( lastr );

                if ( ( l1 + dddlen + l2 ) <= maxlength ) {
                    // new dir at the left will fit
                    lastl = startl;
                }
        
                // get the number of bytes up to including lastl
                right_pos = lastl - str;
                UTF8::movePosToNextChar( str, right_pos );
                len1 = right_pos;

                l1 = lencalc.getWidth( str, len1 );
                l2 = lencalc.getWidth( startr );

                if ( ( l1 + dddlen + l2 ) <= maxlength ) {
                    // new dir at the right will fit
                    lastr = startr;
                }
        
                if ( ( lastl != startl ) && ( lastr != startr ) ) {
                    // nothing has changed so stop
                    stopnow = true;
                }
            }
      
            if ( stopnow == true ) {
                // time to stop
                if ( lastl == str ) {
                    // no fitting dirs found
                    // so take as much as possibly

                    l1 = lencalc.getWidth( filename );
                    l1 += dddlen;
                    l2 = maxlength - l1;
	  
                    if ( l2 > 0 ) {
                        l3 = lencalc.getStrlen4Width( str, l2, &real_width );
	    
                        tstr1 = "";
                        tstr1.append( str, l3 );
                        tstr1 += "...";
                        tstr1 += filename;
                        tstr = dupstring( tstr1.c_str() );
                    } else {
                        tstr = shrinkstring( str, maxlength, lencalc );
                    }
                } else {
                    // get the number of bytes up to including lastl
                    right_pos = lastl - str;
                    UTF8::movePosToNextChar( str, right_pos );
                    len1 = right_pos;
          
                    tstr1 = "";
                    tstr1.append( str, len1 );
                    tstr1 += "...";
                    tstr1 += lastr;
                    if ( lencalc.getWidth( tstr1.c_str() ) > maxlength ) {
                        tstr = shrinkstring( str, maxlength, lencalc );
                    } else {
                        tstr = dupstring( tstr1.c_str() );
                    }
                }
                break;
            }
      
            startl++;
            startr--;
        }
    }
  
    return tstr;
}

std::string FileNameShrinker::shrink( const std::string &str, int len_limit, AWidth &len_calc )
{

    char *tstr = NULL;
    std::string res_str;

    tstr = shrinkFilename( str.c_str(), len_limit, len_calc );
    if ( tstr != NULL ) {
        res_str = tstr;
        _freesafe( tstr );
    } else {
        res_str = "";
    }
    return res_str;
}
