/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    
    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.  See the file
    COPYING included with this distribution for more information.
*/

#ifndef VIEW_PROXY_H
#define VIEW_PROXY_H

#include "layer/LayerGeometryProvider.h"

#include "data/model/AlignmentModel.h"

namespace sv {

class ViewProxy : public LayerGeometryProvider
{
public:
    struct Parameters {
        /**
         * The view for which this is a proxy. The ViewProxy is
         * created on-the-fly to capture the (horizontal) position and
         * zoom of the view and its display scale factor at the time
         * of construction, then used during rendering and discarded.
         */
        View *view;

        /**
         * The centre frame of the view at the time of
         * construction. This is the centre frame that should be used
         * throughout a single render cycle.
         */
        sv_frame_t centreFrame;

        /**
         * The zoom level of the view at the time of
         * construction. This is the zoom level that should be used
         * throughout a single render cycle.
         */
        ZoomLevel zoomLevel;

        /**
         * The display scale factor for the view. This is generally
         * used with pixel-doubled "retina" Mac displays and is
         * usually 1 elsewhere.
         */
        int scaleFactor;

        /**
         * An optional alignment model. If this is supplied,
         * coordinates will be mapped through it, such that frame
         * values passed from the caller are mapped "from reference"
         * by that alignment before being used by the view or
         * converted to pixel coordinates, and returned values are
         * mapped back "to reference" before being passed back to the
         * caller.
         *
         * A proxy using an alignment model may be created specially
         * for rendering a single layer which comes from a different
         * alignment to that of the rest of the containing view.
         */
        ModelId alignmentModelId;
    };
    
    /**
     * Create a standard ViewProxy for the given view, using the given
     * parameters. See the parameter documentation above.
     */
    ViewProxy(Parameters params) :
        m_view(params.view),
        m_centreFrame(params.centreFrame),
        m_rawZoomLevel(params.zoomLevel),
        m_scaleFactor(params.scaleFactor),
        m_alignment(params.alignmentModelId) {
    }
    
    int getId() const override {
        return m_view->getId();
    }
    int getScaleFactor() const override {
        return m_scaleFactor;
    }
    sv_frame_t getStartFrame() const override {
        return getFrameForX(0);
    }
    sv_frame_t getCentreFrame() const override {
        return alignToReference(m_centreFrame);
    }
    sv_frame_t getEndFrame() const override {
        return getFrameForX(getPaintWidth()) - 1;
    }
    int getXForFrame(sv_frame_t frame) const override {
        //!!! not actually correct, if frame lies between view's pixels
        return m_scaleFactor *
            m_view->getXForFrameWith(alignFromReference(frame),
                                     m_centreFrame, m_rawZoomLevel);
    }
    sv_frame_t getFrameForX(int x) const override {
        sv_frame_t f0 = m_view->getFrameForXWith(x / m_scaleFactor,
                                                 m_centreFrame, m_rawZoomLevel);
        if (m_scaleFactor == 1) return alignToReference(f0);
        sv_frame_t f1 = m_view->getFrameForXWith((x / m_scaleFactor) + 1,
                                                 m_centreFrame, m_rawZoomLevel);
        sv_frame_t f = f0 + ((f1 - f0) * (x % m_scaleFactor)) / m_scaleFactor;
        return alignToReference(f);
    }
    int getXForViewX(int viewx) const override {
        return viewx * m_scaleFactor;
    }
    int getViewXForX(int x) const override {
        return x / m_scaleFactor;
    }
    sv_frame_t getModelsStartFrame() const override {
        return alignToReference(m_view->getModelsStartFrame());
    }
    sv_frame_t getModelsEndFrame() const override {
        return alignToReference(m_view->getModelsEndFrame());
    }
    double getYForFrequency(double frequency,
                            double minFreq, double maxFreq, 
                            FrequencyMapping mapping) const override {
        return m_scaleFactor *
            m_view->getYForFrequency(frequency, minFreq, maxFreq, mapping);
    }
    double getFrequencyForY(double y, double minFreq, double maxFreq,
                            FrequencyMapping mapping) const override {
        return m_view->getFrequencyForY
            (y / m_scaleFactor, minFreq, maxFreq, mapping);
    }
    int getTextLabelYCoord(const Layer *layer, QPainter &paint) const override {
        return m_scaleFactor * m_view->getTextLabelYCoord(layer, paint);
    }
    CoordinateScale getEffectiveVerticalExtentsForLayer(const Layer *layer) const override {
        return m_view->getEffectiveVerticalExtentsForLayer(layer);
    }
    CoordinateScale getEffectiveVerticalExtents(QString unit = {}) const override {
        return m_view->getEffectiveVerticalExtents(unit);
    }
    ZoomLevel getRoundedZoomLevel() const override {
        ZoomLevel z = m_rawZoomLevel;
        if (z.zone == ZoomLevel::FramesPerPixel) {
            z.level /= m_scaleFactor;
            if (z.level < 1) {
                z.level = 1;
            }
        } else {
            z.level *= m_scaleFactor;
        }
        return z;
    }
    ZoomLevel getRawZoomLevel() const override {
        return m_rawZoomLevel;
    }
    QRect getPaintRect() const override {
        QRect r = m_view->getPaintRect();
        return QRect(r.x() * m_scaleFactor,
                     r.y() * m_scaleFactor,
                     r.width() * m_scaleFactor,
                     r.height() * m_scaleFactor);
    }
    QSize getPaintSize() const override {
        QSize s = m_view->getPaintSize();
        return QSize(s.width() * m_scaleFactor,
                     s.height() * m_scaleFactor);
    }
    int getPaintWidth() const override {
        return m_view->getPaintWidth() * m_scaleFactor;
    }
    int getPaintHeight() const override { 
        return m_view->getPaintHeight() * m_scaleFactor;
    }
    bool hasLightBackground() const override {
        return m_view->hasLightBackground();
    }
    QColor getForeground() const override {
        return m_view->getForeground();
    }
    QColor getBackground() const override {
        return m_view->getBackground();
    }
    ViewManager *getViewManager() const override {
        return m_view->getViewManager();
    }
        
    bool shouldIlluminateLocalFeatures(const Layer *layer,
                                               QPoint &point) const override {
        QPoint p;
        bool should = m_view->shouldIlluminateLocalFeatures(layer, p);
        point = QPoint(p.x() * m_scaleFactor, p.y() * m_scaleFactor);
        return should;
    }

    bool shouldShowFeatureLabels() const override {
        return m_view->shouldShowFeatureLabels();
    }

    void drawMeasurementRect(QPainter &p, const Layer *layer,
                                     QRect rect, bool focus) const override {
        m_view->drawMeasurementRect(p, layer, rect, focus);
    }

    void updatePaintRect(QRect r) override {
        m_view->updatePaintRect(QRect(r.x() / m_scaleFactor,
                                      r.y() / m_scaleFactor,
                                      r.width() / m_scaleFactor,
                                      r.height() / m_scaleFactor));
    }

    /**
     * Scale up a size in pixels for a hi-dpi display without pixel
     * doubling. This is like ViewManager::scalePixelSize, but taking
     * and returning floating-point values rather than integer
     * pixels. It is also a little more conservative - it never
     * shrinks the size, it can only increase or leave it unchanged.
     */
    double scaleSize(double size) const override {
        return m_view->scaleSize(size * m_scaleFactor);
    }

    /**
     * Integer version of scaleSize.
     */
    int scalePixelSize(int size) const override {
        return m_view->scalePixelSize(size * m_scaleFactor);
    }
    
    /**
     * Scale up pen width for a hi-dpi display without pixel doubling.
     * This is like scaleSize except that it also scales the
     * zero-width case.
     */
    double scalePenWidth(double width) const override {
        if (width <= 0) { // zero-width pen, produce a scaled one-pixel pen
            width = 1;
        }
        width *= sqrt(double(m_scaleFactor));
        return m_view->scalePenWidth(width);
    }

    /**
     * Apply scalePenWidth to a pen.
     */
    QPen scalePen(QPen pen) const override {
        return QPen(pen.color(), scalePenWidth(pen.width()));
    }
    
    View *getView() override { return m_view; }
    const View *getView() const override { return m_view; }

private:
    View *m_view;
    sv_frame_t m_centreFrame;
    ZoomLevel m_rawZoomLevel;
    int m_scaleFactor;
    ModelId m_alignment;

    sv_frame_t alignToReference(sv_frame_t frame) const {
        if (auto am = ModelById::getAs<AlignmentModel>(m_alignment)) {
            return am->toReference(frame);
        } else {
            return frame;
        }
    }

    sv_frame_t alignFromReference(sv_frame_t frame) const {
        if (auto am = ModelById::getAs<AlignmentModel>(m_alignment)) {
            return am->fromReference(frame);
        } else {
            return frame;
        }
    }
};

} // end namespace sv

#endif
