//Author:    Max Howell <max.howell@methylblue.com>, (C) 2003-4
//Copyright: See COPYING file that comes with this distribution

#include <kcursor.h>         //make()
#include <kglobalsettings.h> //kdeColours
#include <kimageeffect.h>    //desaturate()
#include <tqapplication.h>    //make()
#include <tqimage.h>          //make() & paint()
#include <tqfont.h>           //ctor
#include <tqfontmetrics.h>    //ctor
#include <tqpainter.h>

#include "builder.h"
#include "Config.h"
#include "fileTree.h"
#include "sincos.h"
#include "widget.h"

#define COLOR_GREY TQColor( 0, 0, 140, TQColor::Hsv )


RadialMap::Map::Map()
  : m_signature( 0 )
  , m_ringBreadth( MIN_RING_BREADTH )
  , m_innerRadius( 0 )
  , m_visibleDepth( DEFAULT_RING_DEPTH )
{
    //FIXME this is all broken. No longer is a maximum depth!
    const int fmh   = TQFontMetrics( TQFont() ).height();
    const int fmhD4 = fmh / 4;
    MAP_2MARGIN = 2 * ( fmh - (fmhD4 - LABEL_MAP_SPACER) ); //margin is dependent on fitting in labels at top and bottom
}

RadialMap::Map::~Map()
{
    delete [] m_signature;
}

void
RadialMap::Map::invalidate( const bool desaturateTheImage )
{
    delete [] m_signature;
    m_signature = 0;

    if( desaturateTheImage )
    {
        TQImage img = this->convertToImage();

        KImageEffect::desaturate( img, 0.7 );
        KImageEffect::toGray( img, true );

        this->convertFromImage( img );
    }

    m_visibleDepth = Config::defaultRingDepth;
}

void
RadialMap::Map::make( const Directory *tree, bool refresh )
{
    //**** determineText seems pointless optimisation
    //   but is it good to keep the text consistent?
    //   even if it makes it a lie?

    //slow operation so set the wait cursor
    TQApplication::setOverrideCursor( KCursor::waitCursor() );

    {
        //build a signature of visible components
        delete [] m_signature;
        Builder builder( this, tree, refresh );
    }

    //colour the segments
    colorise();

    //determine centerText
    if( !refresh )
    {
        int i;

        for( i = 3; i > 0; --i )
            if( tree->size() > File::DENOMINATOR[i] )
                break;

        m_centerText = tree->humanReadableSize( (File::UnitPrefix)i );
    }

    //paint the pixmap
    aaPaint();

    TQApplication::restoreOverrideCursor();
}

void
RadialMap::Map::setRingBreadth()
{
    m_ringBreadth = (height() - MAP_2MARGIN) / (2 * m_visibleDepth + 4);

    if( m_ringBreadth < MIN_RING_BREADTH ) m_ringBreadth = MIN_RING_BREADTH;
    else if( m_ringBreadth > MAX_RING_BREADTH ) m_ringBreadth = MAX_RING_BREADTH;
}

bool
RadialMap::Map::resize( const TQRect &rect )
{
    //there's a MAP_2MARGIN border

    #define mw width()
    #define mh height()
    #define cw rect.width()
    #define ch rect.height()

    if( cw < mw || ch < mh || (cw > mw && ch > mh) )
    {
        uint size = (( cw < ch ) ? cw : ch) - MAP_2MARGIN;

        //this also causes uneven sizes to always resize when resizing but map is small in that dimension
        //size -= size % 2; //even sizes mean less staggered non-antialiased resizing

        {
            const uint minSize = MIN_RING_BREADTH * 2 * (m_visibleDepth + 2);
            const uint mD2 = MAP_2MARGIN / 2;

            if( size < minSize ) size = minSize;

            //this TQRect is used by paint()
            m_rect.setRect( mD2, mD2, size, size );
        }

        //resize the pixmap
        size += MAP_2MARGIN;
        KPixmap::resize( size, size );

        if( m_signature != NULL )
        {
            setRingBreadth();
            paint();
        }
        else fill(); //FIXME I don't like having to do this..

        return true;
    }

    #undef mw
    #undef mh
    #undef cw
    #undef ch

    return false;
}

void
RadialMap::Map::colorise()
{
    TQColor cp, cb;
    double darkness = 1;
    double contrast = (double)Config::contrast / (double)100;
    int h, s1, s2, v1, v2;

    TQColor kdeColour[2] = { KGlobalSettings::inactiveTitleColor(), KGlobalSettings::activeTitleColor() };

    double deltaRed   = (double)(kdeColour[0].red()   - kdeColour[1].red())   / 2880; //2880 for semicircle
    double deltaGreen = (double)(kdeColour[0].green() - kdeColour[1].green()) / 2880;
    double deltaBlue  = (double)(kdeColour[0].blue()  - kdeColour[1].blue())  / 2880;

    for( uint i = 0; i <= m_visibleDepth; ++i, darkness += 0.04 )
    {
        for( Iterator<Segment> it = m_signature[i].iterator(); it != m_signature[i].end(); ++it )
        {
            switch( Config::scheme )
            {
            case 2000: //HACK for summary view

               if( (*it)->file()->name() == "Used" ) {
                  cb = TQApplication::palette().active().color( TQColorGroup::Highlight );
                  cb.hsv( &h, &s1, &v1 );

                  if( s1 > 80 )
                     s1 = 80;

                  v2 = v1 - int(contrast * v1);
                  s2 = s1 + int(contrast * (255 - s1));

                  cb.setHsv( h, s1, v1 );
                  cp.setHsv( h, s2, v2 );
               }
               else {
                  cp = TQt::gray;
                  cb = TQt::white;
               }

               (*it)->setPalette( cp, cb );

               continue;            
            case Filelight::KDE:
            {
                //gradient will work by figuring out rgb delta values for 360 degrees
                //then each component is angle*delta

                int a = (*it)->start();

                if( a > 2880 ) a = 2880 - (a - 2880);

                h  = (int)(deltaRed   * a) + kdeColour[1].red();
                s1 = (int)(deltaGreen * a) + kdeColour[1].green();
                v1 = (int)(deltaBlue  * a) + kdeColour[1].blue();

                cb.setRgb( h, s1, v1 );
                cb.hsv( &h, &s1, &v1 );

                break;
            }

            case Filelight::HighContrast:

                cp.setHsv( 0, 0, 0 ); //values of h, s and v are irrelevant
                cb.setHsv( 180, 0, int(255.0 * contrast) );
                (*it)->setPalette( cp, cb );
                continue;

            default:
                h  = int((*it)->start() / 16);
                s1 = 160;
                v1 = (int)(255.0 / darkness); //****doing this more often than once seems daft!
            }

            v2 = v1 - int(contrast * v1);
            s2 = s1 + int(contrast * (255 - s1));

            if( s1 < 80 ) s1 = 80; //can fall too low and makes contrast between the files hard to discern

            if( (*it)->isFake() ) //multi-file
            {
                cb.setHsv( h, s2, (v2 < 90) ? 90 : v2 ); //too dark if < 100
                cp.setHsv( h, 17, v1 );
            }
            else if( !(*it)->file()->isDir() ) //file
            {
                cb.setHsv( h, 17, v1 );
                cp.setHsv( h, 17, v2 );
            }
            else //directory
            {
                cb.setHsv( h, s1, v1 ); //v was 225
                cp.setHsv( h, s2, v2 ); //v was 225 - delta
            }

            (*it)->setPalette( cp, cb );

            //**** may be better to store KDE colours as H and S and vary V as others
            //**** perhaps make saturation difference for s2 dependent on contrast too
            //**** fake segments don't work with highContrast
            //**** may work better with cp = cb rather than TQt::white
            //**** you have to ensure the grey of files is sufficient, currently it works only with rainbow (perhaps use contrast there too)
            //**** change v1,v2 to vp, vb etc.
            //**** using percentages is not strictly correct as the eye doesn't work like that
            //**** darkness factor is not done for kde_colour scheme, and also value for files is incorrect really for files in this scheme as it is not set like rainbow one is
        }
    }
}

void
RadialMap::Map::aaPaint()
{
    //paint() is called during continuous processes
    //aaPaint() is not and is slower so set overidecursor (make sets it too)
    TQApplication::setOverrideCursor( KCursor::waitCursor() );
    paint( Config::antiAliasFactor );
    TQApplication::restoreOverrideCursor();
}

void
RadialMap::Map::paint( unsigned int scaleFactor )
{
   if( scaleFactor == 0 ) //just in case
      scaleFactor = 1;

   TQPainter paint;
   TQRect rect = m_rect;
   int step = m_ringBreadth;
   int excess = -1;

   //scale the pixmap, or do intelligent distribution of excess to prevent nasty resizing
   if( scaleFactor > 1 )
   {
      int x1, y1, x2, y2;
      rect.coords( &x1, &y1, &x2, &y2 );
      x1 *= scaleFactor;
      y1 *= scaleFactor;
      x2 *= scaleFactor;
      y2 *= scaleFactor;
      rect.setCoords( x1, y1, x2, y2 );

      step *= scaleFactor;
      KPixmap::resize( this->size() * (int)scaleFactor );
   }
   else if( m_ringBreadth != MAX_RING_BREADTH && m_ringBreadth != MIN_RING_BREADTH ) {
      excess = rect.width() % m_ringBreadth;
      ++step;
   }

   //**** best option you can think of is to make the circles slightly less perfect,
   //  ** i.e. slightly eliptic when resizing inbetween


   paint.begin( this );

   fill(); //erase background

   for( int x = m_visibleDepth; x >= 0; --x )
   {
      int width = rect.width() / 2;
      //clever geometric trick to find largest angle that will give biggest arrow head
      int a_max = int(acos( (double)width / double((width + 5) * scaleFactor) ) * (180*16 / M_PI));

      for( ConstIterator<Segment> it = m_signature[x].constIterator(); it != m_signature[x].end(); ++it )
      {
         //draw the pie segments, most of this code is concerned with drawing the little
         //arrows on the ends of segments when they have hidden files

         paint.setPen( (*it)->pen() );

         if( (*it)->hasHiddenChildren() )
         {
            //draw arrow head to indicate undisplayed files/directories
            TQPointArray pts( 3 );
            TQPoint pos, cpos = rect.center();
            int a[3] = { (*it)->start(), (*it)->length(), 0 };

            a[2] = a[0] + (a[1] / 2); //assign to halfway between
            if( a[1] > a_max )
            {
               a[1] = a_max;
               a[0] = a[2] - a_max / 2;
            }

            a[1] += a[0];

            for( int i = 0, radius = width; i < 3; ++i )
            {
               double ra = M_PI/(180*16) * a[i], sinra, cosra;

               if( i == 2 )
                  radius += 5 * scaleFactor;
#if 0
               sincos( ra, &sinra, &cosra );
#endif
	       sinra = sin(ra); cosra = cos(ra);
               pos.rx() = cpos.x() + static_cast<int>(cosra * radius);
               pos.ry() = cpos.y() - static_cast<int>(sinra * radius);
               pts.setPoint( i, pos );
            }

            paint.setBrush( (*it)->pen() );
            paint.drawPolygon( pts );
         }

         paint.setBrush( (*it)->brush() );
         paint.drawPie( rect, (*it)->start(), (*it)->length() );

         if( (*it)->hasHiddenChildren() )
         {
            //**** code is bloated!
            paint.save();
            TQPen pen = paint.pen();
            int width = 2 * scaleFactor;
            pen.setWidth( width );
            paint.setPen( pen );
            TQRect rect2 = rect;
            width /= 2;
            rect2.addCoords( width, width, -width, -width );
            paint.drawArc( rect2, (*it)->start(), (*it)->length() );
            paint.restore();
         }
      }

      if( excess >= 0 ) { //excess allows us to resize more smoothly (still crud tho)
         if( excess < 2 ) //only decrease rect by more if even number of excesses left
         --step;
         excess -= 2;
      }

      rect.addCoords( step, step, -step, -step );
   }

   //  if( excess > 0 ) rect.addCoords( excess, excess, 0, 0 ); //ugly

   paint.setPen( COLOR_GREY );
   paint.setBrush( TQt::white );
   paint.drawEllipse( rect );

   if( scaleFactor > 1 )
   {
      //have to end in order to smoothscale()
      paint.end();

      int x1, y1, x2, y2;
      rect.coords( &x1, &y1, &x2, &y2 );
      x1 /= scaleFactor;
      y1 /= scaleFactor;
      x2 /= scaleFactor;
      y2 /= scaleFactor;
      rect.setCoords( x1, y1, x2, y2 );

      TQImage img = this->convertToImage();
      img = img.smoothScale( this->size() / (int)scaleFactor );
      this->convertFromImage( img );

      paint.begin( this );
      paint.setPen( COLOR_GREY );
      paint.setBrush( TQt::white );
   }

   paint.drawText( rect, TQt::AlignCenter, m_centerText );

   m_innerRadius = rect.width() / 2; //rect.width should be multiple of 2

   paint.end();
}
#if 0
#if __GLIBC__ < 2 ||  __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
   
   void
   sincos( double angleRadians, double *Sin, double *Cos )
   {
      *Sin = sin( angleRadians );
      *Cos = cos( angleRadians );
   }

#endif
#endif
