/*
 ==============================================================================

 This file is part of the JUCETICE project - Copyright 2009 by Lucio Asnaghi.

 JUCETICE is based around the JUCE library - "Jules' Utility Class Extensions"
 Copyright 2007 by Julian Storer.

 ------------------------------------------------------------------------------

 JUCE and JUCETICE can be redistributed and/or modified 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.

 JUCE and JUCETICE are distributed in the hope that they will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with JUCE and JUCETICE; if not, visit www.gnu.org/licenses or write to
 Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 Boston, MA 02111-1307 USA

 ==============================================================================

   @author  Mike W. Smith
   @tweaker Lucio Asnaghi

 ==============================================================================
*/

BEGIN_JUCE_NAMESPACE

//==============================================================================
MeterComponent::MeterComponent(
                               int type,
                               int segs,
                               int markerWidth,
                               const Colour& min,
                               const Colour& thresh,
                               const Colour& max,
                               const Colour& back,
                               float threshold) :
    Component("Meter Component"),
    m_img(0),
    m_value(0),
    m_skew(1.0f),
    m_threshold(threshold),
    m_meterType(type),
    m_segments(segs),
    m_markerWidth(markerWidth),
    m_inset(0),
    m_raised(false),
    m_minColour(min),
    m_thresholdColour(thresh),
    m_maxColour(max),
    m_backgroundColour(back),
    m_decayTime(50),
    m_decayPercent(0.707f),
    m_decayToValue(0),
    m_hold(4),
    m_monostable(0),
    m_background(0),
    m_overlay(0),
    m_minPosition(0.1f),
    m_maxPosition(0.9f),
    m_needleLength(0),
    m_needleWidth(markerWidth)
{
    if(m_meterType == MeterAnalog)
        if(!m_segments)
            // Minimum analog meter wiper width
            m_segments = 8;
    startTimer(m_decayTime);
}

//==============================================================================
MeterComponent::MeterComponent(Image background,
                               Image overlay,
                               float minPosition,
                               float maxPosition,
                               Point<int>& needleCenter,
                               int needleLength,
                               int needleWidth,
                               int arrowLength,
                               int arrowWidth,
                               const Colour& needleColour,
                               int needleDropShadow,
                               int dropDistance) :
    Component("Meter Component"),
    m_img(0),
    m_value(0),
    m_skew(1.0f),
    m_threshold(0.707f),
    m_meterType(MeterAnalog),
    m_segments(0),
    m_markerWidth(0),
    m_inset(0),
    m_raised(false),
    m_minColour(0),
    m_thresholdColour(0),
    m_maxColour(0),
    m_backgroundColour(0),
    m_decayTime(50),
    m_decayPercent(0.707f),
    m_decayToValue(0),
    m_hold(4),
    m_monostable(0),
    m_background(0),
    m_overlay(0),
    m_minPosition(minPosition),
    m_maxPosition(maxPosition),
    m_needleLength(needleLength),
    m_needleWidth(needleWidth),
    m_arrowLength(arrowLength),
    m_arrowWidth(arrowWidth),
    m_needleDropShadow(needleDropShadow),
    m_dropDistance(dropDistance)
{
    m_background = background;
    m_overlay = overlay;
    m_needleCenter.setXY(needleCenter.getX(), needleCenter.getY());
    m_needleColour = needleColour;

    startTimer(m_decayTime);
}

//==============================================================================
MeterComponent::~MeterComponent()
{
    stopTimer();
}

//==============================================================================
void MeterComponent::buildImage(void)
{
    // Build our image in memory
    int w = getWidth() - m_inset*2;
    int h = getHeight() - m_inset*2;

    if(m_meterType == MeterHorizontal)
    {
        m_img = Image(Image(Image::RGB, w, h, false));
        Graphics g(m_img);

        g.setGradientFill (ColourGradient(m_minColour, 0, 0, m_thresholdColour, w * m_threshold, 0, false));
        g.fillRect(0, 0, int(w * m_threshold), h);

        g.setGradientFill (ColourGradient(m_thresholdColour, int(w * m_threshold), 0, m_maxColour, w, 0, false));
        g.fillRect(int(w * m_threshold), 0, w, h);

        if(m_segments)
        {
            // Break up our image into segments
            g.setColour (m_backgroundColour);
            g.fillRect (0, 0, w, m_markerWidth);
            g.fillRect (0, h-m_markerWidth, w, m_markerWidth);
            for(int i = 0; i <= m_segments; i++)
                g.fillRect(i * (w/m_segments), 0, m_markerWidth, h);
        }
    }
    else if(m_meterType == MeterVertical)
    {
        m_img = Image(Image::RGB, w, h, false);
        Graphics g(m_img);

        int hSize = (int)(h * m_threshold);
        g.setGradientFill (ColourGradient(m_minColour, 0, h, m_thresholdColour, 0, h - hSize, false));
        g.fillRect(0, h - hSize, w, hSize);

        g.setGradientFill (ColourGradient(m_thresholdColour, 0, h - hSize, m_maxColour, 0, 0, false));
        g.fillRect(0, 0, w, h - hSize);

        if(m_segments)
        {
            // Break up our image into segments
            g.setColour (m_backgroundColour);
            g.fillRect (0, 0, m_markerWidth, h);
            g.fillRect (w-m_markerWidth, 0, m_markerWidth, h);
            for(int i = 0; i <= m_segments; i++)
                g.fillRect(0, i * (h/m_segments), w, m_markerWidth);
        }
    }
    // Only build if no other analog images exist
    else if(m_meterType == MeterAnalog && !(m_background.isValid() || m_overlay.isValid() || m_needleLength))
    {
        m_img = Image(Image::RGB, w, h, false);
        Graphics g(m_img);

        g.setColour(m_backgroundColour);
        g.fillRect(0, 0, w, h);

        const double left = 4.71238898;     // 270 degrees in radians
        const double right = 1.57079633;    // 90 degrees in radians
        double startPos = m_minPosition;    // Start at 10%
        double endPos = m_maxPosition;      // End at 90%

        float strokeWidth = m_segments;     // We get our wiper width from the segments amount
        double pos;
        float radius = jmax (w/2, h/2) - strokeWidth/2;
        float angle;
        float x;
        float y;

        // Create an arc with lineTo's (works better than addArc)
        Path p;
        for(pos = startPos; pos < endPos; pos += .02)
        {
            angle = left + pos * (right - left);
            x = sin(angle)*radius + w/2;
            y = cos(angle)*radius + h - m_segments;
            if(pos == startPos)
                p.startNewSubPath(x, y);
            else
                p.lineTo(x, y);
        }
        angle = left + pos * (right - left);
        p.lineTo(sin(angle)*radius + w/2, cos(angle)*radius + h - m_segments);

        // Create an image brush of our gradient
        Image img(Image::RGB, w, h, false);
        Graphics g2(img);

        g2.setGradientFill (ColourGradient(m_minColour, 0, 0, m_thresholdColour, w * m_threshold, 0, false));
        g2.fillRect(0, 0, int(w * m_threshold), h);

        g2.setGradientFill (ColourGradient(m_thresholdColour, int(w * m_threshold), 0, m_maxColour, w, 0, false));
        g2.fillRect(int(w * m_threshold), 0, w, h);

        // Stroke the arc with the gradient
        g.setTiledImageFill (img, 0, 0, 1.0);
        g.strokePath(p, PathStrokeType(strokeWidth));
    }
}

//==============================================================================
void MeterComponent::setBounds (int x, int y, int width, int height)
{
    Component::setBounds(x, y, width, height);
    buildImage();
}

//==============================================================================
void MeterComponent::setFrame(int inset, bool raised)
{
    m_inset=inset;
    m_raised=raised;
    buildImage();
}

//==============================================================================
void MeterComponent::setColours(Colour& min, Colour& threshold, Colour& max, Colour& back)
{
    m_minColour = min;
    m_thresholdColour = threshold;
    m_maxColour = max;
    m_backgroundColour = back;
    buildImage();
}

//==============================================================================
void MeterComponent::setValue(float v)
{
    float val = jmin(jmax(v, 0.0f), 1.0f);
    if (m_skew != 1.0 && val > 0.0)
        val = exp (log (val) / m_skew);

    if(m_decayTime)
    {
        if(m_value < val)
        {
            // Sample and hold
            m_monostable=m_hold;
            m_value = val;
            repaint();
        }
        m_decayToValue = val;
    }
    else
    {
        if(m_value != val)
        {
            m_value = val;
            repaint();
        }
    }
}

//==============================================================================
void MeterComponent::setDecay(int decay, int hold, float percent)
{
    m_decayPercent = percent;
    m_decayTime = decay;
    m_hold = hold;
    if(m_decayTime)
        // Start our decay
        startTimer(m_decayTime);
    else
        stopTimer();
}

//==============================================================================
void MeterComponent::timerCallback()
{
    if(m_monostable)
        // Wait for our hold period
        m_monostable--;
    else
    {
        m_value = m_decayToValue + (m_decayPercent * (m_value - m_decayToValue));
        if(m_value - m_decayToValue > 0.01f)
            // Only repaint if there's enough changes
            repaint();
        else if(m_value != m_decayToValue)
        {
            // Zero out
            m_value = m_decayToValue;
            repaint();
        }
    }
}

//==============================================================================
//
//    This needs to be made more efficient by only repainting when things actually
//    change, and only painting the area that has changed. Right now, this paints
//    everything on every repaint request.
//
void MeterComponent::paint (Graphics& g)
{
    int h = getHeight() - m_inset*2;
    int w = getWidth() - m_inset*2;

    // Background
    if(!m_background.isValid() && !m_needleLength)
    {
        g.setColour(m_backgroundColour);
        g.fillRect(m_inset, m_inset, w, h);
    }

    // Bevel outline for the entire draw area
    if(m_inset)
        LookAndFeel_V2::drawBevel(
            g,
            0,
            0,
            getWidth(),
            getHeight(),
            m_inset,
            m_raised?Colours::white.withAlpha(0.4f):Colours::black.withAlpha(0.4f),
            m_raised?Colours::black.withAlpha(0.4f):Colours::white.withAlpha(0.4f));

    // Blit our prebuilt image
    g.setOpacity(1.0f);
    if(m_meterType == MeterHorizontal)
    {
        if(m_segments)
        {
            float val = float(int(m_value * m_segments)) / m_segments;
            g.drawImage(m_img, m_inset, m_inset, (int) (w * val), h, 0, 0, (int) (w * val), h);
        }
        else
            g.drawImage(m_img, m_inset, m_inset, (int) (w * m_value), h, 0, 0, (int) (w * m_value), h);
    }
    else if(m_meterType == MeterVertical)
    {
        int hSize;
        if(m_segments)
            hSize = (int) (h * float(int(m_value * m_segments)) / m_segments);
        else
            hSize = (int) (h * m_value);
        g.drawImage(m_img, m_inset, m_inset + h - hSize, w, hSize, 0, h - hSize, m_img.getWidth(), hSize);
    }
    else if(m_meterType == MeterAnalog)
    {
        if(m_background.isValid())
            g.drawImage(m_background, m_inset, m_inset, w, h, 0, 0, m_background.getWidth(), m_background.getHeight());
        else if(m_img.isValid())
            g.drawImage(m_img, m_inset, m_inset, w, h, 0, 0, m_img.getWidth(), m_img.getHeight());

        float angle = 4.71238898 + (m_value * (m_maxPosition - m_minPosition) + m_minPosition) * -3.14159265;
        if(m_needleLength)
        {
            g.setColour(m_needleColour);
			Line<float> l (m_needleCenter.getX() + m_inset,
						   m_needleCenter.getY() + m_inset,
						   sin(angle)*m_needleLength + m_needleCenter.getX() + m_inset,
						   cos(angle)*m_needleLength + m_needleCenter.getY() + m_inset);
            g.drawArrow (l, m_needleWidth, m_arrowLength, m_arrowWidth);

            if(m_needleDropShadow)
            {
                int dropX = 0, dropY = 0, dropPointX = 0, dropPointY = 0;
                
                if(m_needleDropShadow == NeedleLeft)
                {
                    dropX = -m_dropDistance;
                    dropY = 0;
                    dropPointX = -m_dropDistance;
                    dropPointY = 0;
                }
                else if(m_needleDropShadow == NeedleRight)
                {
                    dropX = m_dropDistance;
                    dropY = 0;
                    dropPointX = m_dropDistance;
                    dropPointY = 0;
                }
                else if(m_needleDropShadow == NeedleAbove)
                {
                    dropX = 0;
                    dropY = 0;
                    dropPointX = 0;
                    dropPointY = -m_dropDistance;
                }
                else if(m_needleDropShadow == NeedleBelow)
                {
                    dropX = 0;
                    dropY = m_dropDistance;
                    dropPointX = 0;
                    dropPointY = m_dropDistance;
                }
                g.setColour(Colours::black.withAlpha(0.2f));
				Line<float> l (m_needleCenter.getX() + m_inset + dropX,
							   m_needleCenter.getY() + m_inset + dropY,
							   sin(angle)*m_needleLength + m_needleCenter.getX() + m_inset + dropPointX,
							   cos(angle)*m_needleLength + m_needleCenter.getY() + m_inset + dropPointY);
                g.drawArrow(l, m_needleWidth, m_arrowLength, m_arrowWidth);
            }
        }
        else
        {
            // Contrasting arrow for built-in analog meter
            g.setColour(m_backgroundColour.contrasting(1.0f).withAlpha(0.9f));
			Line<float> l (w/2 + m_inset,
						   h - m_segments + m_inset,
						   sin(angle)*jmax (w/2, h/2) + w/2 + m_inset,
						   cos(angle)*jmax (w/2, h/2) + h - m_segments + m_inset);
            g.drawArrow(l, m_needleWidth, m_needleWidth*2, m_needleWidth*2);
        }

        if(m_overlay.isValid())
        {
            g.setOpacity(1.0f);
            g.drawImage(m_overlay, m_inset, m_inset, w, h, 0, 0, m_overlay.getWidth(), m_overlay.getHeight());
        }
    }
}

END_JUCE_NAMESPACE
