/***************************************************************************
 *   Copyright (C) 2005 by John Myers                                      *
 *   electronerd@electronerdia.net                                         *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "rotoswitch.h"

#include "canvasitemparts.h"
#include "ecnode.h"
#include "libraryitem.h"
#include "switch.h"

#include <klocale.h>
#include <qpainter.h>
#include <cmath>
#include <assert.h>

#include <kdebug.h>

//BEGIN class ECRotoSwitch
Item* ECRotoSwitch::construct( ItemDocument *itemDocument, bool newItem, const char *id )
{
    return new ECRotoSwitch( (ICNDocument*)itemDocument, newItem, id );
}

LibraryItem* ECRotoSwitch::libraryItem()
{
    return new LibraryItem(
                            QString("ec/roto_switch"),
                            i18n("Rotary"),
                            i18n("Switches"),
                            "rotary.png",
                            LibraryItem::lit_component,
                            ECRotoSwitch::construct );
}


ECRotoSwitch::ECRotoSwitch( ICNDocument *icnDocument, bool newItem, const char *id )
: Component( icnDocument, newItem, id ? id : "roto_switch" ),
m_numPositions(0)
{
// 	m_name = i18n("Rotary Switch(WIP)");
	m_name = i18n("Rotary Switch");
    m_desc = i18n("Rotary Switch"); ///< \todo better description for rotoswitch
    QPointArray pa;
    pa.makeArc( -_pinInnerRadius, -_pinInnerRadius, 2*_pinInnerRadius, 2*_pinInnerRadius , 0, 16*360 );
    setItemPoints( pa );
    //setSize( -64, -64, 128, 128 );
    
    //half the side length of the buttons
    int buttonRadius = 10;
    addButton( "go_left", QRect( -_pinOuterRadius/3-buttonRadius, _pinOuterRadius-3*buttonRadius, 2*buttonRadius, 2*buttonRadius ), "<", false );
    addButton( "go_right", QRect(_pinOuterRadius/3-buttonRadius, _pinOuterRadius-3*buttonRadius, 2*buttonRadius, 2*buttonRadius ), ">", false );
    
    /*Variant * v = createProperty( "button_map", Variant::Type::String );
    v->setCaption( i18n("Button Map") );
    v->setAdvanced(true);
    const QString defButtonMap("SSSSSSSSSSSM");
    v->setValue(defButtonMap);
    */
    Variant * v = createProperty( "num_positions", Variant::Type::Int );
    v->setCaption( i18n("Number of Positions") );
    v->setAdvanced(false);
    v->setValue(6);
    v->setMinValue(3);
    m_inNode = createPin(0,height()/2,270,"in");    

    v = createProperty( "bounce", Variant::Type::Bool );
    v->setCaption("Bounce");
    v->setAdvanced(true);
    v->setValue(false);
    

    v = createProperty( "bounce_period", Variant::Type::Double );
    v->setCaption("Bounce Period");
    v->setAdvanced(true);
    v->setUnit("s");
    v->setValue(5e-3);
    

    v = createProperty( "cur_position", Variant::Type::Int );
    v->setHidden( true );
    v->setValue( 0 );
    
    //v = createProperty( "left_momentary", Variant::Type::Bool );
    //v->setCaption(i18n("Left Momentary" ) );
    //v->setValue(false);
}


ECRotoSwitch::~ECRotoSwitch()
{
}


void ECRotoSwitch::dataChanged()
{
    bool bounce = dataBool("bounce");
    int bouncePeriod_ms = int(dataDouble("bounce_period")*1e3);
    m_curPosition = dataInt( "cur_position" );
    setUpSwitches();
    if(m_positions[m_curPosition].posSwitch->state() != Switch::Closed)
    {
        m_positions[m_curPosition].posSwitch->setState(Switch::Closed);
    }
    for(int i = 0; i < m_numPositions; i++)
    {
        m_positions[i].posSwitch->setBounce( bounce, bouncePeriod_ms );
    }
}

inline int roundTo10(int a){return ((a/10)+(a%10<5?0:1))*10;}
void ECRotoSwitch::drawShape( QPainter &p )
{
    initPainter(p);
    
    
    int cx = static_cast<int>(x());
    int cy =  static_cast<int>(y());

    const int rotorRadius = 5;

    
    //draw the rotor
    p.drawEllipse(cx - rotorRadius, cy-rotorRadius, 2*rotorRadius, 2*rotorRadius);
    //and its connection
    p.drawLine(cx, cy+rotorRadius, cx, cy+_pinInnerRadius);
    
    //draw the output positions
    double angleBetweenPositions = (4*M_PI/3)/(m_numPositions - 1);
    //kdDebug() << "drawShape: " << bigRadius << " " << angleBetweenPositions << endl;
    
    /// \internal \brief Round to the nearest multiple of 8    
#define round_8(a)      (((a) > 0) ? int(((a)+4)/8)*8 : int(((a)-4)/8)*8)
    for(int i = 0; i < m_numPositions ; i++)
    {
        double angle = (7*M_PI/6) - (i * angleBetweenPositions);
        int contactX = static_cast<int>(_contactRingRadius * cos(angle));
        int contactY = static_cast<int>(_contactRingRadius * sin(angle));

        p.drawEllipse(cx+contactX-_contactRadius, cy-contactY-_contactRadius, 2*_contactRadius, 2*_contactRadius);
        int pinX, pinY;
        switch(m_positions[i].pinAngle)
        {
        case 180:
            pinX = _pinInnerRadius;
            pinY = round_8(contactY);
            break;
        case 90:
            pinX = round_8(contactX);
            pinY = _pinInnerRadius;
            break;
        case 0:
            pinX = -_pinInnerRadius;
            pinY = round_8(contactY);
            break;
        default:
            assert(!"Bad pin angle");
        }
        p.drawLine(cx+contactX, cy-contactY, cx+pinX, cy-pinY);
        //kdDebug() << contactX <<", "<< contactY <<endl;
    }
#undef round_8
    //draw the connection to the selected position
    double angle = (7*M_PI/6) - (m_curPosition * angleBetweenPositions);
    int contactX = static_cast<int>(_contactRingRadius * cos(angle));
    int contactY = static_cast<int>(_contactRingRadius * sin(angle));
    int rotorX = static_cast<int>(rotorRadius * cos(angle));
    int rotorY = static_cast<int>(rotorRadius * sin(angle));
    p.drawLine(cx+rotorX, cy-rotorY, cx+contactX, cy-contactY);
    
    
    deinitPainter(p);
}

void ECRotoSwitch::buttonStateChanged( const QString & id, bool state )
{
    SwitchPosition& curSP = m_positions[m_curPosition];
    int nextPos = m_curPosition;
    if(m_numPositions < 2)
    {
        return;
    }
    if(!state) //release
    {
        if(!curSP.isMomentary)
        {
            return;
        }
        
        if(m_curPosition == 0)
        {
            nextPos = m_curPosition + 1;
        }
        else if(m_curPosition == m_numPositions - 1)
        {
            nextPos = m_curPosition - 1;
        }
        
    }
    else //press
    {
        if(id == "go_left" && m_curPosition > 0)
        {
            nextPos = m_curPosition - 1;
        }
        else if(id == "go_right" && m_curPosition < m_numPositions - 1)
        {
            nextPos = m_curPosition + 1;
        }

    }
    if(nextPos != m_curPosition)
    {
        SwitchPosition& nextSP = m_positions[nextPos];
        
        curSP.posSwitch->setState(Switch::Open);
        nextSP.posSwitch->setState(Switch::Closed);
        
        m_curPosition = nextPos;
        
        property( "cur_position" )->setValue( m_curPosition );
    }
}


/*!
    Set up the switches according to the button_map 
 * 
 * 
 */
void ECRotoSwitch::setUpSwitches()
{
    if( dataInt("num_positions") == m_numPositions )
    {
        // number of positions didn't change, so we don't have to do anything.
        return;
    }
    //this uses the _old_ value of m_numPositions!
    for(int i=0; i<m_numPositions; i++)
    {
        SwitchPosition& sp = m_positions[i];
        QString pinName = QString("pin_%1").arg(i);
        removeNode(pinName);
        removeSwitch(sp.posSwitch);
    }
    
    m_numPositions = dataInt("num_positions");
    if(m_curPosition >= m_numPositions )
    {
        setActivePosition( m_numPositions - 1 );
    }
    m_positions.clear();///\todo readjust old pins        
    m_positions.reserve(m_numPositions);
    double angleBetweenPositions = (4*M_PI/3)/(m_numPositions - 1);
    //kdDebug() << "setUpSwitches: " << bigRadius << " " << angleBetweenPositions << endl;
    for( int i = 0; i < m_numPositions; i++)
    {
        double angle = (7*M_PI/6) - (i * angleBetweenPositions);
        int contactX = static_cast<int>(_contactRingRadius * cos(angle));
        int contactY = static_cast<int>(_contactRingRadius * sin(angle));
        
        SwitchPosition sp;
        if(angle > 3*M_PI/4)
        {
            sp.pinAngle = 0;
            contactX = -_pinOuterRadius;
        }
        else if(angle > M_PI/4)
        {
            sp.pinAngle = 90;
            contactY=_pinOuterRadius;
        }
        else 
        {
            sp.pinAngle = 180;
            contactX = _pinOuterRadius;
        }
        // kdDebug() << contactX <<", "<< contactY <<endl;
        
        
        sp.node = createPin(contactX,-contactY,sp.pinAngle,QString("pin_%1").arg(i));
        sp.posSwitch = createSwitch(m_inNode, sp.node, true);
        sp.isMomentary = false;//(map[i] == 'M');
        m_positions.push_back(sp);
    }
    updateAttachedPositioning();

	// redraw ourself
	setChanged();
}

/*!
 * Set the current position to \c newPosition updating the state of the switch. 
 * \c m_curPosition must reference a valid position to switch away from
 * 
 * \param newPosition the position to switch to
 */
void ECRotoSwitch::setActivePosition(int newPosition)
{        
    SwitchPosition& curSP = m_positions[m_curPosition];
    SwitchPosition& nextSP = m_positions[newPosition];
    
    curSP.posSwitch->setState(Switch::Open);
    nextSP.posSwitch->setState(Switch::Closed);
    
    m_curPosition = newPosition;
    
    property( "cur_position" )->setValue( m_curPosition );
    
}
//END class ECRotoSwitch
