/* -*- C++ -*-
 
  This file is part of ViPEC
  Copyright (C) 1991-2000 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 <SchematicFrame.h>
#include <SchematicWindow.h>

#include <Types.h>
#include <Strings.h>
#include <Utils.h>
#include <Setup.h>
#include <Logger.h>
#include <VectorFont.h>
#include <CircuitLine.h>
#include <Component.h>
#include <Schematic.h>
#include <ToolBar.h>
#include <SymbolBar.h>
#include <MainWindow.h>
#include <SchematicWindow.h>
#include <ComponentFactory.h>
#include <EditAttributesWindow.h>

#include "images/arrow.xpm"
#include "images/rotate.xpm"
#include "images/line.xpm"
#include "images/sweep.xpm"
#include "images/delete.xpm"

#include <qpen.h>
#include <qprinter.h>
#include <qpainter.h>
#include <qfileinfo.h>
#include <qpopupmenu.h>
#include <qmessagebox.h>
#include <qapplication.h>

#include <iostream>

using namespace std;

SchematicFrame* SchematicFrame::instance_ = 0;

//-----------------------------------------------------------------
SchematicFrame* SchematicFrame::instance()
{
  return instance_;
}

//-----------------------------------------------------------------
SchematicFrame::SchematicFrame( QWidget* parent, const char* name,
                                WFlags f, bool )
    : QFrame(parent, name, f),
    activeSchematic_(0),
    dragging_( FALSE ),
    drawing_( FALSE ),
    selecting_( FALSE ),
    gridEnabled_( FALSE ),
    gridSpacing_( 8 ),
    printer_(0),
    editAttributeWindow_(0)
{
  ASSERT( instance_ == 0 );
  instance_ = this;
  setFrameStyle( Box + Plain );

  selectedComponents_.setAutoDelete( FALSE );
  selectedLines_.setAutoDelete( FALSE );
  selectedNodes_.setAutoDelete( FALSE );
  selectedText_ = 0;

  createContextMenus();
}

//-----------------------------------------------------------------
SchematicFrame::~SchematicFrame()
{
  instance_ = 0;
  if ( printer_ )
    {
      delete printer_;
    }
}

//-----------------------------------------------------------------
QString SchematicFrame::getActiveSchematicName()
{
  QString name = "";
  if ( activeSchematic_ )
    {
      name = activeSchematic_->getName();
    }
  return name;
}

//-----------------------------------------------------------------
void SchematicFrame::setActiveSchematic( Schematic* schematic )
{
  activeSchematic_ = schematic;
  selectedComponents_.clear();
  selectedLines_.clear();
  selectedNodes_.clear();
  selectedText_ = 0;
  if ( activeSchematic_ )
    {
      setFixedSize( activeSchematic_->width(), activeSchematic_->height() );
      SchematicWindow::instance()->setSchematicSize( activeSchematic_->width(),
          activeSchematic_->height() );
      show();
      reDraw();
    }
  else
    {
      hide();
    }
}

//-----------------------------------------------------------------
void SchematicFrame::schematicSizeChanged( const QString& name )
{
  if ( activeSchematic_ )
    {
      if ( activeSchematic_->getName() == name )
        {
          setFixedSize( activeSchematic_->width(), activeSchematic_->height() );
          SchematicWindow::instance()->setSchematicSize( activeSchematic_->width(),
              activeSchematic_->height() );

          reDraw();
        }
    }
}

//-----------------------------------------------------------------
void SchematicFrame::deleteSelectedItemSlot()
{
  if ( selectedLines_.count() > 0 )
    {
      CircuitLine* line = 0;
      for (line=selectedLines_.first(); line!=0; line=selectedLines_.next())
        {
          activeSchematic_->removeLine( line );
        }
      selectedLines_.clear();
      activeSchematic_->removeEmptyNodes();
    }
  if ( selectedComponents_.count() > 0 )
    {
      Component* comp = 0;
      for (comp=selectedComponents_.first(); comp!=0; comp=selectedComponents_.next())
        {
          activeSchematic_->removeComponent( comp );
        }
      selectedComponents_.clear();
    }
  reDraw();
}

//-----------------------------------------------------------------
void SchematicFrame::paintEvent(QPaintEvent *)
{
  QPainter painter( this );
  draw( &painter );
}

//-----------------------------------------------------------------
void SchematicFrame::print()
{
  if ( printer_ == 0 )
    {
      printer_ = new QPrinter;
    }
  if ( printer_->setup( this ) )
    {
      QPainter painter( printer_ );
      QRect frameRect = rect();
      QRect printerRect = painter.window();
      TReal aspectPrinter = ((TReal) printerRect.width())/
                            ((TReal) printerRect.height());
      TReal aspectFrame = ((TReal) frameRect.width())/
                          ((TReal) frameRect.height());
      int imageWidth = 0;
      int imageHeight = 0;
      if (aspectFrame < aspectPrinter) //Height of frame limiting factor
        {
          imageHeight = printerRect.height();
          imageWidth = (int) floor( imageHeight * aspectFrame );
        }
      else
        {
          imageWidth = printerRect.width();
          imageHeight = (int) floor( imageWidth / aspectFrame );
        }
      int borderX = (printerRect.width() - imageWidth) / 2;
      int borderY = (printerRect.height() - imageHeight) / 2;
      painter.setWindow( frameRect );
      painter.setViewport( borderX, borderY, imageWidth, imageHeight );
      Component* comp = 0;
      for (comp=selectedComponents_.first(); comp!=0; comp=selectedComponents_.next())
        {
          comp->setSelected( FALSE );
        }
      selectedComponents_.clear();
      draw( &painter );
    }
}

//-----------------------------------------------------------------
void SchematicFrame::draw(QPainter* p)
{
  drawGrid(p);
  if ( activeSchematic_ )
    {
      p->setPen( Qt::black );
      QRect frameRect = rect();
      Utils::growRect(frameRect, -2);
      p->drawRect(frameRect);
      frameRect.setHeight(30);
      p->drawRect(frameRect);
      p->drawLine( 100, 2, 100, 32 );
      p->drawLine( 320, 2, 320, 32 );
      QString circuitName = "CIRCUIT: " + (activeSchematic_->getName()).upper();
      QFileInfo fileInfo( MainWindow::instance()->getFilename() );
      QString filename=  fileInfo.fileName();
      filename = "FILENAME: " + filename.upper();
      VectorFont::instance()->drawText("ViPEC (C) 2001", p, QPoint(10,20));
      VectorFont::instance()->drawText(filename, p, QPoint(110,20));
      VectorFont::instance()->drawText(circuitName, p, QPoint(330,20));
      activeSchematic_->draw( p );
    }
}

//-----------------------------------------------------------------
void SchematicFrame::reDraw()
{
  QPainter paint( this );
  QRect rect = paint.window();
  paint.eraseRect(rect);
  draw(&paint);
  //update the main toolbar
  ToolBar::instance()->updateToolbarIcons( anythingSelected() );
}

//-----------------------------------------------------------------
void SchematicFrame::drawGrid(QPainter* p)
{
  if ( !gridEnabled_ )
    {
      return;
    }

  QPen pen(Qt::darkGray, 1, Qt::SolidLine);
  p->setPen(pen);

  QRect rect = p->window();
  for (int x = gridSpacing_; x<rect.width(); x += gridSpacing_)
    for (int y = gridSpacing_; y<rect.height(); y += gridSpacing_)
      {
        QPoint point(x, y);
        p->drawPoint(point);
      }
}

//-----------------------------------------------------------------
void SchematicFrame::mouseDoubleClickEvent( QMouseEvent* e )
{
  Logger::debug("Mouse double click event");
  QPoint point = e->pos();
  Component* component = findComponentAt( point );
  if ( component != 0 )
    {
      Component::AttributeList& attrList = component->getAttributeList();
      if ( attrList.count() == 0 )
        {
          return;
        }
      if ( editAttributeWindow_ )
        {
          delete editAttributeWindow_;
        }
      QString title = Strings::translate( Strings::EditComponentAttributesWindowTitle );
      editAttributeWindow_ = new EditAttributesWindow( MainWindow::instance(), title );
      editAttributeWindow_->setAttributeList( attrList );
      editAttributeWindow_->show();
    }
  else
    {
      Logger::debug("No components found!");
    }
}

//-----------------------------------------------------------------
void SchematicFrame::mousePressEvent(QMouseEvent* e)
{
  if ( e->button() == RightButton )
    {
      menu_->setItemEnabled( deleteMenuID_, anythingSelected() );
      menu_->setItemEnabled( rotateMenuID_, anythingSelected() );
      menu_->popup( e->globalPos() );
      return;
    }
  if ( ToolBar::instance()->isEditingMode() )
    {
      placingEvent( e );
      ToolBar::instance()->updateToolbarIcons( anythingSelected() );
      return;
    }
  if ( ToolBar::instance()->isDrawingMode() )
    {
      drawingEvent( e );
    }
}

//-----------------------------------------------------------------
void SchematicFrame::placingEvent(QMouseEvent* e)
{
  QPoint point = e->pos();
  Utils::snapToGrid(point, gridSpacing_);
  lastMousePos_ = point;

  selecting_ = FALSE;
  dragging_ = FALSE;
  drawing_ = FALSE;

  QString name = SymbolBar::instance()->getSelectedSymbolName();
  if (name != "")
    {
      placeComponent(name, point);
      return;
    }

  if ( e->state() != ControlButton )
    {
      clearSelection();
    }

  Logger::debug("Searching for components at mouse click position");
  Component* component = findComponentAt( e->pos() );
  if ( component )
    {
      Logger::debug("Component found");
      if ( selectedComponents_.find( component ) < 0 )
        {
          Logger::debug("Selected component added to list");
          selectedComponents_.append( component );
          component->setSelected( TRUE );
        }
      reDraw();
      dragging_ = TRUE;
      lastMousePos_ = point;
      return;
    }
  else
    {
      Logger::debug("Could not find any components");
    }

  Logger::debug("Searching for nodes at mouse click position");
  CircuitNode* node = findNodeAt(point);
  if (node)
    {
      if ( !node->isComponentNode() )
        {
          selectedNodes_.clear();
          selectedNodes_.append( node );
          dragging_ = TRUE;
        }
      return;
    }

  Logger::debug("Searching for lines at mouse click position");
  CircuitLine* line = findLineAt( e->pos() );
  if ( line )
    {
      selectedLines_.append( line );
      line->setSelected( TRUE );
      reDraw();
      return;
    }

  Logger::debug("Searching for component text at mouse click position");
  if ( Setup::instance()->isComponentTextEnabled() )
    {
      selectedText_ = findTextAt( e->pos() );
      if ( selectedText_ )
        {
          dragging_ = TRUE;
          lastMousePos_ = point;
          return;
        }
    }

  Logger::debug("Entered box selection mode");
  selecting_ = TRUE;
  lastMousePos_ = e->pos();
}

//-----------------------------------------------------------------
void SchematicFrame::drawingEvent(QMouseEvent* e)
{
  Logger::debug("Drawing event - clearing selection");
  clearSelection();
  dragging_ = FALSE;
  selecting_ = FALSE;

  QPoint point = e->pos();
  Utils::snapToGrid( point, gridSpacing_ );

  //Check if clicked on node
  CircuitNode* node = findNodeAt( point );
  CircuitLine* line = findLineAt( point, TRUE );

  if ( line )
    {
      node = new CircuitNode( point );
      activeSchematic_->addNode( node );
      line->breakAndInsertNode( activeSchematic_, node );
    }

  if ( node != 0 )
    {
      drawing_ = TRUE;
      selectedNodes_.append( node );
      return;
    }
  else
    {
      drawing_ = FALSE;
    }
}

//-----------------------------------------------------------------
void SchematicFrame::placeComponent(const QString& name, const QPoint& point)
{
  Component* component = ComponentFactory::createComponent( name, point );

  //Init symbol and add to list
  if (component != 0)
    {
      activeSchematic_->addComponent( component );
      clearSelection();
      selectedComponents_.append( component );
      component->setSelected( TRUE );
      paintEvent(0);
      ;
    }
}

//-----------------------------------------------------------------
void SchematicFrame::mouseMoveEvent(QMouseEvent* e)
{
  if ( selecting_ )
    {
      reDraw();
      QPainter painter( this );
      painter.setPen( QPen( Qt::red, 1, Qt::DashLine ) );
      QRect rect = Utils::makeRect( lastMousePos_, e->pos() );
      painter.drawRect( rect );
      return;
    }

  //Check if needed to process event
  if ( (!drawing_) && (!dragging_) )
    {
      return;
    }

  //Check if mouse moved from previous processing
  QPoint point = e->pos();
  Utils::snapToGrid(point, gridSpacing_);

  bool mousePositionChanged = ( point != lastMousePos_ );

  QPoint delta = point - lastMousePos_;

  if ( dragging_ && selectedComponents_.count() && mousePositionChanged )
    {
      Component* comp = 0;
      for ( comp=selectedComponents_.first(); comp != 0; comp = selectedComponents_.next() )
        {
          comp->moveRel(delta);
        }
      MainWindow::instance()->setFileChanged();
    }

  if ( dragging_ && selectedNodes_.count() && mousePositionChanged )
    {
      CircuitNode* node = 0;
      for (node=selectedNodes_.first(); node!=0; node=selectedNodes_.next())
        {
          if ( !node->isComponentNode() )
            {
              node->pos() += delta;
            }
        }
      MainWindow::instance()->setFileChanged();
    }

  if ( drawing_ && selectedNodes_.count() )
    {
      if ( !mousePositionChanged )
        {
          reDraw();
        }
      QPainter painter( this );
      painter.setPen( Qt::red );
      painter.moveTo(selectedNodes_.first()->pos());
      painter.lineTo( e->pos() );
    }

  if ( dragging_ && selectedText_ )
    {
      QPoint textDelta = e->pos() - lastMousePos_;
      reDraw();
      QRect rect = selectedText_->getTextRect();
      rect.moveBy( textDelta.x(), textDelta.y() );
      QPainter painter( this );
      painter.setPen( Qt::red );
      painter.drawRect( rect );
      return;
    }

  if ( mousePositionChanged )
    {
      reDraw();
      lastMousePos_ = point;
    }

}

//-----------------------------------------------------------------
void SchematicFrame::mouseReleaseEvent(QMouseEvent* e)
{
  Logger::debug("Mouse release event");
  //If selecting a block
  if ( selecting_ )
    {
      Logger::debug("Selection box defined");
      QPoint releasePos = e->pos();
      QRect rect = Utils::makeRect( lastMousePos_, releasePos );
      activeSchematic_->findComponentsInsideRect( selectedComponents_, rect );
      activeSchematic_->findLinesInsideRect( selectedLines_, rect );
      activeSchematic_->findNodesInsideRect( selectedNodes_, rect );
      reDraw();
      selecting_ = FALSE;
      return;
    }

  //If dragging text
  if ( dragging_ && selectedText_ )
    {
      Logger::debug("Dragging text");
      QPoint point = e->pos();
      QPoint offset = point - lastMousePos_;
      selectedText_->setTextOffset( offset );
      selectedText_ = 0;
      reDraw();
      MainWindow::instance()->setFileChanged();
      dragging_ = FALSE;
      return;
    }

  //If dragging a node, check if dropped on another node
  if ( selectedNodes_.count() && (!drawing_) )
    {
      QPoint point = e->pos();
      Utils::snapToGrid(point, gridSpacing_);
      CircuitNode* node = findNodeAt(point);
      if ( node && (node != selectedNodes_.first()) )
        {
          Logger::debug("Node dragged onto another node - removing one!");
          selectedNodes_.first()->replaceWith( node );
          activeSchematic_->removeEmptyNodes();
        }
    }
  dragging_ = FALSE;

  //If drawing a line, create a new node at the release point
  if (drawing_)
    {
      QPoint point = e->pos();
      Utils::snapToGrid( point, gridSpacing_ );
      if ( point != selectedNodes_.first()->pos() )
        {
          CircuitNode* node = findNodeAt( point );
          if ( node == 0 )
            {
              CircuitLine* line = findLineAt( point, TRUE );
              if ( line )
                {
                  node = new CircuitNode( point );
                  activeSchematic_->addNode( node );
                  line->breakAndInsertNode( activeSchematic_, node );
                }
            }
          if ( node == 0 )
            {
              node = new CircuitNode( point );
              activeSchematic_->addNode( node );
            }
          CircuitLine* newline = new CircuitLine( *selectedNodes_.first(), *node );
          activeSchematic_->addLine( newline );
          MainWindow::instance()->setFileChanged();
        }
      reDraw();
    }
  drawing_ = FALSE;
  selectedNodes_.clear();

  selecting_ = FALSE;

}

//-----------------------------------------------------------------
Component* SchematicFrame::findComponentAt(const QPoint& point)
{
  Component* component = 0;
  if ( activeSchematic_ )
    {
      component = activeSchematic_->findComponentAt( point );
    }
  return component;
}

//-----------------------------------------------------------------
Component* SchematicFrame::findTextAt(const QPoint& point)
{
  Component* component = 0;
  if ( activeSchematic_ )
    {
      component = activeSchematic_->findTextAt( point );
    }
  return component;
}


//-----------------------------------------------------------------
CircuitNode* SchematicFrame::findNodeAt( const QPoint& point )
{
  CircuitNode* node = 0;
  if ( activeSchematic_ )
    {
      node = activeSchematic_->findNodeAt( point );
    }
  return node;
}

//-----------------------------------------------------------------
CircuitLine* SchematicFrame::findLineAt( const QPoint& point,
    bool orthoganalOnly )
{
  CircuitLine* line  = 0;
  if ( activeSchematic_ )
    {
      line = activeSchematic_->findLineAt( point, orthoganalOnly );
    }
  return line;
}

//-----------------------------------------------------------------
void SchematicFrame::toggleGrid()
{
  gridEnabled_ = !gridEnabled_;
  reDraw();
}

//-----------------------------------------------------------------
void SchematicFrame::rotateSelectedComponent()
{
  if ( selectedComponents_.count() == 1 )
    {
      selectedComponents_.first()->rotate();
      MainWindow::instance()->setFileChanged();
    }
  reDraw();
}

//-----------------------------------------------------------------
void SchematicFrame::queryNet(CircuitLine* line)
{
  queryNet( line->start() );
}

//-----------------------------------------------------------------
void SchematicFrame::queryNet(CircuitNode* node)
{
  QList<CircuitLine> lineList;
  try
    {
      node->buildLineList(lineList);
      QPainter p(this);
      p.setPen( Qt::red );
      CircuitLine* line = 0;
      for (line = lineList.first(); line != 0; line = lineList.next())
        {
          p.moveTo(line->startPoint());
          p.lineTo(line->endPoint());
        }
    }
  catch (...)
    {
      QString msg = Strings::translate( Strings::MsgFloatingNodes );
      QMessageBox::warning( MainWindow::instance(), Strings::LabelApplicationName,
                            msg );
    }
}

//-----------------------------------------------------------------
void SchematicFrame::clearSelection()
{
  Component* comp = 0;
  for (comp=selectedComponents_.first(); comp!=0; comp=selectedComponents_.next())
    {
      comp->setSelected( FALSE );
    }
  selectedComponents_.clear();
  selectedNodes_.clear();
  CircuitLine* line = 0;
  for ( line=selectedLines_.first(); line!=0; line=selectedLines_.next() )
    {
      line->setSelected( FALSE );
    }
  selectedLines_.clear();
  reDraw();
}

//-----------------------------------------------------------------
void SchematicFrame::createContextMenus()
{
  //create the menus including the submenus
  menu_ = new QPopupMenu( this );
  QPopupMenu* subMenuFile = new QPopupMenu( this );
  QPopupMenu* subMenuTools = new QPopupMenu( this );

  //translate the labels
  QString fileMenuName = Strings::translate(Strings::MenuLabelFile);
  QString toolsMenuName = Strings::translate(Strings::MenuLabelTools);
  QString sweepMenuName = Strings::translate(Strings::TooltipSweepCircuit);
  QString placeSymbolMenuName = Strings::translate(Strings::TooltipPlaceSymbol);
  QString placeLineMenuName = Strings::translate(Strings::TooltipLineSymbol);
  deleteMenuName_ = Strings::translate(Strings::MenuLabelDeleteItem);
  rotateMenuName_ = Strings::translate(Strings::TooltipRotateSymbol);

  //create the Icons, you have to set this explicitely to Small
  QIconSet sweepIcon;
  QIconSet arrowIcon;
  QIconSet lineIcon;

  sweepIcon.setPixmap(QPixmap(sweep_xpm), QIconSet::Small, QIconSet::Normal);
  arrowIcon.setPixmap(QPixmap(arrow), QIconSet::Small, QIconSet::Normal);
  lineIcon.setPixmap(QPixmap(line_xpm), QIconSet::Small, QIconSet::Normal);

  //these Icons will be used for the hack in mousePressEvent()
  rotateIcon_.setPixmap(QPixmap(rotate_xpm), QIconSet::Small, QIconSet::Normal);
  deleteIcon_.setPixmap(QPixmap(delete_xpm), QIconSet::Small, QIconSet::Normal);

  //construct the main menu
  menu_->insertItem( fileMenuName, subMenuFile);
  menu_->insertItem( toolsMenuName, subMenuTools);

  menu_->insertSeparator();

  menu_->insertItem(sweepIcon, sweepMenuName, ToolBar::instance(),
                    SLOT( sweepCircuitSlot() ));
  menu_->insertItem(arrowIcon, placeSymbolMenuName, ToolBar::instance(),
                    SLOT( popupSelectionSlot() ));
  menu_->insertItem(lineIcon, placeLineMenuName, ToolBar::instance(),
                    SLOT( popupToggleLineSlot() ));

  menu_->insertSeparator();

  deleteMenuID_ = menu_->insertItem( deleteIcon_, deleteMenuName_, this,
                                     SLOT( deleteSelectedItemSlot() ) );
  rotateMenuID_ = menu_->insertItem( rotateIcon_, rotateMenuName_, ToolBar::instance(),
                                     SLOT( rotateSymbolSlot()));

  //construct the sub-menus
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelOpen),
                           MainWindow::instance(), SLOT( loadSlot() ) );
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelSave),
                           MainWindow::instance(), SLOT( saveSlot() ) );
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelSaveAs),
                           MainWindow::instance(), SLOT( saveAsSlot() ) );
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelClose),
                           MainWindow::instance(), SLOT( closeSlot() ) );
  subMenuFile->insertSeparator();
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelNewSchematic),
                           MainWindow::instance(), SLOT( newSchematicSlot() ) );
  subMenuFile->insertSeparator();
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelPrint),
                           MainWindow::instance(), SLOT( printSlot() ) );
  subMenuFile->insertSeparator();
  subMenuFile->insertItem( Strings::translate(Strings::MenuLabelQuit),
                           MainWindow::instance(), SLOT( quit() ) );

  subMenuTools->insertItem( Strings::translate(Strings::MenuLabelMicroStripCalc),
                            MainWindow::instance(), SLOT( microStripCalculator() ) );
  subMenuTools->insertItem( Strings::translate(Strings::MenuLabelTuner),
                            MainWindow::instance(), SLOT( tuner() ) );

}


//-----------------------------------------------------------------
bool SchematicFrame::anythingSelected()
{
  return ( selectedLines_.count() || selectedComponents_.count() );
}
