#include "qwt3d_axis.h"

using namespace Qwt3D;

Axis::Axis()
{
    init();
}

Axis::~Axis() { }

Axis::Axis(Triple beg, Triple end)
{
    init();
    setPosition(beg, end);
}

void Axis::init()
{
    detachAll();

    scale_ = qwt3d_ptr<Scale>(new LinearScale);

    beg_ = Triple(0.0, 0.0, 0.0);
    end_ = beg_;

    majorintervals_ = 0;
    minorintervals_ = 0;
    setMajors(1);
    setMinors(1);
    setLimits(0, 0);

    setTicOrientation(0.0, 0.0, 0.0);
    setTicLength(0.0, 0.0);
    setColor(0.0, 0.0, 0.0);
    setLineWidth(1.0);
    symtics_ = false;
    drawNumbers_ = false;
    drawLabel_ = false;

    drawTics_ = false;
    autoscale_ = true;
    markerLabel_.clear();
    numberfont_ = QFont("Courier", 12);
    setLabelFont(QFont("Courier", 14));

    numbercolor_ = RGBA(0, 0, 0, 0);

    setNumberAnchor(Center);

    numbergap_ = 0;
    labelgap_ = 0;
}

void Axis::setPosition(const Triple &beg, const Triple &end)
{
    beg_ = beg;
    end_ = end;
}

void Axis::setMajors(int val)
{
    if (val == majorintervals_)
        return;

    majorintervals_ = (val <= 0) ? 1 : val; // always >= 1
}

/*!
\see LogScale::setMinors().
*/
void Axis::setMinors(int val)
{
    if (val == minorintervals_)
        return;

    minorintervals_ = (val <= 0) ? 1 : val; // always >= 1
}

void Axis::setTicLength(double majorl, double minorl)
{
    lmaj_ = majorl;
    lmin_ = minorl;
}

void Axis::setTicOrientation(double tx, double ty, double tz)
{
    setTicOrientation(Triple(tx, ty, tz));
}

void Axis::setTicOrientation(const Triple &val)
{
    orientation_ = val;
    orientation_.normalize();
}

/**
\param val thickness for axis base line
\param majfac relative thickness for axis major tics (majfac*val)
\param minfac relative thickness for axis minor tics (minfac*val)
*/
void Axis::setLineWidth(double val, double majfac, double minfac)
{
    lineWidth_ = val;
    majLineWidth_ = majfac * lineWidth_;
    minLineWidth_ = minfac * lineWidth_;
}

void Axis::draw()
{
    Drawable::draw();

    saveGLState();

    //	GLStateBewarer sb(GL_LINE_SMOOTH, true);
    //	glBlendFunc(GL_ONE, GL_ZERO);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glColor4d(color.r, color.g, color.b, color.a);

    drawBase();
    drawTics();
    drawLabel();

    restoreGLState();
}

/**
Always use AFTER drawNumbers() ! (Needs length of number string)
*/
void Axis::drawLabel()
{
    if (!drawLabel_)
        return;

    Triple diff = end() - begin();
    Triple center = begin() + diff / 2;

    Triple bnumber = biggestNumberString();
    //	double fac = 6*(second()-first()).length() / 100;

    switch (scaleNumberAnchor_) {
    case BottomLeft:
    case TopLeft:
    case CenterLeft:
        bnumber.y = 0;
        break;
    case BottomRight:
    case TopRight:
    case CenterRight:
        bnumber.x = -bnumber.x;
        bnumber.y = 0;
        break;
    case TopCenter:
        bnumber.x = 0;
        bnumber.y = -bnumber.y;
        break;
    case BottomCenter:
        bnumber.x = 0;
        break;
    default:
        break;
    }

    Triple pos = ViewPort2World(World2ViewPort(center + ticOrientation() * lmaj_) + bnumber);
    setLabelPosition(pos, scaleNumberAnchor_);

    label_.adjust(labelgap_);
    label_.draw();
}

void Axis::drawBase()
{
    setDeviceLineWidth(lineWidth_);
    glBegin(GL_LINES);
    glVertex3d(beg_.x, beg_.y, beg_.z);
    glVertex3d(end_.x, end_.y, end_.z);
    glEnd();
}

bool Axis::prepTicCalculation(Triple &startpoint)
{
    if (isPracticallyZero(start_, stop_))
        return false;

    autostart_ = start_;
    autostop_ = stop_;

    if (autoScale()) {
        setMajors(scale_->autoscale(autostart_, autostop_, start_, stop_, majors()));
        if (isPracticallyZero(autostart_, autostop_))
            return false;
    }

    scale_->setLimits(start_, stop_);
    scale_->setMajors(majors());
    scale_->setMinors(minors());
    scale_->setMajorLimits(autostart_, autostop_);
    scale_->calculate();

    startpoint = end_ - beg_;

    majorpos_.clear();
    minorpos_.clear();

    return true;
}

void Axis::recalculateTics()
{
    Triple runningpoint;
    if (false == prepTicCalculation(runningpoint))
        return;

    unsigned int i;

    for (i = 0; i != scale_->majors_p.size(); ++i) {
        double t = (scale_->majors_p[i] - start_) / (stop_ - start_);
        majorpos_.push_back(beg_ + t * runningpoint);
    }
    for (i = 0; i != scale_->minors_p.size(); ++i) {
        double t = (scale_->minors_p[i] - start_) / (stop_ - start_);
        minorpos_.push_back(beg_ + t * runningpoint);
    }
}

void Axis::drawTics()
{
    Triple runningpoint;
    if (!drawTics_ || false == prepTicCalculation(runningpoint))
        return;

    unsigned int i;
    Triple nadir;

    markerLabel_.resize(scale_->majors_p.size());
    setDeviceLineWidth(majLineWidth_);
    for (i = 0; i != scale_->majors_p.size(); ++i) {
        double t = (scale_->majors_p[i] - start_) / (stop_ - start_);
        nadir = beg_ + t * runningpoint;
        majorpos_.push_back(drawTic(nadir, lmaj_));
        drawTicLabel(nadir + 1.2 * lmaj_ * orientation_, i);
    }
    setDeviceLineWidth(minLineWidth_);
    for (i = 0; i != scale_->minors_p.size(); ++i) {
        double t = (scale_->minors_p[i] - start_) / (stop_ - start_);
        nadir = beg_ + t * runningpoint;
        minorpos_.push_back(drawTic(nadir, lmin_));
    }
}

void Axis::drawTicLabel(Triple pos, int mtic)
{
    if (!drawNumbers_ || (mtic < 0))
        return;

    markerLabel_[mtic].setFont(numberfont_.family(), numberfont_.pointSize(), numberfont_.weight(),
                               numberfont_.italic());
    markerLabel_[mtic].setColor(numbercolor_);
    markerLabel_[mtic].setString(scale_->ticLabel(mtic));
    markerLabel_[mtic].setPosition(pos, scaleNumberAnchor_);
    markerLabel_[mtic].adjust(numbergap_);
    markerLabel_[mtic].draw();
}

Triple Axis::drawTic(Triple nadir, double length)
{
    double ilength = (symtics_) ? -length : 0.0;

    glBegin(GL_LINES);
    glVertex3d(nadir.x + ilength * orientation_.x, nadir.y + ilength * orientation_.y,
               nadir.z + ilength * orientation_.z);
    glVertex3d(nadir.x + length * orientation_.x, nadir.y + length * orientation_.y,
               nadir.z + length * orientation_.z);
    glEnd();
    return nadir;
}

void Axis::setNumberFont(QString const &family, int pointSize, int weight, bool italic)
{
    numberfont_ = QFont(family, pointSize, weight, italic);
}

void Axis::setNumberFont(QFont const &font)
{
    numberfont_ = font;
}

void Axis::setNumberColor(RGBA col)
{
    numbercolor_ = col;
}

void Axis::setLabelFont(QString const &family, int pointSize, int weight, bool italic)
{
    labelfont_ = QFont(family, pointSize, weight, italic);
    label_.setFont(family, pointSize, weight, italic);
}

void Axis::setLabelFont(QFont const &font)
{
    setLabelFont(font.family(), font.pointSize(), font.weight(), font.italic());
}

void Axis::setLabelString(QString const &name)
{
    label_.setString(name);
}

/*!
  Sets label position in conjunction with an anchoring strategy
*/
void Axis::setLabelPosition(const Triple &pos, Qwt3D::ANCHOR an)
{
    label_.setPosition(pos, an);
}

//! Sets color for label
void Axis::setLabelColor(RGBA col)
{
    label_.setColor(col);
}

Triple Axis::biggestNumberString()
{
    Triple ret;
    size_t size = markerLabel_.size();

    double width, height;

    for (unsigned i = 0; i != size; ++i) {
        width = fabs(
                (World2ViewPort(markerLabel_[i].second()) - World2ViewPort(markerLabel_[i].first()))
                        .x);
        height = fabs(
                (World2ViewPort(markerLabel_[i].second()) - World2ViewPort(markerLabel_[i].first()))
                        .y);

        if (width > ret.x)
            ret.x = width + markerLabel_[i].gap();
        if (height > ret.y)
            ret.y = height + markerLabel_[i].gap();
        ;
    }
    return ret;
}

/*!
  This variant sets a user-defined scale object.
  Use with a heap based initialized pointer only.
  The axis adopts ownership.
*/
void Axis::setScale(Scale *val)
{
    scale_ = qwt3d_ptr<Scale>(val);
}

/*!
  Sets one of the predefined scaling types.
  \warning Too small intervals in logarithmic scales lead to
  empty scales (or perhaps a scale only containing an isolated
  major tic). Better switch to linear scales in such cases.
*/
void Axis::setScale(Qwt3D::SCALETYPE val)
{
    switch (val) {
    case Qwt3D::LINEARSCALE:
        setScale(new LinearScale);
        break;
    case Qwt3D::LOG10SCALE:
        setScale(new LogScale);
        setMinors(9);
        break;
    default:
        break;
    }
}
