/*
 *   khexedit - Versatile hex editor
 *   Copyright (C) 1999  Espen Sand, espensa@online.no
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <kapp.h>
#include <kdialog.h>
#include <klocale.h>

#include "hextoolwidget.h"


//
// This value reduces the checkbox width so that there is no margin
// on the right edge. Zero gives original layout.
//
static int CHECKBOX_HACK_WIDTH=0;

CHexToolWidget::CHexToolWidget( QWidget *parent, const char *name )
  : QFrame( parent, name )
{
  setFrameStyle( QFrame::Panel | QFrame::Raised );
  setLineWidth( 1 );
  
  QString text;
  mUtilBox = new QGridLayout( this, 5, 7, KDialog::marginHint(), 
			      KDialog::spacingHint() );
  if( mUtilBox == 0 ) { return; }

  mUtilBox->setRowStretch( 4, 10 );
  mUtilBox->setColStretch( 6, 10 );

  QString msg1[4] = 
  { 
    i18n("Signed 8 bit"), i18n("Unsigned 8 bit"), 
    i18n("Signed 16 bit"), i18n("Unsigned 16 bit") 
  };

  QString msg2[4] = 
  { 
    i18n("Signed 32 bit"), i18n("Unsigned 32 bit"), 
    i18n("32 bit float"), i18n("64 bit float") 
  };
  
  QString msg3[4] = 
  { 
    i18n("Hexadecimal"), i18n("Octal"), 
    i18n("Binary"), i18n("Text")
  };

  QLabel *label1[4];
  QLabel *label2[4];
  QLabel *label3[4];

  int max1 = 0;
  int max2 = 0;
  int max3 = 0;
  int i;
  for( i=0; i<4; i++ )
  {
    label1[i] = new QLabel( i18n( msg1[i] ), this );
    max1 = QMAX( max1, label1[i]->sizeHint().width() );
    label1[i]->setAlignment( AlignRight|AlignVCenter );
    mUtilBox->addWidget( label1[i], i, 0 );
    //label1[i]->setBackgroundColor( Qt::red );
  
    mText1[i] = new QLabel( "XXXXXXXX", this );
    mText1[i]->setMinimumSize( mText1[i]->sizeHint() );
    mText1[i]->setMaximumWidth( mText1[i]->sizeHint().width() );
    mUtilBox->addWidget( mText1[i], i, 1 );

    label2[i] = new QLabel( i18n( msg2[i] ), this );
    max2 = QMAX( max2, label2[i]->sizeHint().width() );
    label2[i]->setAlignment( AlignRight|AlignVCenter );
    mUtilBox->addWidget( label2[i], i, 2 );
    //label2[i]->setBackgroundColor( Qt::red );

    mText2[i] = new QLabel( "XXXXXXXXXXXX", this );
    mText2[i]->setMinimumSize( mText2[i]->sizeHint() );
    mText2[i]->setMaximumWidth( mText2[i]->sizeHint().width() );
    mUtilBox->addWidget( mText2[i], i, 3 );

    label3[i] = new QLabel( i18n( msg3[i] ), this );
    max3 = QMAX( max3, label3[i]->sizeHint().width() );
    label3[i]->setAlignment( AlignRight|AlignVCenter );
    mUtilBox->addWidget( label3[i], i, 4 );
    //label3[i]->setBackgroundColor( Qt::red );

    mText3[i] = new QLabel( "888888888888888888", this );
    mText3[i]->setMinimumSize( mText3[i]->sizeHint() );
    mText3[i]->setMaximumWidth( mText3[i]->sizeHint().width() );
    mUtilBox->addWidget( mText3[i], i, 5 );
  }

  text = i18n("Show little endian decoding");
  mCheckIntelFormat = new QCheckBox( text, this );
  mCheckIntelFormat->setMinimumHeight( mCheckIntelFormat->sizeHint().height());
  mCheckIntelFormat->setMinimumWidth( mCheckIntelFormat->sizeHint().width()
				      - CHECKBOX_HACK_WIDTH );
  connect( mCheckIntelFormat, SIGNAL(clicked()),this,SLOT(intelFormat()) );
  mUtilBox->addMultiCellWidget( mCheckIntelFormat, 4, 4, 0, 1, AlignLeft );
  //mCheckIntelFormat->setBackgroundColor( Qt::red );

  //
  // The checkbox spans over two columns. Seems that the layout mechanism
  // can't readjust the column widths. Must make sure it is room for 
  // the checkbox.
  //
  if( mCheckIntelFormat->sizeHint().width() > 
      max1 + mText1[0]->sizeHint().width() + KDialog::spacingHint() )
  {
    int w = mCheckIntelFormat->sizeHint().width() - max1 - 
      mText1[0]->sizeHint().width()-CHECKBOX_HACK_WIDTH-KDialog::spacingHint();
    max1 += w;
  }

  text = i18n("Show unsigned as hexadecimal");
  mCheckHexadecimal = new QCheckBox( text, this );
  mCheckHexadecimal->setMinimumHeight( mCheckHexadecimal->sizeHint().height());
  mCheckHexadecimal->setMinimumWidth( mCheckHexadecimal->sizeHint().width()
				      - CHECKBOX_HACK_WIDTH );
  connect( mCheckHexadecimal, SIGNAL(clicked()),this,SLOT(unsignedFormat()) );
  mUtilBox->addMultiCellWidget( mCheckHexadecimal, 4, 4, 2, 3, AlignLeft );
  //mCheckHexadecimal->setBackgroundColor( Qt::red );

  //
  // Same as for previous checkbox
  //
  if( mCheckHexadecimal->sizeHint().width() > 
      max2 + mText2[0]->sizeHint().width() + KDialog::spacingHint())
  {
    int w = mCheckHexadecimal->sizeHint().width() - max2 - 
      mText2[0]->sizeHint().width()-CHECKBOX_HACK_WIDTH-KDialog::spacingHint();
    max2 += w;
  }


  //
  // Variable bitwidth. Based on Craig Graham's work.
  //
  mBitCombo = new QComboBox( false, this );
  if( mBitCombo == 0 ) { return; }
  text = i18n("Fixed 8 bit" );
  mBitCombo->insertItem( text );
  for( i=0; i<16; i++ )
  {
    text.sprintf("%u ", i+1 );
    if( i==0 )
    {
      text += i18n("bit window");
    }
    else
    {
      text += i18n("bits window");
    }
    mBitCombo->insertItem( text );
  }
  mBitCombo->setMinimumSize( mBitCombo->sizeHint() );
  connect( mBitCombo, SIGNAL(activated(int)), SLOT(bitWidthChanged(int)));
  mUtilBox->addWidget( mBitCombo, 4, 5 );
  
  QLabel *bitLabel = new QLabel( i18n("Stream length"), this );
  bitLabel->setAlignment( AlignRight|AlignVCenter );
  mUtilBox->addWidget( bitLabel, 4, 4 );
  max3 = QMAX( max3, bitLabel->sizeHint().width() );
  //bitLabel->setBackgroundColor( Qt::red );

  QFontMetrics fm( mText1[0]->font() );
  for( i=0; i<4; i++ )
  {
    mText1[i]->setFixedHeight( fm.height()+2 );
    mText2[i]->setFixedHeight( fm.height()+2 );
    mText3[i]->setFixedHeight( fm.height()+2 );
    label1[i]->setFixedHeight( fm.height()+2 );
    label2[i]->setFixedHeight( fm.height()+2 );
    label3[i]->setFixedHeight( fm.height()+2 );
    label1[i]->setFixedWidth( max1/* + 5*/ );
    label2[i]->setFixedWidth( max2/* + 5*/ );
    label3[i]->setFixedWidth( max3/* + 5*/ );

    mText1[i]->setLineWidth( 1 );
    mText2[i]->setLineWidth( 1 );
    mText3[i]->setLineWidth( 1 );
    mText1[i]->setFrameStyle( QFrame::Panel | QFrame::Sunken );
    mText2[i]->setFrameStyle( QFrame::Panel | QFrame::Sunken );
    mText3[i]->setFrameStyle( QFrame::Panel | QFrame::Sunken );
    mText1[i]->setBackgroundColor( kapp->palette().normal().base() );
    mText2[i]->setBackgroundColor( kapp->palette().normal().base() );
    mText3[i]->setBackgroundColor( kapp->palette().normal().base() );
    mText1[i]->setAlignment( AlignRight|AlignVCenter );
    mText2[i]->setAlignment( AlignRight|AlignVCenter );
    mText3[i]->setAlignment( AlignRight|AlignVCenter );
    mText1[i]->setMargin( 3 );
    mText2[i]->setMargin( 3 );
    mText3[i]->setMargin( 3 );
    mText1[i]->setText( QString("") );
    mText2[i]->setText( QString("") );
    mText3[i]->setText( QString("") );
  }

  mUtilBox->activate();
  setGeometry( x(), y(), minimumSize().width(), minimumSize().height() );

  connect( kapp, SIGNAL( kdisplayPaletteChanged() ),
	   SLOT( paletteChanged() ) );
  connect( kapp, SIGNAL( kdisplayFontChanged() ),
	   SLOT( fontChanged() ) );

  mCursorState.valid = false;
  mViewHexCaps = true;

  setMinimumSize( sizeHint() );
  show();
}


CHexToolWidget::~CHexToolWidget( void )
{
}


void CHexToolWidget::writeConfiguration( KConfig &config )
{
  config.setGroup("Conversion" );
  config.writeEntry("LittleEndian",  mCheckIntelFormat->isChecked() );
  config.writeEntry("UnsignedAsHex", mCheckHexadecimal->isChecked() );
  config.writeEntry("StreamWindow", mBitCombo->currentItem() );
}

void CHexToolWidget::readConfiguration( KConfig &config )
{
  config.setGroup("Conversion" );
  bool s1  = config.readBoolEntry( "LittleEndian", true );
  bool s2  = config.readBoolEntry( "UnsignedAsHex", false );
  int  val = config.readNumEntry( "StreamWindow", 0 );

  mCheckIntelFormat->setChecked( s1 );
  mCheckHexadecimal->setChecked( s2 );
  mBitCombo->setCurrentItem( val );
}

//++cg[6/7/1999]: handler for change signal from bit width combo
void CHexToolWidget::bitWidthChanged( int /*i*/ )
{
  cursorChanged( mCursorState );
}


//
// Variable bitwidth. Based on Craig Graham's work.
//
// ++cg[6/7/1999]: Read n bit's from a bitstream (allows N length bit 
// values to cross byte boundarys).
//
unsigned long CHexToolWidget::bitValue( SCursorState &state, int n )
{
  static const unsigned char bitmask[9] =
  {
    0, 1<<7, 3<<6, 7<<5, 15<<4, 31<<3, 63<<2, 127<<1, 255
  };

  unsigned long rtn = 0;
  unsigned char *byte = state.data;
  int bit = 7 - state.cell;

  while( n )
  {
    //
    // c hold's current byte, shifted to put remaining bits in 
    // high bits of byte
    //
    unsigned char c = *byte << bit;
	
    //
    // if there are n bits or more remaining in this byte, we 
    // swallow n bits, otherwise we swallow as many
    // bits as we can (8-bit)
    //
    int this_time = ((8-bit)>=n)?n:(8-bit);

    //
    // mask to get only the bit's we're swallowing
    //
    c &= bitmask[this_time];

    //
    // shift down to get bit's in low part of byte
    //
    c >>= 8-this_time;

    //
    // shift up previous results to make room and OR in the extracted bits.
    //
    rtn = (rtn<<this_time)|c; 

    n   -= this_time;  // update the count of remaining bits
    bit += this_time;  // tell the stream we swallowed some swallowed bits

    //
    // if we've swallowed 8 bits, we zero the bit count and move on to 
    // the next byte
    //
    if( bit==8 )
    {
      bit=0;
      byte++;
    }
  }
	
  return( rtn );
}


void CHexToolWidget::cursorChanged( SCursorState &state )
{
  if( state.valid == true )
  {
    QString buf;
    if( mCheckIntelFormat->isChecked() == true )
    {
      //
      // Assume Little Endian. This is the case for the Intel architecture. 
      // The 'state.data' stores the first byte from file/memory in its 
      // first array element.
      //

      unsigned int i,j,k;
      unsigned long long d;
      float fv;
      double dv;

      i = state.data[5]<<8 | state.data[4];
      j = state.data[7]<<8 | state.data[6];
      d = i | j<<16;
      
      i = state.data[1]<<8 | state.data[0];
      j = state.data[3]<<8 | state.data[2];
      k = i | j<<16;
      d = d<<32 | k;

      memcpy( &fv, &k, 4 );
      memcpy( &dv, &d, 8 );

      if( mCheckHexadecimal->isChecked() == true )
      {
	if( mViewHexCaps == true )
	{
	  buf.sprintf( "0x%02X", state.data[0] );
	  mText1[1]->setText( buf );
	  buf.sprintf( "0x%04X", (unsigned short)i );
	  mText1[3]->setText( buf );
	  buf.sprintf( "0x%08X", k );
	  mText2[1]->setText( buf );
	}
	else
	{
	  buf.sprintf( "0x%02x", state.data[0] );
	  mText1[1]->setText( buf );
	  buf.sprintf( "0x%04x", (unsigned short)i );
	  mText1[3]->setText( buf );
	  buf.sprintf( "0x%08x", k );
	  mText2[1]->setText( buf );
	}

	buf.sprintf( "%d", (char)state.data[0] );
	mText1[0]->setText( buf );
	buf.sprintf( "%d", (short)i );
	mText1[2]->setText( buf );
	buf.sprintf( "%d", (int)k );
	mText2[0]->setText( buf );
	buf.sprintf( "%E", fv );
	mText2[2]->setText( buf );
	buf.sprintf( "%E", dv );
	mText2[3]->setText( buf );
      }
      else
      {
	buf.sprintf( "%d", (char)state.data[0] );
	mText1[0]->setText( buf );
	buf.sprintf( "%u", state.data[0] );
	mText1[1]->setText( buf );
	buf.sprintf( "%d", (short)i );
	mText1[2]->setText( buf );
	buf.sprintf( "%u", (unsigned short)i );
	mText1[3]->setText( buf );
	buf.sprintf( "%d", (int)k );
	mText2[0]->setText( buf );
	buf.sprintf( "%u", k );
	mText2[1]->setText( buf );
	buf.sprintf( "%E", fv );
	mText2[2]->setText( buf );
	buf.sprintf( "%E", dv );
	mText2[3]->setText( buf );
      }
    }
    else
    {
      //
      // Assume Big Endian. This is the case for SUN machines (amongst others)
      // The 'state.data' stores the first byte from file/memory in its 
      // first array element.
      //
      unsigned int i,j,k;
      unsigned long long d;
      float fv;
      double dv;

      i = state.data[5] | state.data[4]<<8;
      j = state.data[7] | state.data[6]<<8;
      d = i<<16 | j;
      
      i = state.data[1] | state.data[0]<<8;
      j = state.data[3] | state.data[2]<<8;
      k = i<<16 | j;
      d = d<<32 | k;

      memcpy( &fv, &k, 4 );
      memcpy( &dv, &d, 8 );
      
      if( mCheckHexadecimal->isChecked() == true )
      {
	if( mViewHexCaps == true )
	{
	  buf.sprintf( "0x%02X", state.data[0] );
	  mText1[1]->setText( buf );
	  buf.sprintf( "0x%04X", (unsigned short)i );
	  mText1[3]->setText( buf );
	  buf.sprintf( "0x%08X", k );
	  mText2[1]->setText( buf );
	}
	else
	{
	  buf.sprintf( "0x%02x", state.data[0] );
	  mText1[1]->setText( buf );
	  buf.sprintf( "0x%04x", (unsigned short)i );
	  mText1[3]->setText( buf );
	  buf.sprintf( "0x%08x", k );
	  mText2[1]->setText( buf );
	}

	buf.sprintf( "%d", (char)state.data[0] );
	mText1[0]->setText( buf );
	buf.sprintf( "%d", (short)i );
	mText1[2]->setText( buf );
	buf.sprintf( "%d", (int)k );
	mText2[0]->setText( buf );
	buf.sprintf( "%E", fv );
	mText2[2]->setText( buf );
	buf.sprintf( "%E", dv );
	mText2[3]->setText( buf );
      }
      else
      {
	buf.sprintf( "%d", (char)state.data[0] );
	mText1[0]->setText( buf );
	buf.sprintf( "%u", state.data[0] );
	mText1[1]->setText( buf );
	buf.sprintf( "%d", (short)i );
	mText1[2]->setText( buf );
	buf.sprintf( "%u", (unsigned short)i );
	mText1[3]->setText( buf );
	buf.sprintf( "%d", (int)k );
	mText2[0]->setText( buf );
	buf.sprintf( "%u", k );
	mText2[1]->setText( buf );
	buf.sprintf( "%E", fv );
	mText2[2]->setText( buf );
	buf.sprintf( "%E", dv );
	mText2[3]->setText( buf );
      }
    }

    int numBits = mBitCombo->currentItem();
    if( numBits == 0 )
    {
      //
      // This is the original stuff
      //
      unsigned char data = (unsigned char)state.data[0];
      if( mViewHexCaps == true )
      {
	buf.sprintf( "%02X", data );
      }
      else
      {
	buf.sprintf( "%02x", data );
      }
      mText3[0]->setText( buf );
      buf.sprintf( "%03o", data );
      mText3[1]->setText( buf );
      
      char bitBuf[32];
      for( int i = 0; i < 8; i++ )
      {
	bitBuf[7-i] = (data&(1<<i)) ? '1' : '0';
      }
      bitBuf[8] = 0;
      mText3[2]->setText( QString(bitBuf) );
    }
    else
    {
      //
      // Variable bitwidth. Based on Craig Graham's work.
      //
      unsigned long data = bitValue( state, numBits );
      if( mViewHexCaps == true )
      {
	buf.sprintf( "%02lX %02lX", (data>>8)&0xFF, data&0xFF );
      }
      else
      {
	buf.sprintf( "%02lx %02lx", (data>>8)&0xFF, data&0xFF );
      }
      mText3[0]->setText( buf );
      buf.sprintf( "%03lo %03lo", (data>>8)&0xFF, data&0xFF );
      mText3[1]->setText( buf );
      char bitBuf[32];
      for( int i = 0; i<numBits; i++ )
      {
	bitBuf[numBits-i-1] = (data&(1L<<i)) ? '1' : '0';
      }
      bitBuf[numBits] = 0;
      mText3[2]->setText( QString(bitBuf) );
    }

    unsigned char data = (unsigned char)state.data[0];
    buf.sprintf( "%c", state.charValid == false ? '.' : (char)(data&0xff) );
    mText3[3]->setText( buf );
  }
  else
  {
    QString str;
    for( int i=0; i<4; i++)
    {
      mText1[i]->setText( str );
      mText2[i]->setText( str );
      mText3[i]->setText( str );
    }
  }

  mCursorState = state;
}



void CHexToolWidget::paletteChanged( void )
{
  for( int i=0; i<4; i++ )
  {
    mText1[i]->setBackgroundColor( kapp->palette().normal().base() );
    mText2[i]->setBackgroundColor( kapp->palette().normal().base() );
    mText3[i]->setBackgroundColor( kapp->palette().normal().base() );  
  }
}


void CHexToolWidget::fontChanged( void )
{
  QFontMetrics fm( mText1[0]->font() );
  for( int i=0; i<4; i++ )
  {
    mText1[i]->setFixedHeight( fm.height()+2 );
    mText2[i]->setFixedHeight( fm.height()+2 );
    mText3[i]->setFixedHeight( fm.height()+2 );
  }
}


void CHexToolWidget::intelFormat( void )
{
  cursorChanged( mCursorState );
}


void CHexToolWidget::unsignedFormat( void )
{
  cursorChanged( mCursorState );
}


void CHexToolWidget::resizeEvent( QResizeEvent */*e*/ )
{
}



void CHexToolWidget::closeEvent( QCloseEvent *e )
{
  e->accept();
  emit closed();
}

