/* -*- C++ -*-
 
  This file is part of ViPEC
  Copyright (C) 1991-2001 Johan Rossouw (jrossouw@alcatel.altech.co.za)
 
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Library 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 Library General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
*/

#include <Component.h>

#include <Utils.h>
#include <Setup.h>
#include <Strings.h>
#include <Exception.h>
#include <MainWindow.h>
#include <VectorFont.h>
#include <ComponentFactory.h>

#include <qdom.h>
#include <qpen.h>
#include <qpainter.h>
#include <qtextstream.h>
#include <qmessagebox.h>

#include <iostream>

int Component::nodeSize_ = 3;

//-----------------------------------------------------------------
Component::Component( const QPoint& center )
    : boundingRect_( QRect(0,0,0,0) ),
    centerPoint_( center ),
    textRect_( QRect(0,0,0,0) ),
    textOffset_( QPoint(0,0) ),
    rotation_( 0 ),
    schematic_(0)
{
  attributeList_.setAutoDelete( TRUE );
}

//-----------------------------------------------------------------
Component::~Component()
{
  CircuitNode* node = 0;
  for (node = nodeList_.first(); node != 0; node = nodeList_.next())
    {
      delete node;
    }
}

//-----------------------------------------------------------------
QPoint Component::getCenter() const
  {
    return centerPoint_;
  }

//-----------------------------------------------------------------
int Component::getRotation() const
  {
    return rotation_;
  }

//-----------------------------------------------------------------
void Component::setRotation( int rotation )
{
  for ( int i=0; i<(rotation/90); i++ )
    {
      rotate();
    }
}

//-----------------------------------------------------------------
int Component::getNodeSize()
{
  return nodeSize_;
}

//-----------------------------------------------------------------
void Component::setSchematic( Schematic* schematic )
{
  schematic_ = schematic;
}

//-----------------------------------------------------------------
void Component::writeToStream( QTextStream& stream )
{
  stream << "<COMPONENT TYPE=\"";
  stream << getName() << "\">" << endl;
  stream << "<LOCATION X=\"" << centerPoint_.x() << "\" ";
  stream << "Y=\"" << centerPoint_.y() << "\" ";
  stream << "ROTATION=\"" << rotation_ << "\" />" << endl;
  stream << "<TEXTOFFSET X=\"" << textOffset_.x() << "\" ";
  stream << "Y=\"" << textOffset_.y() << "\" />";
  Attribute* it = 0;
  for( it = attributeList_.first(); it != 0; it = attributeList_.next() )
    {
      stream << "<ATTRIBUTE " << it->symbol_ << "=\"";
      if ( (it->value_).isEmpty() )
        {
          stream << "NULL";
        }
      else
        {
          stream << it->value_;
        }
      stream << "\" />" << endl;
    }
  stream << "</COMPONENT>" << endl;
}

//-----------------------------------------------------------------
bool Component::readFromDOM( QDomElement& element )
{
  QDomNode node = element.firstChild();
  while( !node.isNull() )
    {
      QDomElement childElement = node.toElement();
      if( !childElement.isNull() )
        {
          if ( childElement.tagName() == "LOCATION" )
            {
              QString xStr = childElement.attribute( "X" );
              QString yStr = childElement.attribute( "Y" );
              QString rotStr = childElement.attribute( "ROTATION" );
              int x = xStr.toInt();
              int y = yStr.toInt();
              int rotation = rotStr.toInt();
              moveTo( QPoint(x,y) );
              setRotation( rotation);
            }
          else if ( childElement.tagName() == "TEXTOFFSET" )
            {
              QString xStr = childElement.attribute( "X" );
              QString yStr = childElement.attribute( "Y" );
              int x = xStr.toInt();
              int y = yStr.toInt();
              setTextOffset( QPoint( x, y ) );
            }
          else if ( childElement.tagName() == "ATTRIBUTE" )
            {
              QDomNamedNodeMap map = childElement.attributes();
              for (uint l=0; l<map.length(); l++)
                {
                  QDomNode node = map.item(l);
                  QString name = node.nodeName();
                  QString value = node.nodeValue();
                  Attribute* attribute = findAttribute( name );
                  if ( attribute == 0 )
                    {
                      QMessageBox::warning( MainWindow::instance(),
                                            "ViPEC", "Unknown attribute "
                                            + name +
                                            " encountered while reading circuit file!\n",
                                            "&Ok", 0, 0, 0, 0);
                      return FALSE;
                    }
                  if ( value == "NULL" )
                    {
                      value = "";
                    }
                  attribute->value_ = value;
                }
            }
          else
            {
              return FALSE;
            }
        }
      node = node.nextSibling();
    }
  return TRUE;
}

//-----------------------------------------------------------------
CircuitNode* Component::addNode(int x, int y, bool isPortNode,
                                bool isGndNode, bool isFloatAllowed)
{
  ASSERT( schematic_ );
  QPoint point(x,y);
  point += centerPoint_;
  CircuitNode* node = new CircuitNode( point, TRUE, isPortNode,
                                       isGndNode, isFloatAllowed );
  schematic_->addNode( node );
  nodeList_.append( node );
  return node;
}

//-----------------------------------------------------------------
CircuitNode* Component::node(uint i)
{
  ASSERT(i < nodeList_.count() );
  return nodeList_.at(i);
}

//-----------------------------------------------------------------
void Component::rotate()
{
  rotation_ = (rotation_ + 90) % 360;
  int x = boundingRect_.x();
  int y = boundingRect_.y();
  int w = boundingRect_.width();
  int h = boundingRect_.height();
  boundingRect_ = QRect(y,x,h,w);
  CircuitNode* node = 0;
  for (node = nodeList_.first(); node != 0; node = nodeList_.next())
    {
      node->pos() -= centerPoint_;
      Utils::rotatePoint90Degccw( node->pos() );
      node->pos() += centerPoint_;
    }
}

//-----------------------------------------------------------------
void Component::moveTo(QPoint point)
{
  CircuitNode* node = 0;
  for (node = nodeList_.first(); node != 0; node = nodeList_.next())
    {
      node->pos() -= centerPoint_;
      node->pos() += point;
    }
  centerPoint_ = point;
}

//-----------------------------------------------------------------
void Component::moveRel(QPoint delta)
{
  CircuitNode* node = 0;
  for (node = nodeList_.first(); node != 0; node = nodeList_.next())
    {
      node->pos() += delta;
    }
  centerPoint_ += delta;
}

//-----------------------------------------------------------------
QRect Component::getBoundingRect() const
  {
    QRect rect = boundingRect_;
    rect.moveBy(centerPoint_.x(), centerPoint_.y());
    return rect;
  }

//-----------------------------------------------------------------
bool Component::isInsideBoundingRect(QPoint point) const
  {
    return getBoundingRect().contains(point);
  }

//-----------------------------------------------------------------
QRect Component::getTextRect() const
  {
    QRect rect = textRect_;
    rect.moveBy(centerPoint_.x(), centerPoint_.y());
    return rect;
  }

//-----------------------------------------------------------------
bool Component::isInsideTextRect(QPoint point) const
  {
    return getTextRect().contains(point);
  }

//-----------------------------------------------------------------
void Component::setTextOffset( QPoint offset )
{
  textOffset_ += offset;
}

//-----------------------------------------------------------------
void Component::draw(QPainter* painter)
{
  painter->save();

  if ( isSelected() )
    {
      painter->setPen( Qt::red );
      if ( Setup::instance()->isDebugMode() )
        {
          painter->save();
          painter->setPen( QPen( Qt::red, 1, Qt::DashLine ) );
          QRect rect = getBoundingRect();
          Utils::growRect( rect, 2 );
          painter->drawRect( rect );
          painter->restore();
        }
    }
  else
    {
      painter->setPen( Qt::blue );
    }

  painter->translate( centerPoint_.x(), centerPoint_.y() );
  painter->rotate( rotation_ );
  drawSymbol( painter );
  painter->restore();
  if ( (Setup::instance()->isComponentTextEnabled()) || isSelected() )
    {
      drawAttributes( painter );
    }
}

//-----------------------------------------------------------------
void Component::drawAttributes(QPainter* painter)
{
  painter->save();
  painter->translate( centerPoint_.x(), centerPoint_.y() );
  if ( isSelected() )
    {
      painter->setPen( Qt::red );
    }
  else
    {
      painter->setPen( Qt::black );
    }
  QRect rect = getBoundingRect();
  rect.moveBy(-centerPoint_.x(), -centerPoint_.y());
  int nrAttr = (int) attributeList_.count();
  bool horisonalOrientation = (rotation_ == 0) || (rotation_ == 180);
  int ypos = 0;
  if ( horisonalOrientation )
    {
      ypos = rect.bottom() + 12;
    }
  else
    {
      ypos =  -(nrAttr-1) * 5 + 4;
    }
  ypos += textOffset_.y();

  bool firstRect = true;

  Attribute* it = 0;
  for( it = attributeList_.first(); it != 0; it = attributeList_.next() )
    {
      QString text = it->symbol_ + "=" + it->value_;
      if ( (it->value_).isEmpty() )
        {
          text = text + "?";
        }
      int xpos =  0;
      if ( horisonalOrientation )
        {
          xpos = - VectorFont::textWidth(text)/2;
        }
      else
        {
          xpos = rect.right() + 4;
        }
      xpos += textOffset_.x();
      VectorFont::instance()->drawText(text, painter, QPoint( xpos, ypos ) );
      QRect tempRect( xpos, ypos-8, VectorFont::textWidth(text), 8 );
      if ( firstRect )
        {
          textRect_ = tempRect;
          firstRect = false;
        }
      else
        {
          textRect_ = textRect_.unite( tempRect );
        }
      ypos += 12;
    }
  if ( Setup::instance()->isDebugMode() )
    {
      painter->setPen( Qt::red );
      painter->drawRect( textRect_ );
    }
  painter->restore();
}

//-----------------------------------------------------------------
void Component::addAttribute( const QString& name,
                              const QString& symbol,
                              double value )
{
  Attribute* attribute = new Attribute();
  attribute->name_ = name;
  attribute->symbol_ = symbol;
  if ( value != 0 )
    {
      attribute->value_.setNum( value );
    }
  else
    {
      attribute->value_ = "";
    }
  attributeList_.append( attribute );
}

//-----------------------------------------------------------------
Component::AttributeList& Component::getAttributeList()
{
  return attributeList_;
}

//-----------------------------------------------------------------
Component::Attribute* Component::findAttribute( const QString& symbol )
{
  Attribute* item = 0;
  for( item = attributeList_.first(); item != 0; item = attributeList_.next() )
    {
      if ( (item->symbol_) == symbol )
        {
          break;
        }
    }
  return item;
}

//-----------------------------------------------------------------
const QString& Component::getAttributeStr( const QString& symbol )
{
  Attribute* attr = findAttribute( symbol );
  ASSERT( attr );
  const QString& valueStr = attr->value_;
  return valueStr;
}

//-----------------------------------------------------------------
TReal Component::getAttributeValue( const QString& symbol )
{
  Attribute* attr = findAttribute( symbol );
  ASSERT( attr );
  const QString& valueStr = attr->value_;
  if ( valueStr.isEmpty() )
    {
      throw Exception::AttributeHasNoValue();
    }
  bool ok = FALSE;
  double value = valueStr.toDouble(&ok);
  if ( !ok )
    {
      value = MainWindow::instance()->getVariableValue( valueStr );
    }
  return value;
}

//-----------------------------------------------------------------
TReal Component::getDimensionValue( const QString& name )
{
  return Setup::instance()->getDimensionValue( name );
}

//-----------------------------------------------------------------
void Component::copyData( Component& source )
{
  setRotation( source.getRotation() );
  Attribute* attr = 0;
  for ( attr = attributeList_.first(); attr != 0; attr = attributeList_.next() )
    {
      Attribute* sourceAttr = source.findAttribute( attr->symbol_ );
      ASSERT( sourceAttr != 0 );
      attr->value_ = sourceAttr->value_;
    }
}
