#include <iostream>
#include <qobject.h>
#include <qlist.h>
#include <qmessagebox.h>
#include <qregexp.h>

#include <klocale.h>

using std::cout;
using std::endl;

#include "molinfodialog.h"
#include "drawable.h"
#include "molecule.h"
#include "bond.h"
#include "arrow.h"
#include "bracket.h"
#include "text.h"
#include "chemdata.h"
#include "defs.h"
#include "crings_dialog.h"

// For AutoLayout
class LayoutGroup {
public:
  Drawable *tmp_draw;
  QList<Drawable> items;
  LayoutGroup *left;
  LayoutGroup *right;
  LayoutGroup *above;
  LayoutGroup *below;
  bool placed;
  void Move(double x, double y) {
    cout << "MOVE:" << x << " " << y << endl;
    for (tmp_draw = items.first(); tmp_draw!=0; tmp_draw = items.next()) {
      tmp_draw->SelectAll();
      tmp_draw->Move(x, y);
      tmp_draw->DeselectAll();
    }
  }
  QRect BoundingBox() {
    int top = 99999, bottom = 0, left = 99999, right = 0;
    QRect tmprect;

    for (tmp_draw = items.first(); tmp_draw != NULL; tmp_draw = items.next()) {
      tmp_draw->SelectAll();
      tmprect = tmp_draw->BoundingBox();
      tmp_draw->DeselectAll();
      if (tmprect.isValid()) {
	if (tmprect.left() < left) left = tmprect.left();
	if (tmprect.right() > right) right = tmprect.right();
	if (tmprect.top() < top) top = tmprect.top();
	if (tmprect.bottom() > bottom) bottom = tmprect.bottom();
      }
    }
    return QRect( QPoint(left,top), QPoint(right,bottom) );
  }
  QPoint Center() {
    return BoundingBox().center();
  }
};

// Determine Molecule clicked, do Tool action
void ChemData::Tool(DPoint *target, int mode) {
  Molecule *m = 0;
  Text *tt, *tt_mw, *tt_ef, *tt_ea;
  MolInfoDialog mi(r, "mol info");
  QString tmpname;

  for (tmp_draw = drawlist.first(); tmp_draw!=0; tmp_draw = drawlist.next()) {
    if (tmp_draw->Type() == TYPE_MOLECULE) {
      m = (Molecule *)tmp_draw;
      if (m->WithinBounds(target)) break;
      m = 0;
    }
  }
  if (m == 0) return;
  if (mode==MODE_TOOL_CUSTOMRING) {
    CustomRingDialog* dia=new CustomRingDialog(dynamic_cast<QWidget*>(r->parent()));
    dia->setMolecule(m);
    dia->setCustomRings(m_rings);
    dia->exec();
    delete dia;
    return;
  }

  switch (mode) {

  case MODE_TOOL_MOLECULE_INFO:
    tt_mw = m->CalcMW();
    tt_ef = m->CalcEmpiricalFormula();
    tt_ea = m->CalcElementalAnalysis(false);
    mi.setMW(tt_mw->getText());
    mi.setEF(tt_ef->getText());
    mi.setEA(tt_ea->getText());
    if ( !mi.exec() ) return;
    if (mi.isMWChecked()) drawlist.append(tt_mw);
    if (mi.isEFChecked()) drawlist.append(tt_ef);
    break;
  case MODE_TOOL_CALCMW:
    tt = m->CalcMW();
    if (tt != 0) drawlist.append(tt);
    break;
  case MODE_TOOL_CALCEF:
    tt = m->CalcEmpiricalFormula();
    if (tt != 0) drawlist.append(tt);
    break;
  case MODE_TOOL_CALCEA:
    tt = m->CalcElementalAnalysis();
    if (tt != 0) drawlist.append(tt);
    break;
  case MODE_TOOL_13CNMR:
    m->Calc13CNMR();
    break;
  case MODE_TOOL_1HNMR:
    m->Calc1HNMR();
    break;
  case MODE_TOOL_IR:
    m->CalcIR();
    break;
  case MODE_TOOL_NAME:
    m->CalcName();
    break;
  case MODE_TOOL_TOSMILES:
    tmpname = m->ToSMILES();
    if (tmpname.length() == 0) {
      cout << "Could not get SMILES string!" << endl;
    }
    QMessageBox::information(r, i18n("SMILES string"), i18n("SMILES string for selected molecule:") + "\n\n" + tmpname);
    break;
  case MODE_TOOL_CLEANUPMOL:
    m->CleanUp();
    break;
  case MODE_TOOL_GROUP_REACTANT:
    m->setGroupType(GROUP_REACTANT);
    break;
  case MODE_TOOL_GROUP_PRODUCT:
    m->setGroupType(GROUP_PRODUCT);
    break;
  case MODE_TOOL_GROUP_CLEAR:
    m->setGroupType(GROUP_NONE);
    break;
  }
  // Need to pick next tool manually
}

void ChemData::clearAllGroups() {
  Molecule *m = 0;
  QString tmpname;

  for (tmp_draw = drawlist.first(); tmp_draw!=0; tmp_draw = drawlist.next()) {
    if (tmp_draw->Type() == TYPE_MOLECULE) {
      m = (Molecule *)tmp_draw;
      m->setGroupType(GROUP_NONE);
    }
  }
}

// calculate molecular weights of Molecules
// AutoLayout
void ChemData::AutoLayout() {
  QList<LayoutGroup> layout;
  LayoutGroup *tmp_lo, *tl, *tr, *ta, *tb, *tl1;
  int dista, distb, distl, distr, d1, d2, d3, d4, ds;
  Text *tmp_text;
  Arrow *tmp_arrow;
  Drawable *td2;

  // first, put Arrows and Molecules into LayoutGroups
  for (tmp_draw = drawlist.first(); tmp_draw != NULL;
       tmp_draw = drawlist.next() ) {
    if (tmp_draw->Type() == TYPE_ARROW) {
      tmp_lo = new LayoutGroup;
      tmp_lo->items.append(tmp_draw);
      tmp_lo->placed = false;
      tmp_lo->left = 0;
      tmp_lo->right = 0;
      tmp_lo->above = 0;
      tmp_lo->below = 0;
      layout.append(tmp_lo);
    }
    if (tmp_draw->Type() == TYPE_MOLECULE) {
      tmp_lo = new LayoutGroup;
      tmp_lo->items.append(tmp_draw);
      tmp_lo->placed = false;
      tmp_lo->left = 0;
      tmp_lo->right = 0;
      tmp_lo->above = 0;
      tmp_lo->below = 0;
      layout.append(tmp_lo);
    }
  }
  // now, attach Text to Arrows as needed
  for (tmp_lo = layout.first(); tmp_lo != 0; tmp_lo = layout.next()) {
    td2 = tmp_lo->items.first();
    if (td2->Type() == TYPE_ARROW) {
      tmp_arrow = (Arrow *)td2;
      for (tmp_draw = drawlist.first(); tmp_draw != NULL;
	   tmp_draw = drawlist.next() ) {
	if (tmp_draw->Type() == TYPE_TEXT) {
	  tmp_text = (Text *)tmp_draw;
	  int ns;
	  QPoint amid = tmp_arrow->Midpoint();
	  QPoint tcenter = tmp_text->NearestCenter(amid,
						   tmp_arrow->Orientation(),
						   ns);
	  int dx = tcenter.x() - amid.x();
	  int dy = tcenter.y() - amid.y();
	  double dist = sqrt(dx*dx + dy*dy);
	  cout << dist << endl;
	  if (dist < 25) {
	    if (tmp_arrow->Orientation() == ARROW_HORIZONTAL) {
	      if (dy < 0) { // above arrow
		dy = dy + 12;
		tmp_text->ForceMove(-dx, -dy);
	      } else { // below arrow
		dy = dy - 12;
		tmp_text->ForceMove(-dx, -dy);
	      }
	    } else {  // ARROW_VERTICAL
	      if (dx < 0) { // above arrow
		dx = dx + 12;
		tmp_text->ForceMove(-dx, -dy);
	      } else { // below arrow
		dx = dx - 12;
		tmp_text->ForceMove(-dx, -dy);
	      }
	    }
	    tmp_lo->items.append(tmp_text); // add Text to LayoutGroup
	  } // if (dist...)
	} // if (...TYPE_TEXT)
      } // for (...)
    } // if (...TYPE_ARROW)
  } // for(...)
  // Now determine position of LayoutGroups
  QListIterator<LayoutGroup> it(layout);
  for (tmp_lo = layout.first(); tmp_lo != 0; tmp_lo = layout.next()) {
    QRect box = tmp_lo->BoundingBox();
    QPoint l1(box.left(), box.center().y());
    QPoint r1(box.right(), box.center().y());
    QPoint a1(box.top(), box.center().x());
    QPoint b1(box.bottom(), box.center().x());
    // check sides
    dista = 9999; distb = 9999; distl = 9999; distr = 9999; ds = 9999;
    tl = 0; tr = 0; ta = 0; tb = 0;
    for ( it.toFirst(); it.current(); ++it ) {
      tl1 = it.current();
      QRect box1 = tl1->BoundingBox();
      QPoint l2(box1.left(), box1.center().y());
      QPoint r2(box1.right(), box1.center().y());
      QPoint a2(box1.top(), box1.center().x());
      QPoint b2(box1.bottom(), box1.center().x());
      d1 = (int)(r->DistanceBetween(l1, r2));
      d2 = (int)(r->DistanceBetween(r1, l2));
      d3 = (int)(r->DistanceBetween(a1, b2));
      d4 = (int)(r->DistanceBetween(b1, a2));
      if (d1 < ds) ds = d1;
      if (d2 < ds) ds = d2;
      if (d3 < ds) ds = d3;
      if (d4 < ds) ds = d4;
      if (d1 == ds) tl = tl1;
      if (d2 == ds) tr = tl1;
      if (d3 == ds) ta = tl1;
      if (d4 == ds) tb = tl1;
    }
    if (tl != 0) {
      cout << "left";
      tl->right = tmp_lo;
      tmp_lo->left = tl;
    }
    if (tr != 0) {
      cout << "right";
      tr->left = tmp_lo;
      tmp_lo->right = tr;
    }
    if (ta != 0) {
      cout << "above";
      ta->below = tmp_lo;
      tmp_lo->above = ta;
    }
    if (tb != 0) {
      cout << "below";
      tb->above = tmp_lo;
      tmp_lo->below = tb;
    }
    cout << endl;
  }
  // Place everything
  // Start with things near arrows
  for (tmp_lo = layout.first(); tmp_lo != 0; tmp_lo = layout.next()) {
    if (tmp_lo->items.first()->Type() == TYPE_ARROW) {
      tmp_lo->placed = true;
      tmp_arrow = (Arrow *)(tmp_lo->items.first());
      if (tmp_arrow->Orientation() == ARROW_HORIZONTAL) {
	// adjust position according to already placed Molecule or Arrow
	if (tmp_lo->left != 0) {
	  if (tmp_lo->left->placed == true) {
	    int dy = tmp_lo->Center().y() - tmp_lo->left->Center().y();
	    tmp_lo->Move(0, -dy);
	  }
	}
	if (tmp_lo->right != 0) {
	  if (tmp_lo->right->placed == true) {
	    int dy = tmp_lo->Center().y() - tmp_lo->right->Center().y();
	    tmp_lo->Move(0, -dy);
	  }
	}
	if (tmp_lo->left != 0) {
	  if (tmp_lo->left->placed == false) {
	    int dy = tmp_lo->left->Center().y() - tmp_lo->Center().y();
	    tmp_lo->left->Move(0, -dy);
	    tmp_lo->left->placed = true;
	  }
	}
	if (tmp_lo->right != 0) {
	  if (tmp_lo->right->placed == false) {
	    int dy = tmp_lo->right->Center().y() - tmp_lo->Center().y();
	    tmp_lo->right->Move(0, -dy);
	    tmp_lo->right->placed = true;
	  }
	}
      } else {  // ARROW_VERTICAL
	// adjust position according to already placed Molecule or Arrow
	if (tmp_lo->above != 0) {
	  if (tmp_lo->above->placed == true) {
	    int dx = tmp_lo->Center().y() - tmp_lo->above->Center().y();
	    tmp_lo->Move(-dx, 0);
	  }
	}
	if (tmp_lo->below != 0) {
	  if (tmp_lo->below->placed == true) {
	    int dx = tmp_lo->Center().x() - tmp_lo->below->Center().x();
	    tmp_lo->Move(-dx, 0);
	  }
	}
	if (tmp_lo->above != 0) {
	  if (tmp_lo->above->placed == false) {
	    int dx = tmp_lo->above->Center().x() - tmp_lo->Center().x();
	    tmp_lo->above->Move(-dx, 0);
	    tmp_lo->above->placed = true;
	  }
	}
	if (tmp_lo->below != 0) {
	  if (tmp_lo->below->placed == false) {
	    int dx = tmp_lo->below->Center().x() - tmp_lo->Center().x();
	    tmp_lo->below->Move(-dx, 0);
	    tmp_lo->below->placed = true;
	  }
	}
      } // if (tmp_arrow...)
    } // if (...TYPE_ARROW)
  } // for (...)
}

void ChemData::fromSMILES(QString sm) {
  Molecule *m1 = new Molecule(r);
  m1->FromSMILES(sm);
  m1->SelectAll();
  drawlist.append(m1);
}

void ChemData::SmartPlace(QString sf, DPoint *t1) {
  if (sf.contains("cyclopentadiene") > 0)
    sf.replace(QRegExp("diene"), "diene-sp");
  double ang1 = -CalculateRingAttachAngle(t1) + 1.5708;
  load(sf);
  tmp_draw = drawlist.last();
  Molecule *m1 = (Molecule *)tmp_draw;
  cout << "angle = " << (ang1 * 180.0 / 3.14159) << " degrees!" << endl;
  tmp_pt = m1->GetRingAttachPoint();
  m1->Rotate(ang1);
  double dx, dy;
  dx = t1->x - tmp_pt->x;
  dy = t1->y - tmp_pt->y;
  m1->Move(dx, dy);
  QList<DPoint> nb = m1->BreakRingBonds(tmp_pt);
  for (tmp_pt = nb.first(); tmp_pt != 0; tmp_pt = nb.next() ) {
    addBond(tmp_pt, t1, 1, tmp_pt->new_order, QColor(0,0,0), true);
    cout << "added a bond" << endl;
  }
}

void ChemData::SmartPlaceToo(QString sf, DPoint *t1) {
  double ang1 = -CalculateRingAttachAngle(t1) + 3.14159;
  load(sf);
  tmp_draw = drawlist.last();
  Molecule *m1 = (Molecule *)tmp_draw;
  cout << "angle = " << (ang1 * 180.0 / 3.14159) << " degrees!" << endl;
  //tmp_pt = m1->GetRingAttachPoint();
  tmp_pt = m1->GetAttachPoint(sf);
  m1->Rotate(ang1);
  double dx, dy;
  dx = t1->x - tmp_pt->x;
  dy = t1->y - tmp_pt->y;
  m1->Move(dx, dy);
  QList<DPoint> nb = m1->BreakRingBonds(tmp_pt);
  for (tmp_pt = nb.first(); tmp_pt != 0; tmp_pt = nb.next() ) {
    addBond(tmp_pt, t1, 1, tmp_pt->new_order, QColor(0,0,0), true);
    cout << "added a bond" << endl;
  }
  // need to put back N if EDANS
  if (sf.contains("edans") > 0) {
    t1->element = "NH";
    Text *nt = new Text(r);
    nt->setPoint(t1);
    nt->setJustify(JUSTIFY_CENTER);
    nt->setText("NH");
    nt->setTextMask("  ");
    addText(nt);
  }
}

void ChemData::SmartPlaceThree(QString sf, DPoint *t1) {
  double ang1 = -CalculateRingAttachAngle(t1);
  load(sf);
  tmp_draw = drawlist.last();
  Molecule *m1 = (Molecule *)tmp_draw;
  cout << "angle = " << (ang1 * 180.0 / 3.14159) << " degrees!" << endl;
  //tmp_pt = m1->GetRingAttachPoint();
  tmp_pt = m1->GetAttachPoint(sf);
  m1->Rotate(ang1);
  double dx, dy;
  dx = t1->x - tmp_pt->x;
  dy = t1->y - tmp_pt->y;
  m1->Move(dx, dy);
  QList<DPoint> nb = m1->BreakRingBonds(tmp_pt);
  for (tmp_pt = nb.first(); tmp_pt != 0; tmp_pt = nb.next() ) {
    addBond(tmp_pt, t1, 1, tmp_pt->new_order, QColor(0,0,0), true);
    cout << "added a bond" << endl;
  }
  // need to put back N
  t1->element = "NH";
  Text *nt = new Text(r);
  nt->setPoint(t1);
  nt->setJustify(JUSTIFY_CENTER);
  nt->setText("NH");
  nt->setTextMask("  ");
  addText(nt);
}

double ChemData::CalculateRingAttachAngle(DPoint *t1) {
  double a1;
  Molecule *m = 0;

  for (tmp_draw = drawlist.first(); tmp_draw!=0; tmp_draw = drawlist.next()) {
    if (tmp_draw->Type() == TYPE_MOLECULE) {
      m = (Molecule *)tmp_draw;
      break;
    }
  }
  if (m == 0) return 0.0;

  a1 = m->CalculateRingAttachAngle(t1);

  return a1;
}

