//---------------------------------------------------------------------------
// Show qubits' states
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

//using namespace std;

#include <new>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include "QDrawGraph.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
const double QDrawGraph::EPS = 1.0e-10; // Cut-off for showing non-zero
//---------------------------------------------------------------------------
/**
 *  Constructor
 */
QDrawGraph::QDrawGraph(TCanvas *_Canvas) {
  mCanvas = _Canvas;
  mViewingTop = 0;

  mDrawMode = DM_ALL;
  mTopWindowMargin = 30;
  mTopMargin    = 10;
  mBottomMargin = 10;
  mLeftMargin   = 10;
  mRightMargin  = 10;
  mRowMargin    = 10;
  mIAMargin     = 20;
  mAVMargin     = 20;
  mVQMargin     = 20;
  mStrOffset    = 10;
  SetPrecision(5);
  SetFontSizeOfQbit(14);
  SetFontSizeOfValue(14);
  SetSizeOfArrow(40);
}
//---------------------------------------------------------------------------
/**
 *  Destructor
 */
QDrawGraph::~QDrawGraph() {
}
//---------------------------------------------------------------------------
/**
 *  Draw all states
 */
void
QDrawGraph::DrawAll(const QBits *qbits, const int type) {

  mCanvas->Brush->Color = clWhite;
  mCanvas->FillRect(Rect(0,0,mImageWidth,mImageHeight));
  SetSize(qbits);
  int num_state = qbits->GetNumberOfStates();
  for (int i = 0; i < num_state; i++) {
    DrawNthRow(qbits, i, i);
  }
}
//---------------------------------------------------------------------------
/**
 *  Draw Only NonZero States
 */
void
QDrawGraph::DrawNonZero(const QBits *qbits) {
  mCanvas->Brush->Color = clWhite;
  mCanvas->FillRect(Rect(0,0,mImageWidth,mImageHeight));

  std::vector<int> vq;
  for (int i=0;i<qbits->GetNumberOfStates();i++) {
    double re = qbits->NthStateR(i);
    double im = qbits->NthStateI(i);
    if (re*re+im*im > EPS) {
      vq.push_back(i);
    }
  }
  for (int i=0;i<(int)vq.size();i++) {
    DrawNthRow(qbits,vq[i],i);
  }
}
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::Paint(const QBits *qbits) {
  switch (GetDrawMode()) {
  case DM_ALL:
    DrawAll(qbits);
    break;
  case DM_NONZERO:
    DrawNonZero(qbits);
    break;
  }
}
//---------------------------------------------------------------------------
void
QDrawGraph::SetDrawMode(int Mode,const QBits *qbits) {
  mDrawMode = Mode;
  SetSize(qbits);
  SetViewingTop(0);
  switch (GetDrawMode()) {
  case DM_ALL:
    SetSize(qbits);
    break;
  case DM_NONZERO:
    SetSizeNonZero(qbits);
    break;
  }
}
//---------------------------------------------------------------------------
void
QDrawGraph::SetSize(const QBits *qbits) {

  int num_state = qbits->GetNumberOfStates();
  SetPageFormat(qbits);

  mWidth = mLeftMargin + mWidthOfIndex + mIAMargin + mWidthOfArrow + mAVMargin
           + mWidthOfValue + mVQMargin + mWidthOfQbits + mRightMargin;
  mHeight = mTopMargin + num_state * (mRowMargin + GetRowHeight())
            + mBottomMargin + mTopMargin + mTopWindowMargin;

  if (mHeight < mImageHeight)mHeight = mImageHeight;
}
//---------------------------------------------------------------------------
void
QDrawGraph::SetViewingTop(int t) {
  if (t<0) {
    t=0;
  } else if (t > (mHeight- mImageHeight)) {
    t = (mHeight- mImageHeight);
  }
  mViewingTop = t;
}
//---------------------------------------------------------------------------
/**
 * Get size to show only non-zero states
 */
void
QDrawGraph::SetSizeNonZero(const QBits *qbits) {

  int num_state = GetNonZeroCount(qbits);
  SetPageFormat(qbits);

  mWidth = mLeftMargin + mWidthOfIndex + mIAMargin + mWidthOfArrow + mAVMargin
           + mWidthOfValue + mVQMargin + mWidthOfQbits + mRightMargin;
  mHeight = mTopMargin + num_state * (mRowMargin + GetRowHeight())
            + mBottomMargin + mTopMargin + mTopWindowMargin;

  if (mHeight < mImageHeight)mHeight = mImageHeight;
}

//---------------------------------------------------------------------------
// Utility functions
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
int
QDrawGraph::GetRowHeight(void) const {
  int max_height;
  max_height = mHeightOfArrow >= mHeightOfValue ? mHeightOfArrow : mHeightOfValue;
  max_height = max_height >= mHeightOfQbits ? max_height : mHeightOfQbits;
  max_height = max_height >= mHeightOfIndex ? max_height : mHeightOfIndex;
  return max_height;
}
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::SetPageFormat(const QBits *qbits) {
  const double LOG10 = 2.3025850929940456840179914546844;
  int num_state = qbits->GetNumberOfStates();
  int num_qbits = qbits->GetNumberOfQBits();

//==========
  mWidthOfArrow = mHeightOfArrow = mSizeOfArrow;
//==========
  int num_index = (int)(std::log((double)num_state)/LOG10) + 1;
  AnsiString as1 = "";
  for (int i = 0; i < num_index; i++) as1 += "0";
  mCanvas->Font->Size = FSIZE_INDEX;
  mWidthOfIndex = mCanvas->TextWidth(as1);
  mHeightOfIndex = mCanvas->TextHeight("0");

//==========
  as1 = "-- + i..00";
  for (int i = 0; i < mPrecision; i++) as1 += "00";
  mCanvas->Font->Size = mFontSizeOfValue;
  mWidthOfValue    = mCanvas->TextWidth(as1);
  mHeightOfValue   = mCanvas->TextHeight("0");

//==========
  as1 = "|";
  for (int i = 0; i < num_qbits; i++) as1 += "0";
  as1 += ">";
  mCanvas->Font->Size = mFontSizeOfQbit;
  mWidthOfQbits  = mCanvas->TextWidth(as1);
  mHeightOfQbits = mCanvas->TextHeight("0");
}
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::DrawNthRow(const QBits *qbits, const int nq, const int nrow) const {
  int left, bottom, max_height;
  //judge whether this qubits is in viewport;
  int h = mTopMargin + (nrow) * (mRowMargin + GetRowHeight())
          + mTopMargin + mTopWindowMargin;
  if (h > mViewingTop+mHeight || h < (mRowMargin+mViewingTop)) {
    return;
  }

  double re = qbits->NthStateR(nq);
  double im = qbits->NthStateI(nq);

  max_height = GetRowHeight();

  left   = mLeftMargin;
  bottom = mTopMargin + max_height + (max_height + mRowMargin ) * nrow;
  bottom -= mViewingTop;

  bottom -= mStrOffset;
  DrawIndex(left, bottom - mHeightOfIndex, nq);
  bottom += mStrOffset;
  left   += mWidthOfIndex + mIAMargin;

  DrawArrow(left, bottom - mHeightOfArrow, re, im);
  bottom -= mStrOffset;
  left   += mWidthOfArrow + mAVMargin;

  DrawValue(left, bottom - mHeightOfValue, re, im);
  left   += mWidthOfValue + mVQMargin;
  DrawQbit(left, bottom - mHeightOfQbits, qbits->GetNumberOfQBits(), nq);
}
//---------------------------------------------------------------------------
// Drawing Methods
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::DrawIndex(const int x, const int y, const int num) const {
  mCanvas->Brush->Color = clWhite;
  mCanvas->Font->Color = clBlack;
  mCanvas->Font->Size = FSIZE_INDEX;
  mCanvas->TextOut(x, y, IntToStr(num));
}
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::DrawArrow(const int x, const int y, const double re, const double im) const {
  int default_length = mWidthOfArrow / 2;
  int xfrom = x + default_length;
  int yfrom = y + default_length;
  double norm  = std::sqrt(re * re + im * im);

  mCanvas->Brush->Color = clWhite;
  mCanvas->Pen->Color = clBlack;
  mCanvas->Pen->Width = 2;
  mCanvas->Ellipse(x, y, x + mWidthOfArrow, y + mHeightOfArrow);
  mCanvas->Pen->Color = clRed;
  mCanvas->MoveTo(xfrom, yfrom);

  default_length *= 0.9;
  if (norm != 0.0)
    mCanvas->LineTo(xfrom + default_length * re / norm, yfrom - default_length * im / norm);
}
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::DrawValue(const int x, const int y, double re, double im) const {
  std::stringstream ss;
  ss << std::setprecision(mPrecision);
  ss.setf(std::ios::fixed);
  ss << re;
  if (im >= 0){
    ss << " + ";
  } else {
    ss << " - ";
    im = -im;
  }
  ss << im;
  ss << "i";
  mCanvas->Brush->Color = clWhite;
  mCanvas->Font->Color = clRed;
  mCanvas->Font->Size = mFontSizeOfValue;
  mCanvas->TextOut(x, y, ss.str().c_str());
}
//---------------------------------------------------------------------------
/**
 *
 */
void
QDrawGraph::DrawQbit(const int x, const int y, const int ntotal, int nth) const {
  string s =">";
  nth = nth < 0 ? -nth : nth;
  for (int i = 0; i < ntotal; i++) {
    if ((nth >> i) & 1) s = "1" + s;
    else s = "0" + s;
  }
  s = "|" + s;
  mCanvas->Brush->Color = clWhite;
  mCanvas->Font->Color = clBlack;
  mCanvas->Font->Size = mFontSizeOfQbit;
  mCanvas->TextOut(x, y, s.c_str());
}
//---------------------------------------------------------------------------
/**
 *  Count How many non-zero number of states
 */
int
QDrawGraph::GetNonZeroCount(const QBits *qbits) {
  int n = 0;
  for (int i=0;i<qbits->GetNumberOfStates();i++) {
    double re = qbits->NthStateR(i);
    double im = qbits->NthStateI(i);
    if (re*re+im*im > EPS) {
      n++;
    }
  }
  return n;
}
//---------------------------------------------------------------------------
