/* ****************************************************************************
  This file is part of KMathTool

  Copyright (C) 2003 by Marco Wegner <mail@marcowegner.de>

  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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************************** */


#include <math.h>

#include <qlabel.h>
#include <qlayout.h>
#include <qstringlist.h>
#include <qvalidator.h>

#include <kcombobox.h>
#include <klineedit.h>
#include <klocale.h>

#include "areaperimeterwidget.h"


AreaPerimeterWidget::AreaPerimeterWidget(Type type, QWidget* parent, const char* name)
  : ImageModuleView(parent, name), _type(type)
{
  // The number of rows of QLabels/KLineEdits needed
  uint numRows = ( _type == Rectangle ? 5 : 4 );

  labels.resize(numRows);
  inputs.resize(numRows);

  QWidget* mainWidget = new QWidget(this);
  QGridLayout* layout = new QGridLayout(mainWidget, numRows+2, 2, 0, 6);

  layout->addWidget(new QLabel(i18n("Given values:"), mainWidget), 0, 0);

  select = new KComboBox(false, mainWidget);
  layout->addWidget(select, 0, 1);

  connect(select, SIGNAL(activated(int)), SLOT(slotItemSelected(int)));

  QLabel* lbl;
  KLineEdit* kle;
  QDoubleValidator* val;

  uint i;
  for (i = 0; i < numRows; i++) {
    lbl = new QLabel(mainWidget);
    layout->addWidget(lbl, i+1, 0);
    labels.insert(i, lbl);

    kle = new KLineEdit(mainWidget);
    val = new QDoubleValidator(kle);
    val->setBottom(0);
    kle->setValidator(val);
    //kle->setAlignment(Qt::AlignRight);
    layout->addWidget(kle, i+1, 1);
    inputs.insert(i, kle);

    lbl->setBuddy(kle);

    connect(kle, SIGNAL(returnPressed( )), this, SIGNAL(signalReturnPressed( )));
    connect(kle, SIGNAL(textChanged(const QString&)), this, SLOT(slotInputChanged( )));
  }

  QStringList comboItems;
  QStringList labelTexts;

  switch (_type) {
    case Circle:
      comboItems << i18n("Radius r") << i18n("Diameter d")
        << i18n("Area A") << i18n("Perimeter U");
      labelTexts << i18n("&Radius r:") << i18n("&Diameter d:")
        << i18n("&Area A:") << i18n("&Perimeter U:");
      break;

    case Rectangle:
      comboItems << i18n("Lengths of sides a and b") << i18n("Length of side a and Area A")
        << i18n("Length of side a and Perimeter U") << i18n("Length of side a and Diagonal d")
        << i18n("Length of side b and Area A") << i18n("Length of side b and Perimeter U")
        << i18n("Length of side b and Diagonal d") << i18n("Area A and Perimeter U")
        << i18n("Area A and Diagonal d") << i18n("Perimeter U and Diagonal d");

      labelTexts << i18n("&Length of side a:") << i18n("L&ength of side b:")
        << i18n("&Area A:") << i18n("&Perimeter U:") << i18n("&Diagonal d:");
      break;

    case Square:
      comboItems << i18n("Length of side a") << i18n("Area A")
        << i18n("Perimeter U") << i18n("Diagonal d");
      labelTexts << i18n("&Length of side a:") << i18n("&Area A:")
        << i18n("&Perimeter U:") << i18n("&Diagonal d:");
      break;
  }

  select->insertStringList(comboItems);

  QStringList::Iterator it;
  for (i = 0, it = labelTexts.begin( ); it != labelTexts.end( ); ++i, ++it)
    labels[i]->setText(*it);

  layout->addMultiCell(new QSpacerItem(0, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), numRows+1, numRows+1, 0, 1);

  setMainWidget(mainWidget);
}

void AreaPerimeterWidget::aboutToActivate( )
{
  slotItemSelected(select->currentItem( ));
  slotClearInput( );
}

void AreaPerimeterWidget::errorMessage(Error err)
{
  QString msg;

  switch (err) {
    case ZeroInput:
      msg = i18n("Using the value \'0\' as input does not lead to useful results.");
      break;
    case InvalidRect:
      msg = i18n("The data you entered does not result in a regular rectangle.");
      break;
  }

  emit signalError(msg);
}

void AreaPerimeterWidget::rectFields(int item, int& a, int& b) {
  int x = 4;
  a = b = 0;
  while (item >= x) {
    a++;
    item -= x--;
  }
  b = item + a + 1;
}

// *******************************
// ***** SLOT implementation *****
// *******************************
void AreaPerimeterWidget::slotCalculate( )
{
  emit signalNewCalculation( );

  int item = select->currentItem( );

  switch (_type) {
    case Circle:
      {
        QString inp = inputs[item]->text( );

        // no input?
        if (inp.isEmpty( )) return;

        // check for double number
        bool ok;
        double in = inp.toDouble(&ok);
        if (!ok) {
          emit signalError(i18n("Invalid input: %1").arg(inp));
          return;
        }

        // check for "0" input
        if (!in) {
          errorMessage(ZeroInput);
          return;
        }

        double r = 0, d = 0, A = 0, U = 0;
        switch (item) {
          case 0:
            r = in;
            d = 2*r;
            A = M_PI*r*r;
            U = M_PI*d;
            break;
          case 1:
            d = in;
            r = d/2;
            A = M_PI*r*r;
            U = M_PI*d;
            break;
          case 2:
            A = in;
            r = sqrt(A/M_PI);
            d = 2*r;
            U = M_PI*d;
            break;
          case 3:
            U = in;
            r = U/(2*M_PI);
            d = 2*r;
            A = M_PI*r*r;
            break;
        }
        inputs[0]->setText(QString("%1").arg(r));
        inputs[1]->setText(QString("%1").arg(d));
        inputs[2]->setText(QString("%1").arg(A));
        inputs[3]->setText(QString("%1").arg(U));
      }
      break;

    case Rectangle:
      {
        // find out which fields
        int m, n;
        rectFields(item, m, n);
        QString inp1 = inputs[m]->text( );
        QString inp2 = inputs[n]->text( );

        // no input?
        if (inp1.isEmpty( ) || inp2.isEmpty( )) return;

        // check for double numbers
        bool ok;
        double in1 = inp1.toDouble(&ok);
        if (!ok) {
          emit signalError(i18n("Invalid input: %1").arg(inp1));
          return;
        }
        double in2 = inp2.toDouble(&ok);
        if (!ok) {
          emit signalError(i18n("Invalid input: %1").arg(inp2));
          return;
        }

        // check for "0" input
        if (in1*in2 == 0) {
          errorMessage(ZeroInput);
          return;
        }

        double a = 0, b = 0, A = 0, U = 0, d = 0;
        switch(item) {
          case 0:       // a, b
            a = in1;
            b = in2;
            A = a*b;
            U = 2*(a+b);
            d = sqrt(a*a+b*b);
            break;
          case 1:       // a, A
            a = in1;
            A = in2;
            b = A/a;
            U = 2*(a+b);
            d = sqrt(a*a+b*b);
            break;
          case 2:       // a, U
            a = in1;
            U = in2;
            if (a >= U/2) {
              errorMessage(InvalidRect);
              return;
            }
            b = U/2 - a;
            A = a*b;
            d = sqrt(a*a+b*b);
            break;
          case 3:       // a, d
            a = in1;
            d = in2;
            if (a*a >= d*d) {
              errorMessage(InvalidRect);
              return;
            }
            b = sqrt(d*d-a*a);
            A = a*b;
            U = 2*(a+b);
            break;
          case 4:       // b, A
            b = in1;
            A = in2;
            a = A/b;
            U = 2*(a+b);
            d = sqrt(a*a+b*b);
            break;
          case 5:       // b, U
            b = in1;
            U = in2;
            if (b >= U/2) {
              errorMessage(InvalidRect);
              return;
            }
            a = U/2-b;
            A = a*b;
            d = sqrt(a*a+b*b);
            break;
          case 6:       // b, d
            b = in1;
            d = in2;
            if (b*b >= d*d) {
              errorMessage(InvalidRect);
              return;
            }
            a = sqrt(d*d-b*b);
            A = a*b;
            U = 2*(a+b);
            break;
          case 7:       // A, U
            A = in1;
            U = in2;
            if (A > U*U/16) {
              errorMessage(InvalidRect);
              return;
            }
            a = U/4 + sqrt(U*U/16-A);
            if (a >= U/2) {
              errorMessage(InvalidRect);
              return;
            }
            b = U/2 - a;
            d = sqrt(a*a+b*b);
            break;
          case 8:       // A, d
            A = in1;
            d = in2;
            if (A*A > d*d*d*d/4 || sqrt(d*d*d*d/4-A*A) >= d*d/2) {
              errorMessage(InvalidRect);
              return;
            }
            b = sqrt(d*d/2+sqrt(d*d*d*d/4-A*A));
            if (b*b >= d*d) {
              errorMessage(InvalidRect);
              return;
            }
            a = sqrt(d*d-b*b);
            U = 2*(a+b);
            break;
          case 9:       // U, d
            U = in1;
            d = in2;
            if (U*U/16 > d*d/2) {
              errorMessage(InvalidRect);
              return;
            }
            a = U/4 + sqrt(d*d/2-U*U/16);
            if (a >= U/2) {
              errorMessage(InvalidRect);
              return;
            }
            b = U/2 - a;
            A = a*b;
            break;
        }
        inputs[0]->setText(QString("%1").arg(a));
        inputs[1]->setText(QString("%1").arg(b));
        inputs[2]->setText(QString("%1").arg(A));
        inputs[3]->setText(QString("%1").arg(U));
        inputs[4]->setText(QString("%1").arg(d));
      }
      break;

    case Square:
      {
        QString inp = inputs[item]->text( );

        // no input?
        if (inp.isEmpty( )) return;

        // check for double number
        bool ok;
        double in = inp.toDouble(&ok);
        if (!ok) {
          emit signalError(i18n("Invalid input: %1").arg(inp));
          return;
        }

        // check for "0" input
        if (!in) {
          errorMessage(ZeroInput);
          return;
        }

        double a = 0, A = 0, U = 0, d = 0;
        switch (item) {
          case 0:
            a = in;
            A = a*a;
            U = 4*a;
            d = sqrt(2)*a;
            break;
          case 1:
            A = in;
            a = sqrt(A);
            U = 4*a;
            d = sqrt(2)*a;
            break;
          case 2:
            U = in;
            a = U/4;
            A = a*a;
            d = sqrt(2)*a;
            break;
          case 3:
            d = in;
            a = d/sqrt(2);
            A = a*a;
            U = 4*a;
            break;
        }
        inputs[0]->setText(QString("%1").arg(a));
        inputs[1]->setText(QString("%1").arg(A));
        inputs[2]->setText(QString("%1").arg(U));
        inputs[3]->setText(QString("%1").arg(d));
      }
      break;
  }

  slotItemSelected(select->currentItem( ));
}

void AreaPerimeterWidget::slotClearInput( )
{
  for (uint i = 0; i < inputs.count( ); i++)
    inputs[i]->clear( );
}

void AreaPerimeterWidget::slotItemSelected(int item)
{
  int sel;
  if (_type == Rectangle) {
    int a, b;
    rectFields(item, a, b);
    for (uint i = 0; i < inputs.count( ); i++)
      inputs[i]->setReadOnly(i != (uint)a && i != (uint)b);
    sel = a;
  } else {
    for (uint i = 0; i < inputs.count( ); i++)
      inputs[i]->setReadOnly(i != (uint)item);
    sel = item;
  }

  inputs[sel]->selectAll( );
  inputs[sel]->setFocus( );

  slotInputChanged( );
}

void AreaPerimeterWidget::slotInputChanged( )
{
  int item = select->currentItem( );

  bool empty;
  if (_type == Rectangle) {
    int a, b;
    rectFields(item, a, b);
    empty = inputs[a]->text( ).isEmpty( ) || inputs[b]->text( ).isEmpty( );
  } else {
    empty = inputs[item]->text( ).isEmpty( );
  }

  emit signalInputPresent(!empty);
}

#include "areaperimeterwidget.moc"
