/* ascaler.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2004,2008 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 "ascaler.h"

int AScaler::scaleElementsSimple( int wantedWidth,
				  int elems,
				  const int *minWidths,
				  const int *maxWidths,
				  const int *weights,
				  int *returnWidths )
{
  int i, w, maxweight;
  int tw, tmaxweight;
  bool changes;
  int *nw;
  bool *finished;

  if ( wantedWidth < 0 ) return -1;
  if ( elems < 1 ) return -1;
  if ( minWidths == NULL ) return -1;
  if ( maxWidths == NULL ) return -1;
  if ( weights == NULL ) return -1;
  if ( returnWidths == NULL ) return -1;
  w = wantedWidth;
  maxweight = 0;
  for ( i = 0; i < elems; i++ ) {
    if ( weights[i] < 1 ) return -1;
    maxweight += weights[i];
  }
  nw = new int[elems];
  finished = new bool[elems];
  for ( i = 0; i < elems; i++ ) {
    nw[i] = 0;
    finished[i] = false;
  }
  do {
    changes = false;
    tw = w;
    tmaxweight = maxweight;
    for ( i = 0; i < elems; i++ ) {
      if ( finished[i] == false ) {
        nw[i] = tw * weights[i];
        nw[i] /= tmaxweight;
        tw -= nw[i];
        tmaxweight -= weights[i];
      }
    }
    for ( i = 0; i < elems; i++ ) {
      if ( finished[i] == false ) {
        if ( nw[i] < minWidths[i] ) {
          nw[i] = minWidths[i];
          finished[i] = true;
          w -= nw[i];
          maxweight -= weights[i];
          changes = true;
          break;
        } else if ( ( nw[i] > maxWidths[i] ) && ( maxWidths[i] >= 0 ) ) {
          nw[i] = maxWidths[i];
          finished[i] = true;
          w -= nw[i];
          maxweight -= weights[i];
          changes = true;
          break;
        }
      }
    }
  } while ( changes == true );
  delete [] finished;
  
  w = 0;
  for ( i = 0; i < elems; i++ ) {
    w += nw[i];
    returnWidths[i] = nw[i];
  }
  delete [] nw;
  return w;
}

int AScaler::scaleElements( int wantedWidth,
			    int elems,
			    const int *minWidths,
			    const int *maxWidths,
			    const int *weights,
			    int *returnWidths )
{
  int i, w, maxweight;
  int tw, tmaxweight;
  bool changes;
  int *nw;
  int tweight;
  int *avw;
  int rest, v, minw, secmin, *neww, trest;
  bool *minimal, applyValues;
  int maxw, secmax;

  typedef enum { ELEM_FREE, ELEM_SMALLER, ELEM_LARGER, ELEM_FIXED } elem_t;
  elem_t *elemMode;

  if ( wantedWidth < 0 ) return -1;
  if ( elems < 1 ) return -1;
  if ( minWidths == NULL ) return -1;
  if ( maxWidths == NULL ) return -1;
  if ( weights == NULL ) return -1;
  if ( returnWidths == NULL ) return -1;

  w = wantedWidth;
  maxweight = 0;
  for ( i = 0; i < elems; i++ ) {
    if ( weights[i] < 1 ) return -1;
    maxweight += weights[i];
  }
  nw = new int[elems];
  avw = new int[elems];
  elemMode = new elem_t[elems];
  neww = new int[elems];
  minimal = new bool[elems];

  tw = w;
  tmaxweight = maxweight;
  for ( i = 0; i < elems; i++ ) {
    avw[i] = tw * weights[i];
    avw[i] /= tmaxweight;
    tw -= avw[i];
    tmaxweight -= weights[i];

    nw[i] = avw[i];
    elemMode[i] = ELEM_FREE;
  }

  rest = 0;
  do {
    changes = false;
    for ( i = 0; i < elems; i++ ) {
      if ( ( nw[i] > maxWidths[i] ) && ( maxWidths[i] >= 0 ) &&
           elemMode[i] == ELEM_FREE ) {
	rest += maxWidths[i] - nw[i];
	nw[i] = maxWidths[i];
	changes = true;
	elemMode[i] = ELEM_SMALLER;
      } else if ( nw[i] < minWidths[i] &&
                  elemMode[i] == ELEM_FREE ) {
	rest += minWidths[i] - nw[i];
	nw[i] = minWidths[i];
	changes = true;
	elemMode[i] = ELEM_LARGER;
      }
    }
    if ( rest < 0 ) {
      // stretch
      // only FREE or LARGER elements
      
      // first find minimum
      minw = -1;
      for ( i = 0; i < elems; i++ ) {
	if ( ( elemMode[i] == ELEM_FREE ) ||
	     ( elemMode[i] == ELEM_LARGER ) ) {
	  v = nw[i] / weights[i];
	  if ( ( minw < 0 ) ||
	       ( v < minw ) ) {
	    minw = v;
	  }
	}
      }

      if ( minw >= 0 ) {
	// now mark all minimal elements
	tweight = 0;
	for ( i = 0; i < elems; i++ ) {
	  minimal[i] = false;
	  if ( ( elemMode[i] == ELEM_FREE ) ||
	       ( elemMode[i] == ELEM_LARGER ) ) {
	    v = nw[i] / weights[i];
	    if ( v == minw ) {
	      minimal[i] = true;
	      tweight += weights[i];
	    }
	  }
	}
	
	// now find second smallest
	secmin = -1;
	for ( i = 0; i < elems; i++ ) {
	  if ( ( elemMode[i] == ELEM_FREE ) ||
	       ( elemMode[i] == ELEM_LARGER ) ) {
	    v = nw[i] / weights[i];
	    if ( v > minw ) {
	      if ( ( secmin < 0 ) ||
		   ( v < secmin ) ) {
		secmin = v;
	      }
	    }
	  }
	}

	// reset new width
	for ( i = 0; i < elems; i++ ) neww[i] = 0;
	
	// calc new widths
	applyValues = true;
	trest = rest;
	for ( i = 0; i < elems; i++ ) {
	  if ( minimal[i] == true ) {
	    v = trest * weights[i];
	    v /= tweight;

	    // not more than the difference to secmin
	    if ( secmin >= 0 ) {
	      if (  v < ( ( minw - secmin ) * weights[i] ) ) v = ( minw - secmin ) * weights[i];
	    }

	    // new value, only update temp rest
	    neww[i] = nw[i] - v;
	    trest -= v;

	    if ( ( neww[i] > maxWidths[i] ) && ( maxWidths[i] >= 0 ) ) {
	      // limit reached
	      // use max width and update real rest
	      rest += maxWidths[i] - nw[i];
	      nw[i] = maxWidths[i];
	      changes = true;
	      elemMode[i] = ELEM_FIXED;

	      // but do not update other elements
	      applyValues = false;
	      break;
	    }
	    tweight -= weights[i];
	  }
	}

	// apply new values
	if ( applyValues == true ) {
	  for ( i = 0; i < elems; i++ ) {
	    if ( minimal[i] == true ) {
	      rest += neww[i] - nw[i];
	      nw[i] = neww[i];
	      changes = true;
	    }
	  }
	}
      }
    } else {
      // shrink
      // only FREE or SMALLER elements
      
      // first find maximum
      maxw = -1;
      for ( i = 0; i < elems; i++ ) {
	if ( ( elemMode[i] == ELEM_FREE ) ||
	     ( elemMode[i] == ELEM_SMALLER ) ) {
	  v = nw[i] / weights[i];
	  if ( v > maxw ) {
	    maxw = v;
	  }
	}
      }

      if ( maxw >= 0 ) {
	// now mark all minimal elements
	tweight = 0;
	for ( i = 0; i < elems; i++ ) {
	  minimal[i] = false;
	  if ( ( elemMode[i] == ELEM_FREE ) ||
	       ( elemMode[i] == ELEM_SMALLER ) ) {
	    v = nw[i] / weights[i];
	    if ( v == maxw ) {
	      minimal[i] = true;
	      tweight += weights[i];
	    }
	  }
	}
	
	// now find second smallest
	secmax = -1;
	for ( i = 0; i < elems; i++ ) {
	  if ( ( elemMode[i] == ELEM_FREE ) ||
	       ( elemMode[i] == ELEM_SMALLER ) ) {
	    v = nw[i] / weights[i];
	    if ( v < maxw ) {
	      if ( ( secmax < 0 ) ||
		   ( v > secmax ) ) {
		secmax = v;
	      }
	    }
	  }
	}

	// reset new width
	for ( i = 0; i < elems; i++ ) neww[i] = 0;
	
	// calc new widths
	applyValues = true;
	trest = rest;
	for ( i = 0; i < elems; i++ ) {
	  if ( minimal[i] == true ) {
	    v = trest * weights[i];
	    v /= tweight;

	    if ( secmax >= 0 ) {
	      if ( v > ( ( maxw - secmax ) * weights[i] ) ) v = ( maxw - secmax ) * weights[i];
	    }
	    neww[i] = nw[i] - v;
	    trest -= v;
	    if ( neww[i] < minWidths[i] ) {
	      rest += minWidths[i] - nw[i];
	      nw[i] = minWidths[i];
	      changes = true;
	      elemMode[i] = ELEM_FIXED;
	      applyValues = false;
	      break;
	    }
	    tweight -= weights[i];
	  }
	}

	// apply new values
	if ( applyValues == true ) {
	  for ( i = 0; i < elems; i++ ) {
	    if ( minimal[i] == true ) {
	      rest += neww[i] - nw[i];
	      nw[i] = neww[i];
	      changes = true;
	    }
	  }
	}
      }
    }
    if ( changes == false ) break;
  } while ( rest != 0 );

  w = 0;
  for ( i = 0; i < elems; i++ ) {
    w += nw[i];
    returnWidths[i] = nw[i];
  }
  delete [] nw;
  delete [] avw;
  delete [] elemMode;
  delete [] neww;
  delete [] minimal;
  return w;
}
