
#include "ads_widgets.h"
#include "ads_debug.h"
#include "ads_workbench.h"
#include <QMetaEnum>
#include <QDebug>


struct MyCustomPalette
{
  QPalette normalPalette;
  QPalette invalidPalette;
  QPalette outOfRangePalette;

  MyCustomPalette()
  {
    invalidPalette.setColor(QPalette::Light, Qt::red);
    invalidPalette.setColor(QPalette::Midlight, Qt::red);
    invalidPalette.setColor(QPalette::Dark, Qt::red);
    invalidPalette.setColor(QPalette::Mid, Qt::red);

    //outOfRangePalette.setColor(QPalette::Light, Qt::red);
    outOfRangePalette.setColor(QPalette::Midlight, Qt::yellow);
    outOfRangePalette.setColor(QPalette::Dark, Qt::green);
    outOfRangePalette.setColor(QPalette::Mid, Qt::blue);
  }
};

static MyCustomPalette& customPalette()
{
  static MyCustomPalette cPal;

  return cPal;
}

//Widgets:

AdsWidgetBase::AdsWidgetBase(QWidget* w):
    dW(w),
    d(0)
{
  if (dW)
    dW->setDisabled(true);
}

void AdsWidgetBase::setWidget(QWidget *w)
{
  dW=w;
}

QWidget *AdsWidgetBase::onAttachTo()
{
  return 0;
}

void AdsWidgetBase::invalidateWidget(const QString & msg)
{
  if (dW)
  {
    dW->setDisabled(true);
    dW->setToolTip(QString("Invalid: ")+msg);
    dW->setPalette(customPalette().invalidPalette);
  }
}

void AdsWidgetBase::attachTo(AdsLogicData & datum, bool /*subwidget*/)
{
  d=&datum;
  dW=onAttachTo();
  if (!dW)
    return;

  //dW->setToolTip(d->objectName());
  {
    QString tt=QString("%1").arg(d->objectName());

    Ads1553MsgQ* msg=d->getMsg();
    QString msgName=msg ? msg->objectName() : "??";
    unsigned int mask=d->rawMask();
    int n=0;
    for(mask>>=1; mask; mask>>=1)
      ++n;

    double f=d->getLsb();
    double min=d->getRawMin()*f;
    double max=d->getRawMax()*f;
    //double min=d->getManipulator().min()*f;
    //double max=d->getManipulator().max()*f;

    QString tmp=QString(" {%1W%2[%3:%4] (%5) [%6:%7]}\n{%8->[%9:%10]}").
            arg(msgName).
            arg(1+d->rawWordPos()).
            arg(d->rawBitPos()).
            arg(n+d->rawBitPos()).
            arg(d->rawlsb()).arg(d->getManipulator().min()).arg(d->getManipulator().max()).arg(f).arg(min).arg(max);


      tt+=tmp;
    dW->setToolTip(tt);
  }
  dW->setDisabled(d->isReadOnly());

  //dW->setPalette(customPalette().outOfRangePalette);

  if (!dW->isEnabled())
  {
    QString ss=dW->styleSheet();
    if (ss.isEmpty() || ss.isNull())
      dW->setStyleSheet(":disabled { color: black;}");
  }
}


AdsWidgetIntRef::AdsWidgetIntRef(QSpinBox& r):
    sb(r)
{
}

void AdsWidgetIntRef::invalidateWidget(const QString &msg)
{
  sb.setDisabled(true);
  sb.setValue(sb.minimum());
  sb.setSpecialValueText(msg);
  sb.setButtonSymbols(QAbstractSpinBox::NoButtons);
  sb.setToolTip(QString("Invalid data source: ")+msg);
}

QWidget *AdsWidgetIntRef::onAttachTo()
{
  //sb.setRange(INT_MIN, INT_MAX);
  int min=d->getManipulator().min();
  int max=d->getManipulator().max();
  sb.setRange(min, max);
  sb.setSizePolicy(QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed));
  sb.setKeyboardTracking(false);

  //sb.setToolTip(d->objectName());

  if (d->isReadOnly())
  {
    sb.setButtonSymbols(QAbstractSpinBox::NoButtons);
  }
  if (!connect(d, SIGNAL(sigUpdated()), SLOT(onUpdated())))
  {
    sb.setDisabled(true);
    sb.setValue(sb.minimum());
    sb.setSpecialValueText("<invalid>");
  }
  else
  {
    onUpdated();
    connect(&sb, SIGNAL(valueChanged(int)), SLOT(usrUpdated(int)));
  }

  return &sb;
}

void AdsWidgetIntRef::onUpdated()
{
  sb.blockSignals(true);
  sb.setValue(d->getValue());
  sb.blockSignals(false);
}

void AdsWidgetIntRef::usrUpdated(int n)
{
  if (d)
  {
    d->setFromUser(n);
  }
}

AdsWidgetInt::AdsWidgetInt():
    AdsWidgetIntRef(theSB)
{
}

AdsWidgetBoolRef::AdsWidgetBoolRef(QCheckBox & r):
    AdsWidgetBase(&r),
    cb(r)
{
}


void AdsWidgetBoolRef::invalidateWidget(const QString &msg)
{
  cb.setDisabled(true);
  AdsWidgetBase::invalidateWidget(msg);
}

QWidget *AdsWidgetBoolRef::onAttachTo()
{
  cb.setToolTip(d->objectName());

  if (!connect(d, SIGNAL(sigUpdated()), SLOT(onUpdated())))
  {
    cb.setDisabled(true);
  }
  else
  {
    onUpdated();
    connect(&cb, SIGNAL(clicked(bool)), SLOT(usrUpdated(bool)));
  }
  return &cb;

}

void AdsWidgetBoolRef::usrUpdated(bool v)
{
  if (d)
  {
    d->setFromUser((int)v);
  }
}

void AdsWidgetBoolRef::onUpdated()
{
  bool x=d->toBool();
  cb.blockSignals(true);
  cb.setChecked(x);
  cb.blockSignals(false);

}

AdsWidgetEnumRef::AdsWidgetEnumRef(QComboBox& r):
    cb(r)
{
}

void AdsWidgetEnumRef::invalidateWidget(const QString &msg)
{
  cb.setDisabled(true);
  cb.clear();
  cb.insertItem(0, msg);
  cb.setCurrentIndex(0);
  cb.setToolTip(QString("Invalid data source: ")+msg);
}

QWidget *AdsWidgetEnumRef::onAttachTo()
{
  cb.setToolTip(d->objectName());
  //cb.setIconSize(QSize(0, 0));
  if (d->isReadOnly())
  {
    //cb.setStyleSheet("QComboBox { padding: 0px 0px 0px 0px;} :disabled { color: black; } ::drop-down { border-style: none; width: 0px; padding: 0 0 0 0;}");
    cb.setStyleSheet(
      "  QComboBox { padding: 0 0 0 2px;} "
      "  :disabled { color: black;}"
      "  ::drop-down { "
      "  position: absolute;"
      "  left: 0;"
      "  subcontrol-origin: content;"
      "  border-style: none;"
      "  width: 0px;"
      "  border-width: 0;"
      " }"
      );
    QFont f=cb.font();
    f.setBold(true);
    cb.setFont(f);
  }
  else
  {
    //cb.setStyleSheet("padding: 0 0 0 2px;");
  }

  QMetaEnum e=d->metaObject()->enumerator(d->metaObject()->enumeratorOffset());

  for(int i=0; i<e.keyCount(); ++i)
  {
    QString tmp=e.key(i);
    if (tmp.at(0).toLatin1()=='_')
      tmp.remove(0, 1);

    cb.addItem(tmp,e.keyToValue(e.key(i)));
  }

  bool ok=connect(d, &AdsLogicData::sigUpdated, this, &AdsWidgetEnumRef::onUpdated);

  if (!ok)//if (!connect(d, SIGNAL(sigUpdated()), SLOT(onUpdated())))
  {
    cb.setDisabled(true);
    cb.setCurrentIndex(0); //p_.cb.minimum());
  }
  else
  {
    onUpdated();
    //connect(&cb, SIGNAL(currentIndexChanged(int)), SLOT(usrUpdated(int)));
    connect(&cb, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AdsWidgetEnumRef::usrUpdated );
  }

  return &cb;
}

void AdsWidgetEnumRef::onUpdated()
{
  int x=d->getValue();
  QVariant v=cb.itemData(cb.currentIndex());
  if (!v.isValid() || v.toInt()!=x)
    cb.setCurrentIndex(d->getValue());
}

void AdsWidgetEnumRef::usrUpdated(int n)
{
  if (d)
  {
    QVariant v=cb.itemData(n);
    if (v.isValid())
      d->setFromUser(v.toInt());
  }
}

AdsWidgetEnum::AdsWidgetEnum():
    AdsWidgetEnumRef(theCB)
{
}


AdsWidgetRealRef::AdsWidgetRealRef(QDoubleSpinBox& r):
    sb(r)
{
}

void AdsWidgetRealRef::invalidateWidget(const QString &msg)
{
  sb.setDisabled(true);
  sb.setValue(sb.minimum());
  sb.setSpecialValueText(msg);
  sb.setButtonSymbols(QAbstractSpinBox::NoButtons);
  sb.setToolTip(QString("Invalid data source: ")+msg);
}

QWidget *AdsWidgetRealRef::onAttachTo()
{
  double f=d->getLsb();
  double min=d->getManipulator().min()*f;
  double max=d->getManipulator().max()*f;
  sb.setRange(min, max);
  if (sb.decimals()<4)
    sb.setDecimals(4);
  sb.setKeyboardTracking(false);

  sb.setToolTip(d->objectName());

  if (d->isReadOnly())
  {
    sb.setButtonSymbols(QAbstractSpinBox::NoButtons);
  }

  if (!connect(d, SIGNAL(sigUpdated()), SLOT(onUpdated())))
  {
    sb.setDisabled(true);
    sb.setValue(sb.minimum());
    sb.setSpecialValueText("<invalid>");
  }
  else
  {
    onUpdated();
    connect(&sb, SIGNAL(valueChanged(double)), SLOT(usrUpdated(double)));
  }

  return &sb;
}


void AdsWidgetRealRef::onUpdated()
{
  sb.blockSignals(true);
  sb.setValue(d->toDouble());
  sb.blockSignals(false);

}

void AdsWidgetRealRef::usrUpdated(double v)
{
  if (d)
  {
    d->setFromUser(v);
  }
}

AdsWidgetReal::AdsWidgetReal():
    AdsWidgetRealRef(theSB)
{
}

AdsWidgetPushButton::AdsWidgetPushButton():
    d(0),
    actPush(0),
    actPushDestroy(false)
{
  setEnabled(false);
  this->setToolButtonStyle(Qt::ToolButtonTextOnly);
  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
}

AdsWidgetPushButton::~AdsWidgetPushButton()
{
  if (actPushDestroy && actPush)
    delete actPush;
}

void AdsWidgetPushButton::setPushAction(AdsDataAction* act)
{
  actPush=act;
}

void AdsWidgetPushButton::attachTo(AdsLogicData& datum, int value)
{
  AdsDataActionSet* act=new AdsDataActionSet;
  actPushDestroy=true;
  act->attachTo(&datum);
  act->setActionValue(value);
  attachTo(datum, act);
}


void AdsWidgetPushButton::attachTo(AdsLogicData& datum, AdsDataAction* act)
{
  d=&datum;
  setEnabled(!d->isReadOnly());
  setText(QString(datum.objectName()));
  //actionValue=value;
  actPush=act;
  connect(this, SIGNAL(clicked()), SLOT(buttonPushed()));
}

void AdsWidgetPushButton::buttonPushed()
{
  if (!d)
    return;
  onPushed();
}

void AdsWidgetPushButton::onPushed()
{
  //d->setFromUser(actionValue);
  if (actPush)
    actPush->execute();
  //if (cbPush)
  //  cbPush(this);
}



AdsWidgetActionRef::AdsWidgetActionRef(QAction &r, AdsDataAction* ads):
    act(r),
    adsAct(ads)
{
  //connect(&r, SIGNAL(triggered(bool)), SLOT(onAdsActionTiggered(bool)));
  connect(&r, static_cast<void (QAction::*)(bool)>(&QAction::triggered), this, static_cast<void (AdsWidgetActionRef::*)(bool)>(&AdsWidgetActionRef::onAdsActionTriggered));

  //connect(&r, SIGNAL(toggled(bool)), SLOT(onAdsActionToggled(bool)));
  connect(&r, &QAction::toggled, this, &AdsWidgetActionRef::onAdsActionToggled);

  connect(&r, SIGNAL(triggered()), SLOT(onAdsActionTriggered()));
  //connect(&r, &QAction::triggered, this, &AdsWidgetActionRef::onAdsActionTriggered);
}

void AdsWidgetActionRef::attachTo(AdsDataAction *a)
{
  adsAct=a;
}

void AdsWidgetActionRef::onAdsActionTriggered(bool checked)
{
  if (adsAct)
    adsAct->execute(checked);
}

void AdsWidgetActionRef::onAdsActionToggled(bool)
{
  onAdsActionTriggered(true);
}

void AdsWidgetActionRef::onAdsActionTriggered()
{
  onAdsActionTriggered(true);
}


AdsWidgetPushButtonRef::AdsWidgetPushButtonRef(QAbstractButton &r):
    AdsWidgetBase(&r),
    pb(r),
    actClicked(0)
{
  pb.setEnabled(true);
  connect(&pb, SIGNAL(clicked()), SLOT(onAdsClicked()));
}

QWidget *AdsWidgetPushButtonRef::onAttachTo()
{
  if (pb.isCheckable())
  {
    connect(d, SIGNAL(sigUpdated()), SLOT(onUpdated()));
    onUpdated();

    connect(&pb, SIGNAL(clicked(bool)), SLOT(onAdsToggled(bool)));

  }
  return &pb;
}

void AdsWidgetPushButtonRef::onUpdated()
{
  bool s=d->toBool();
  if (pb.isChecked()!=s)
    pb.setChecked(s);
}

void AdsWidgetPushButtonRef::onAdsToggled(bool v)
{
  pb.blockSignals(true);
  d->setFromUser((int)v);
  pb.blockSignals(false);
}

void AdsWidgetPushButtonRef::setClickAdsAction(AdsDataAction *a)
{
  actClicked=a;
}

void AdsWidgetPushButtonRef::onAdsClicked()
{
  if (d)
  {
    d->setFromUser(1);
  }
  if (actClicked)
    actClicked->execute();
}

static QString detect_data_source_(QObject* obj, const QString& /*prop_name*/)
{
  QString data;

  QString name=obj->objectName();

  if (!name.isEmpty())
  {
    if (isupper(name.at(0).toLatin1()))
      data=name;
    else if (name.indexOf("_ads_")>-1)
      data=name.section(QString("_ads_"), 1, 1, QString::SectionSkipEmpty|QString::SectionCaseInsensitiveSeps);
  }

  if (data.isEmpty())
  {
    QVariant prop=obj->property("ads_datasource");
    if (prop.isValid() && !prop.isNull())
      data=prop.toString();
  }

  if (!data.isEmpty())
  {
  }
  return data;
}

static QString detect_data_source(QObject* obj)
{
  return detect_data_source_(obj, QString("ads_datasource"));
}

static QString detect_data_action(QObject* obj)
{
  return detect_data_source_(obj, QString("ads_action"));
  /*
  QString data;
  QVariant prop=obj->property("ads_action");
  if (prop.isValid() && !prop.isNull())
    data=prop.toString();
  return data;
  */
}

void AdsWidgetsManager::mapAction(AdsDataAction &ads_action, QAction &q_act, bool add_as_shortcut)
{
  ads_action.connect(&q_act, SIGNAL(triggered()), SLOT(onQAction()));
  if ((q_act.associatedWidgets().size()<=0) && add_as_shortcut)
    IAdsWorkBench::instance().addShortcut(&q_act);
}

void AdsWidgetsManager::installForm(QObject*f)
{
  QObject* obj;
  foreach(obj, f->children())
  {

    QVariant v=obj->property("ads_icon");
    if (!v.isNull() && v.isValid())
    {
      QPushButton* w=qobject_cast<QPushButton*>(obj);
      if (w)
        w->setIcon(QIcon(v.toString()));
    }

    QString data_source=detect_data_source(obj);
    AdsLogicData* d=0;
    if (!data_source.isEmpty())
    {
      d=AdsDataManager::findLogicData(data_source);

      //if (!d)
      //  AdsDebug::log_print(QString("ADS Widget Map FAIL: %1 ==> %2").arg(obj->objectName()).arg(data_source));
    }

    QString data_action=detect_data_action(obj);
    AdsDataAction* act=0;
    if (!data_action.isEmpty())
    {
      act=AdsDataManager::findDataAction(data_action);
      if (!act && d==0)
        AdsDebug::log_print(QString("ADS Widget Action FAIL: %1 ==> %2").arg(obj->objectName()).arg(data_action));
    }

    if (data_source.isEmpty() && data_action.isEmpty())
    {
      installForm(obj);
      continue;
    }

    if (data_source.isEmpty() && act==0 && d==0)
      AdsDebug::log_print(QString("ADS Widget Map FAIL: %1 ==> %2").arg(obj->objectName()).arg(data_source));

    AdsWidgetBase* wb=0;
    AdsWidgetPushButtonRef* btn=0;

    //Detect Widget type
    {
      QDoubleSpinBox *dsb = qobject_cast<QDoubleSpinBox *>(obj);
      if (dsb)
      {
        wb=new AdsWidgetRealRef(*dsb);
      }
      else
      {
        QSpinBox* sb=qobject_cast<QSpinBox *>(obj);
        if (sb)
        {
          wb=new AdsWidgetIntRef(*sb);
        }
        else
        {
          QSlider* sl=qobject_cast<QSlider*>(obj);
          if (sl)
          {
              wb=new AdsWidgetIntSliderRef(*sl);
          }
          else
          {
              QComboBox* cb=qobject_cast<QComboBox *>(obj);
              if (cb)
              {
                wb=new AdsWidgetEnumRef(*cb);
              }
              else
              {
                QCheckBox* cb=qobject_cast<QCheckBox *>(obj);
                if (cb)
                  wb=new AdsWidgetBoolRef(*cb);
                else
                {
                  QAbstractButton* ab=qobject_cast<QAbstractButton*>(obj);
                  if (ab)
                  {
                    wb=btn=new AdsWidgetPushButtonRef(*ab);
                  }
                  else
                  {
                    QAction* q_act=qobject_cast<QAction*>(obj);
                    if (q_act)
                    {
                      if (act)
                      {
                        mapAction(*act, *q_act);
                        AdsDebug::log_print(QString("ADS Action Map: %1 ").arg(obj->objectName()));
                        //act->connect(q_act, SIGNAL(triggered()), SLOT(onQAction()));
                        //if (q_act->associatedWidgets().size()<=0)
                        //  IAdsWorkBench::instance().addShortcut(q_act);
                      }
                      else
                        AdsDebug::log_print(QString("ADS Action Map Fail: %1 ").arg(obj->objectName()));
                      continue;
                    }
                  }
                }
              }
          }
        }
      }
    }

    if (!wb)
    {
      AdsDebug::log_print(QString("ADS Widget Map Fail: %1 - unknown widget type").arg(obj->objectName()));
      continue;
    }

    if (d)
      wb->attachTo(*d, false);
    if (act)
    {
      if (btn)
        btn->setClickAdsAction(act);
      else
        AdsDebug::log_print(QString("ADS Widget Map Error: %1 - action %2 mapped to non button byte").arg(obj->objectName()).arg(data_action));
    }

    if (!d && !(btn && act))
    {
      wb->invalidateWidget(data_source);
      AdsDebug::log_print(QString("ADS Widget Map Fail: %1 to %2, act %3").arg(obj->objectName()).arg(data_source).arg(data_action));
    }
    //else
    //  AdsDebug::log_print(QString("ADS Widget Map OK: %1 to %2, act %3").arg(obj->objectName()).arg(data_source).arg(data_action));
  }
}

//
AdsWidgetIntSliderRef::AdsWidgetIntSliderRef(QSlider& r):
    sb(r)
{
}

void AdsWidgetIntSliderRef::invalidateWidget(const QString &msg)
{
  sb.setDisabled(true);
  sb.setValue(sb.minimum());
  //sb.setSpecialValueText(msg);
  //sb.setButtonSymbols(QAbstractSpinBox::NoButtons);
  sb.setToolTip(QString("Invalid data source: ")+msg);
}

QWidget *AdsWidgetIntSliderRef::onAttachTo()
{
  //sb.setRange(INT_MIN, INT_MAX);
  //int min=d->getManipulator().min();
  //int max=d->getManipulator().max();
  //int min=d->getRawMin();
  //int max=d->getRawMax();
  //int min=d->getManipulator().min();
  //int max=d->getManipulator().max();
    double f=d->getLsb();
    double min=d->getRawMin()*f;
    double max=d->getRawMax()*f;

  sb.setRange(min, max);

  qDebug()<<"Slider"<<sb.minimum()<<sb.maximum();
  //sb.setSizePolicy(QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed));
  //sb.setKeyboardTracking(false);

  //sb.setToolTip(d->objectName());

  if (d->isReadOnly())
  {
    //sb.setButtonSymbols(QAbstractSpinBox::NoButtons);
  }
  if (!connect(d, SIGNAL(sigUpdated()), SLOT(onUpdated())))
  {
    sb.setDisabled(true);
    sb.setValue(sb.minimum());
    //sb.setSpecialValueText("<invalid>");
  }
  else
  {
    onUpdated();
    connect(&sb, SIGNAL(valueChanged(int)), SLOT(usrUpdated(int)));
  }

  return &sb;
}

void AdsWidgetIntSliderRef::onUpdated()
{
  sb.blockSignals(true);
  sb.setValue(d->getValue());
  sb.blockSignals(false);
}

void AdsWidgetIntSliderRef::usrUpdated(int n)
{
  if (d)
  {
    d->setFromUser(n);
  }
}
