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

#include <Strings.h>
#include <Utils.h>
#include <Setup.h>
#include <GraphView.h>
#include <MainWindow.h>
#include <Exception.h>
#include <NavigationWindow.h>

#include <qdom.h>
#include <iostream>
#include <qtextstream.h>

using namespace std;

//----------------------------------------------------------------------------
GraphDefinition::GraphDefinition( GraphType viewType )
    : type_( viewType )
{
  title_ = "Graph Title";
  results_.setAutoDelete( TRUE );
}

//----------------------------------------------------------------------------
GraphDefinition::~GraphDefinition()
{
  results_.clear();
}

//----------------------------------------------------------------------------
GraphDefinition::GraphType GraphDefinition::graphType() const
  {
    return type_;
  }

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

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

//----------------------------------------------------------------------------
void GraphDefinition::setTitle( const QString& title )
{
  title_ = title;
}

//----------------------------------------------------------------------------
const QString& GraphDefinition::getTitle() const
  {
    return title_;
  }

//----------------------------------------------------------------------------
void GraphDefinition::show()
{
  updateGraph();
  getView()->show();
}

//----------------------------------------------------------------------------
void GraphDefinition::reDraw()
{
  updateGraph();
  if ( getView()->isVisible() )
    {
      getView()->reDraw();
    }
}

//-----------------------------------------------------------------
void GraphDefinition::addOutputDefinition( const ResultDefinition* definition )
{
  results_.append( definition );
}

//-----------------------------------------------------------------
void GraphDefinition::removeOutputDefinition( const ResultDefinition* definition )
{
  ASSERT( results_.remove( definition ) );
  MainWindow::instance()->setFileChanged();
}

//-----------------------------------------------------------------
void GraphDefinition::fill()
{
  getView()->emptySeries();
  ResultDefinition* definition = 0;
  for ( definition = results_.first(); definition != 0; definition = results_.next() )
    {

      const QString& source = definition->source();
      Schematic* schematic = MainWindow::instance()->getSchematic( source );

      if ( schematic == 0 )
        {
          throw Exception::NoSuchCircuit();
        }

      switch ( definition->type() )
        {
        case ResultDefinition::portParameters:
          {
            QList<DataPoint>* data = 0;
            switch ( definition->measurement() )
              {
              case ResultDefinition::sParameters:
                data = &( schematic->getSData() );
                break;
              case ResultDefinition::yParameters:
                data = &( schematic->getYData() );
                break;
              case ResultDefinition::zParameters:
                data = &( schematic->getZData() );
                break;
              case ResultDefinition::groupDelay:
                data = &( schematic->getSData() );
                break;
              }
            addSeries( *definition, *data );
          }
          break;

        case ResultDefinition::linvillStabilityFactor:
        case ResultDefinition::sternStabilityFactor:
          {
            QList<DataPoint>* data = &( schematic->getYData() );
            addSeries( *definition, *data );
          }
          break;
        }
    }
  reDraw();
}

//-----------------------------------------------------------------
void GraphDefinition::addSeries( ResultDefinition& definition, QList<DataPoint>& data )
{
  bool isSmith = ( graphType() == smithType );
  QString legend = ResultDefinition::buildResultDefinitionStr( isSmith, definition );
  uint size = data.count();
  GraphView* graph = getView();
  graph->addSeries( legend, size );
  uint fromNode = definition.from();
  uint toNode = definition.to();
  TReal freqScale = Setup::instance()->getDimensionValue( Strings::FrequencyDim );
  TReal timeScale = Setup::instance()->getDimensionValue( Strings::TimeDim );
  const QString& source = definition.source();
  Schematic* schematic = MainWindow::instance()->getSchematic( source );
  DataPoint* entry = 0;
  TReal previousPhaseValue = 0;
  TReal previousFreqValue = 0;
  bool firstPoint = TRUE;
  for ( entry = data.first(); entry != 0; entry = data.next() )
    {
      TReal freq = entry->frequency() / freqScale;

      Matrix& matrix = entry->data();
      TComplex cvalue = matrix( toNode-1, fromNode-1 );
      if ( isSmith )
        {
          graph->addData( legend, real( cvalue ), imag( cvalue ), size );
        }
      else
        {
          TReal    rvalue = 0;
          switch ( definition.type() )
            {
            case ResultDefinition::portParameters:
              if ( definition.measurement() == ResultDefinition::groupDelay )
                {
                  TReal currentPhase = atan2( imag( cvalue ), real( cvalue ) );
                  if ( firstPoint )
                    {
                      previousPhaseValue = currentPhase;
                      previousFreqValue = freq;
                      firstPoint = FALSE;
                    }
                  TReal phaseDiff = currentPhase - previousPhaseValue;
                  TReal freqDiff = (freq - previousFreqValue)*freqScale;
                  if (phaseDiff >= 1.5*M_PI)	// keep phase linear to one side ...
                    {
                      phaseDiff -= 2*M_PI;
                    }
                  if (phaseDiff <= -1.5*M_PI)	// ...and to the other side
                    {
                      phaseDiff += 2*M_PI;
                    }
                  TReal tD;
                  if ( freqDiff != 0 )
                    {
                      tD = -1.0*(phaseDiff) / freqDiff;
                    }
                  else
                    {
                      tD = 0;
                    }
                  rvalue = tD / timeScale;
                  previousPhaseValue = currentPhase;
                  previousFreqValue = freq;
                }
              else
                {
                  switch ( definition.format() )
                    {
                    case ResultDefinition::realFormat:
                      rvalue = real( cvalue );
                      break;
                    case ResultDefinition::imagFormat:
                      rvalue = imag( cvalue );
                      break;
                    case ResultDefinition::magnitudeFormat:
                      rvalue = abs( cvalue );
                      if ( definition.db() )
                        {
                          rvalue = 20*log10( rvalue );
                        }
                      break;
                    case ResultDefinition::angleFormat:
                      rvalue = atan2( imag(  cvalue ), real( cvalue ) );
                      rvalue *= Setup::instance()->getDimensionValue( Strings::AngularDim );
                      break;
                    }
                }
              break;
            case ResultDefinition::linvillStabilityFactor:
              if ( (schematic->getNumberOfPorts()) != 2 )
                {
                  throw Exception::StabilityFactorNotDefined();
                }
              rvalue = Utils::linvill( matrix );
              break;
            case ResultDefinition::sternStabilityFactor:
              if ( (schematic->getNumberOfPorts()) != 2 )
                {
                  throw Exception::StabilityFactorNotDefined();
                }
              TComplex z1 = schematic->getPortImpedance( 0 );
              TComplex z2 = schematic->getPortImpedance( 1 );
              rvalue = Utils::stern( matrix, z1, z2 );
              break;
            }
          graph->addData( legend, freq, rvalue, size );
        }
    }
}

//-----------------------------------------------------------------
void GraphDefinition::writeToStream( QTextStream& stream )
{
  QString tag = "";
  switch ( type_ )
    {
    case gridType:
      tag = "GRID";
      break;
    case smithType:
      tag = "SMITH";
      break;
    case tableType:
      tag = "TABLE";
      break;
    default:
      ASSERT( "Unknown graph type!" == 0 );
    }

  stream << "<" << tag << " NAME=\"" << name_ << "\" ";
  stream << " TITLE=\"" << title_ << "\" >" << endl;

  writeGraphSettingToStream( stream );

  ResultDefinition* definition = 0;
  for ( definition = results_.first(); definition != 0; definition = results_.next() )
    {
      definition->writeToStream( stream );
    }

  stream << "</" << tag << ">" << endl;
}

//-----------------------------------------------------------------
bool GraphDefinition::readFromDOM( QDomElement& element )
{
  readGraphSettingFromDOM( element );
  QDomNode node = element.firstChild();
  while( !node.isNull() )
    {
      QDomElement childElement = node.toElement();
      if( (!childElement.isNull()) && (childElement.tagName() == "OUTPUT") )
        {
          ResultDefinition* definition = new ResultDefinition();
          definition->readFromDOM( childElement );
          addOutputDefinition( definition );
          NavigationWindow::instance()->updateOutputDefinition( name_, *definition );
        }
      node = node.nextSibling();
    }
  return TRUE;
}

