/***************************************************************************
 *   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 <KLocalizedString>
#include <QPainter>
#include <QPainterPath>
#include <cassert>
#include <cmath>

#include <ktechlab_debug.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(QStringList(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");
    // Q3PointArray pa;    // 2018.08.14 - see below
    // pa.makeArc( -_pinInnerRadius, -_pinInnerRadius, 2*_pinInnerRadius, 2*_pinInnerRadius , 0, 16*360 );
    QPainterPath path;
    path.addEllipse(-_pinInnerRadius, -_pinInnerRadius, 2 * _pinInnerRadius, 2 * _pinInnerRadius);
    QPolygon pa = path.toFillPolygon().toPolygon();

    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);

    /// \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);
    }
#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();
    m_positions.reserve(m_numPositions);
    double angleBetweenPositions = (4 * M_PI / 3) / (m_numPositions - 1);

    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;
        }
        // qCDebug(KTL_LOG) << contactX <<", "<< contactY;

        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
