/* -*- 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 <Schematic.h>

#include <Types.h>
#include <Utils.h>
#include <Logger.h>
#include <Strings.h>
#include <Setup.h>
#include <Exception.h>
#include <CircuitLine.h>
#include <MainWindow.h>
#include <SchematicFrame.h>
#include <ComponentFactory.h>

#include <qdom.h>
#include <qcstring.h>
#include <qtextstream.h>
#include <qmessagebox.h>

#include <stdlib.h>
#include <iostream>

using namespace std;

//-----------------------------------------------------------------
Schematic::Schematic()
    : name_(""),
    portCount_(0),
    maxNodeNr_(0),
    hasChanged_( FALSE ),
    isSolved_( FALSE ),
    yn_(0)
{
  componentList_.setAutoDelete( TRUE );
  sdata_.setAutoDelete( TRUE );
  ydata_.setAutoDelete( TRUE );
  zdata_.setAutoDelete( TRUE );
  setSize( mediumSize );
}

//-----------------------------------------------------------------
Schematic::~Schematic()
{
  componentList_.clear();
  sdata_.clear();
  ydata_.clear();
  zdata_.clear();
  if ( yn_ )
    {
      delete yn_;
    }
}

//-----------------------------------------------------------------
Schematic& Schematic::operator=( const Schematic& schematic)
{
  componentList_.clear();
  nodeList_.clear();
  lineList_.clear();
  portCount_ = 0;
  maxNodeNr_ = 0;
  hasChanged_ = FALSE;
  isSolved_ = FALSE;
  if ( yn_ )
    {
      delete yn_;
      yn_ = 0;
    }

  Schematic& sch = (Schematic&) schematic;

  Component* component = 0;
  for ( component = sch.componentList_.first();
        component != 0;
        component = sch.componentList_.next() )
    {
      Component* c = ComponentFactory::createComponent( component->getName(),
                     component->getCenter() );
      addComponent( c );
      c->copyData( *component );
    }

  CircuitLine* line = 0;
  for ( line = sch.lineList_.first();
        line != 0;
        line = sch.lineList_.next() )
    {
      CircuitNode* node1 = findNodeAt( line->startPoint() );
      CircuitNode* node2 = findNodeAt( line->endPoint() );
      if ( node1 == 0 )
        {
          node1 = new CircuitNode( line->startPoint() );
          addNode( node1 );
        }
      if ( node2 == 0 )
        {
          node2 = new CircuitNode( line->endPoint() );
          addNode( node2 );
        }
      addLine( new CircuitLine( *node1, *node2 ) );
    }

  return *this;
}

//-----------------------------------------------------------------
void Schematic::setName(const QString& name)
{
  name_ = name;
}

//-----------------------------------------------------------------
const QString& Schematic::getName() const
  {
    return name_;
  }

//-----------------------------------------------------------------
void Schematic::setSize( SchematicSize size )
{
  size_ = size;
  switch ( size_ )
    {
    case smallSize:
      width_ = 300;
      height_ = 200;
      break;

    case mediumSize:
      width_ = 510;
      height_ = 340;
      break;

    case largeSize:
      width_ = 750;
      height_ = 500;
      break;
    }

  if ( SchematicFrame::instance() )
    {
      SchematicFrame::instance()->schematicSizeChanged( name_ );
    }
}

//-----------------------------------------------------------------
Schematic::SchematicSize Schematic::getSize() const
  {
    return size_;
  }

//-----------------------------------------------------------------
uint Schematic::width() const
  {
    return width_;
  }

//-----------------------------------------------------------------
uint Schematic::height() const
  {
    return height_;
  }

//-----------------------------------------------------------------
uint Schematic::getNumberOfNodes() const
  {
    return maxNodeNr_;
  }

//-----------------------------------------------------------------
uint Schematic::getNumberOfPorts() const
  {
    return portCount_;
  }

//-----------------------------------------------------------------
void Schematic::addComponent(Component* component)
{
  componentList_.append( component );
  component->setSchematic( this );
  component->initComponent();
}

//-----------------------------------------------------------------
void Schematic::removeComponent( Component* component )
{
  ASSERT( componentList_.remove( component ) );
  removeEmptyNodes();
}

//-----------------------------------------------------------------
void Schematic::addNode( CircuitNode* node )
{
  hasChanged_ = TRUE;
  nodeList_.append( node );
  node->setCircuit( this );
  if ( node->isPortNode() )
    {
      portCount_++;
      node->setNodeNumber(portCount_);
    }
}

//-----------------------------------------------------------------
void Schematic::removeNode( CircuitNode* node )
{
  hasChanged_ = TRUE;
  bool isPortNode = node->isPortNode();
  ASSERT( nodeList_.remove( node ) );
  if ( isPortNode )
    {
      portCount_--;
      renumberPortNodes();
    }
}

//-----------------------------------------------------------------
void Schematic::addLine(CircuitLine* line)
{
  hasChanged_ = TRUE;
  lineList_.append(line);
}

//-----------------------------------------------------------------
void Schematic::removeLine(CircuitLine* line)
{
  hasChanged_ = TRUE;
  ASSERT( lineList_.remove(line) );
  delete line;
}

//-----------------------------------------------------------------
void Schematic::writeToStream( QTextStream& stream )
{
  Component *component;
  for ( component=componentList_.first(); component != 0; component=componentList_.next() )
    {
      component->writeToStream( stream );
    }
  CircuitLine* line = 0;
  for ( line = lineList_.first(); line != 0; line = lineList_.next() )
    {
      stream << "<LINE>" << endl;
      stream << "<START X=\"" << (line->startPoint()).x() << "\" ";
      stream << "Y=\"" << (line->startPoint()).y() << "\" />" << endl;
      stream << "<STOP X=\"" << (line->endPoint()).x() << "\" ";
      stream << "Y=\"" << (line->endPoint()).y() << "\" />" << endl;
      stream << "</LINE>" << endl;
    }
}

//-----------------------------------------------------------------
bool Schematic::readFromDOM( QDomElement& element )
{
  QDomNode node = element.firstChild();
  while( !node.isNull() )
    {
      QDomElement childElement = node.toElement();
      if( !childElement.isNull() )
        {
          if ( childElement.tagName() == "COMPONENT" )
            {
              QString type = childElement.attribute( "TYPE" );
              Component* component = ComponentFactory::createComponent( type, QPoint(0,0) );
              ASSERT( component != 0);
              addComponent( component );
              if ( !component->readFromDOM( childElement ) )
                {
                  return FALSE;
                }
            }
          else if ( childElement.tagName() == "LINE" )
            {
              bool success = readLineFromDOM( childElement );
              if ( !success )
                {
                  return FALSE;
                }
            }
          else
            {
              ASSERT( "Unknown circuit element in file!" == 0 );
            }
        }
      node = node.nextSibling();
    }
  return TRUE;
}

//-----------------------------------------------------------------
bool Schematic::readLineFromDOM( QDomElement& element )
{
  QPoint start, stop;
  QDomNode node = element.firstChild();
  while( !node.isNull() )
    {
      QDomElement childElement = node.toElement();
      if( !childElement.isNull() )
        {
          QString xStr = childElement.attribute( "X" );
          QString yStr = childElement.attribute( "Y" );
          int x = xStr.toInt();
          int y = yStr.toInt();
          if ( childElement.tagName() == "START" )
            {
              start.setX( x );
              start.setY( y );
            }
          else if ( childElement.tagName() == "STOP" )
            {
              stop.setX( x );
              stop.setY( y );
            }
          else
            {
              return FALSE;
            }
        }
      node = node.nextSibling();
    }
  CircuitNode* startNode = findNodeAt(start);
  CircuitNode* stopNode = findNodeAt(stop);
  if ( !startNode )
    {
      startNode = new CircuitNode(start);
      addNode(startNode);
    }
  if ( !stopNode )
    {
      stopNode = new CircuitNode(stop);
      addNode(stopNode);
    }
  CircuitLine* line = new CircuitLine(*startNode, *stopNode);
  addLine( line );
  return TRUE;
}

//-----------------------------------------------------------------
CircuitNode* Schematic::findNodeAt( const QPoint& point )
{
  CircuitNode* node = 0;
  for ( node = nodeList_.first(); node != 0; node = nodeList_.next() )
    {
      if ( node->pos() == point )
        {
          return node;
        }
    }
  return 0;
}

//-----------------------------------------------------------------
CircuitLine* Schematic::findLineAt( const QPoint& point,
                                    bool orthoganalOnly )
{
  CircuitLine* line = 0;
  for (line = lineList_.first(); line != 0; line = lineList_.next())
    {
      int distance = distanceFromLine(point, *line, orthoganalOnly );
      if ( (distance>=0) && (distance<5))
        {
          return line;
        }
    }
  return 0;
}

//-----------------------------------------------------------------
Component* Schematic::findComponentAt( const QPoint& point )
{
  Component* selectedComponent = 0;
  Component* component = 0;
  for (component = componentList_.first(); component != 0; component = componentList_.next())
    {
      QRect rect = component->getBoundingRect();
      if ( rect.contains(point) )
        {
          selectedComponent = component;
        }
    }
  return selectedComponent;
}

//-----------------------------------------------------------------
Component* Schematic::findTextAt( const QPoint& point )
{
  Component* selectedComponent = 0;
  Component* component = 0;
  for (component = componentList_.first(); component != 0; component = componentList_.next())
    {
      QRect rect = component->getTextRect();
      if ( rect.contains(point) )
        {
          selectedComponent = component;
        }
    }
  return selectedComponent;
}

//-----------------------------------------------------------------
int Schematic::distanceFromLine( const QPoint& point,
                                 const CircuitLine& line,
                                 bool orthoganalOnly )
{
  int xs = line.startPoint().x();
  int ys = line.startPoint().y();
  int xe = line.endPoint().x();
  int ye = line.endPoint().y();

  int x1 = (xs<xe) ? xs : xe;
  int y1 = (ys<ye) ? ys : ye;
  int x2 = (xs<xe) ? xe : xs;
  int y2 = (ys<ye) ? ye : ys;

  int distance = -1;
  if ( x1 == x2 )
    {
      if ( (point.y() > y1) && (point.y() < y2) )
        {
          distance = abs(line.startPoint().x() - point.x());
        }
    }
  else if ( y1 == y2 )
    {
      if ( (point.x() > x1) && (point.x() < x2) )
        {
          distance = abs(line.startPoint().y() - point.y());
        }
    }
  else if ( !orthoganalOnly )
    {
      TReal Ma = ((TReal) (ys-ye)) / ((TReal) (xs-xe));
      TReal Ca = ys - Ma*xs;
      if ( fabs(Ma) > 1.0 )
        {
          if ( (point.x() > x1) && (point.x() < x2) )
            {
              int x = (int)( (point.y()-Ca)/Ma );
              distance = abs(point.x() - x);
            }
        }
      else
        {
          if ( (point.y() > y1) && (point.y() < y2) )
            {
              int y = (int)( Ma*point.x() + Ca );
              distance = abs(point.y() - y);
            }
        }
    }
  return distance;
}

//-----------------------------------------------------------------
void Schematic::draw( QPainter* p )
{
  p->save();
  p->setPen( Qt::blue );
  //Draw components
  Component* component = 0;
  for (component = componentList_.first();  component != 0;  component = componentList_.next())
    {
      component->draw (p );
    }

  //Draw lines
  CircuitLine* line = 0;
  for ( line = lineList_.first(); line != 0; line = lineList_.next() )
    {
      line->draw( p );
    }

  //Draw nodes
  CircuitNode* node = 0;
  for (node = nodeList_.first(); node != 0; node = nodeList_.next())
    {
      node->draw(p);
    }
  p->restore();
}

//-----------------------------------------------------------------
void Schematic::removeEmptyNodes()
{
  QList<CircuitNode> emptyNodeList;
  CircuitNode* node = 0;
  for (node = nodeList_.first(); node != 0; node = nodeList_.next())
    {
      if ( node->isFree() )
        {
          emptyNodeList.append(node);
        }
    }
  for (node = emptyNodeList.first(); node != 0; node = emptyNodeList.next())
    {
      QString message = QString("Found free node at %1").arg((ulong)node,0,16);
      Logger::debug(message);
      delete node;
    }
}

//-----------------------------------------------------------------
void Schematic::renumberPortNodes()
{
  int count = 0;
  CircuitNode* node = 0;
  for ( node = nodeList_.first(); node != 0; node = nodeList_.next() )
    {
      if ( node->isPortNode() )
        {
          count++;
          node->setNodeNumber( count );
        }
      else if ( node->isGndNode() )
        {
          node->setNodeNumber( 0 );
        }
      else
        {
          node->setNodeNumber( -1 );
        }
    }
  SchematicFrame::instance()->reDraw();
}

//-----------------------------------------------------------------
void Schematic::numberAllNodes()
{
  renumberPortNodes();
  //Number all nets connected to port nodes & ground nodes
  int startNode = 0;
  maxNodeNr_ = 0;
  CircuitNode* node = 0;
  for ( node = nodeList_.first(); node != 0; node = nodeList_.next() )
    {
      if ( node->getNodeNumber() >= 0 )
        {
          int portNodeNumber = node->getNodeNumber();
          if (portNodeNumber > startNode)
            {
              startNode = portNodeNumber;
            }
          QList<CircuitNode> portNodes;
          findAllConnectedNodes(node, portNodes);
          CircuitNode* n = 0;
          for ( n = portNodes.first(); n!=0; n = portNodes.next() )
            {
              if ( n != node )
                {
                  int nodeNumber = n->getNodeNumber();
                  if ( (nodeNumber >= 0) && (nodeNumber != portNodeNumber) )
                    {
                      throw Exception::ShortedPorts();
                    }
                  else
                    {
                      n->setNodeNumber(portNodeNumber);
                    }
                }
            }
        }
    }
  //Now number remaining nodes
  for ( node = nodeList_.first(); node != 0; node = nodeList_.next() )
    {
      if ( node->getNodeNumber() < 0 )
        {
          startNode++;
          node->setNodeNumber( startNode );
          QList<CircuitNode> portNodes;
          findAllConnectedNodes( node, portNodes );
          CircuitNode* n = 0;
          for ( n = portNodes.first(); n!=0; n = portNodes.next() )
            {
              n->setNodeNumber( startNode );
            }
        }
    }
  maxNodeNr_ = startNode;
  if (Setup::instance()->isDebugMode())
    {
      SchematicFrame::instance()->reDraw();
    }
  QString message = QString("Circuit %1 has %2 nodes.").arg(name_).arg(maxNodeNr_);
  Logger::debug(message);
}

//-----------------------------------------------------------------
void Schematic::findAllConnectedNodes(CircuitNode* node,
                                      QList<CircuitNode>& nodeList)
{
  QList<CircuitLine> lineList;
  node->buildLineList(lineList);
  CircuitLine* line = 0;
  for (line = lineList.first(); line != 0; line = lineList.next())
    {
      if ( nodeList.find( line->start() ) < 0)
        {
          nodeList.append( line->start() );
        }
      if ( nodeList.find( line->stop() ) < 0)
        {
          nodeList.append( line->stop() );
        }
    }
}

//-----------------------------------------------------------------
void Schematic::reset()
{
  isSolved_ = FALSE;
  hasChanged_ = TRUE;
}

//-----------------------------------------------------------------
bool Schematic::isSolved() const
  {
    return isSolved_;
  }

//-----------------------------------------------------------------
void Schematic::initSweep()
{
  Component* component = 0;
  for (component=componentList_.first();  component!=0;  component=componentList_.next())
    {
      bool changed = component->initSweep();
      hasChanged_ = hasChanged_ || changed;
    }
  if (hasChanged_)
    {
      //Remove old solution if exist
      Logger::debug("Circuit " + name_ + " has changed - recomputing");
      isSolved_ = FALSE;
      sdata_.clear();
      ydata_.clear();
      zdata_.clear();
    }
}

//-----------------------------------------------------------------
void Schematic::sweep(Vector& frequencies)
{
  if ( isSolved_ )
    {
      return;
    }

  zo_.resize( getNumberOfPorts() );
  CircuitNode* node = 0;
  for ( node=nodeList_.first();  node!=0;  node=nodeList_.next() )
    {
      if ( node->isPortNode() )
        {
          uint nodeNr = node->getNodeNumber() - 1;
          zo_(nodeNr, nodeNr) = node->getImpedance();
        }
    }

  for ( uint i=0; i<frequencies.size(); i++ )
    {
      //Create new nodal admittance matrix
      if ( yn_ != 0 )
        {
          delete yn_;
          yn_ = 0;
        }
      yn_ = new Matrix( getNumberOfNodes()+1 );

      //Add all components to nodal admittance matrix
      Component* comp = 0;
      for (comp=componentList_.first();  comp!=0;  comp=componentList_.next())
        {
          comp->addToAdmittanceMatrix( frequencies.at(i) , yn_ );
        }

      Matrix result( getNumberOfPorts() );

      //Solve matrix
      if ( Setup::instance()->solveByInversion() )
        {
          uint noNodes = getNumberOfNodes();
          (*yn_) = ~(yn_->subMatrix( 1, 1, noNodes, noNodes ));
          for (uint r=0; r<getNumberOfPorts(); r++)
            for (uint c=0; c<getNumberOfPorts(); c++)
              {
                result(r,c) = (*yn_)(r,c);
              }
        }
      else
        {
          for ( uint n=0; n<=getNumberOfPorts(); n++ )
            {
              yn_->swapRows( n, n+1 );
              yn_->swapColumns( n, n+1 );
            }
          yn_->reduce( getNumberOfPorts() );
        }

      DataPoint* dataPoint = new DataPoint( frequencies.at(i), result );
      if ( Setup::instance()->solveByInversion() )
        {
          zdata_.append( dataPoint );
        }
      else
        {
          ydata_.append( dataPoint );
        }

    }

  Logger::debug("Solved circuit " + name_ + " ...");

  isSolved_ = TRUE;
  hasChanged_ = FALSE;
}

//-----------------------------------------------------------------
QList<DataPoint>& Schematic::getSData()
{
  if ( sdata_.isEmpty() )
    {
      QList<DataPoint>& zdata = getZData();
      DataPoint* entry = 0;
      for ( entry=zdata.first(); entry != 0; entry=zdata.next() )
        {
          Matrix data;
          Utils::convertZtoS( entry->data(), zo_, data );
          DataPoint* sdata = new DataPoint( entry->frequency(),
                                            data );
          sdata_.append( sdata );
        }
    }
  return sdata_;
}

//-----------------------------------------------------------------
QList<DataPoint>& Schematic::getYData()
{
  if ( ydata_.isEmpty() )
    {
      ASSERT( zdata_.count() > 0 );
      DataPoint* entry = 0;
      for ( entry=zdata_.first(); entry != 0; entry=zdata_.next() )
        {
          Matrix data = entry->data();
          ~data;
          DataPoint* ydata = new DataPoint( entry->frequency(),
                                            data );
          ydata_.append( ydata );
        }
    }
  return ydata_;
}

//-----------------------------------------------------------------
QList<DataPoint>& Schematic::getZData()
{
  if ( zdata_.isEmpty() )
    {
      ASSERT( ydata_.count() > 0 );
      DataPoint* entry = 0;
      for ( entry=ydata_.first(); entry != 0; entry=ydata_.next() )
        {
          Matrix data = entry->data();
          ~data;
          DataPoint* zdata = new DataPoint( entry->frequency(),
                                            data );
          zdata_.append( zdata );
        }
    }
  return zdata_;
}

//-----------------------------------------------------------------
TComplex Schematic::getPortImpedance( uint port )
{
  ASSERT( port < zo_.rows() );
  return zo_( port, port );
}

//-----------------------------------------------------------------
void Schematic::findComponentsInsideRect( QList<Component>& list,
    QRect& rect )
{
  Component* c = 0;
  for ( c = componentList_.first(); c != 0; c = componentList_.next() )
    {
      QRect componentRect = c->getBoundingRect();
      if ( rect.contains( componentRect ) )
        {
          if ( list.find( c ) < 0 )
            {
              list.append( c );
              c->setSelected( TRUE );
            }
        }
    }
}

//-----------------------------------------------------------------
void Schematic::findNodesInsideRect( QList<CircuitNode>& list,
                                     QRect& rect )
{
  CircuitNode* node = 0;
  for ( node = nodeList_.first(); node != 0; node = nodeList_.next() )
    {
      if ( rect.contains( node->pos() ) )
        {
          if ( list.find( node ) < 0 )
            {
              list.append( node );
              node->setSelected( TRUE );
            }
        }
    }
}

//-----------------------------------------------------------------
void Schematic::findLinesInsideRect( QList<CircuitLine>& list,
                                     QRect& rect )
{
  CircuitLine* line = 0;
  for ( line = lineList_.first(); line != 0; line = lineList_.next() )
    {
      if ( rect.contains( line->startPoint() ) && rect.contains( line->endPoint() ) )
        {
          if ( list.find( line ) < 0 )
            {
              list.append( line );
              line->setSelected( TRUE );
            }
        }
    }
}
