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

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

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

   JUCE 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 is distributed in the hope that it 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; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

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

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

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

#include "../jucedemo_headers.h"


//==============================================================================
class DemoThread  : public Thread,
                    public ChangeBroadcaster
{
public:
    int interval;
    float x, y, size, dx, dy, w, h;
    Colour colour;
    CriticalSection coordinateLock;

    DemoThread()
    {
        x = Random::getSystemRandom().nextFloat() * 200.0f;
        y = Random::getSystemRandom().nextFloat() * 200.0f;
        w = 32;
        h = 32;

        dx = Random::getSystemRandom().nextFloat() * 8.0f - 4.0f;
        dy = Random::getSystemRandom().nextFloat() * 8.0f - 4.0f;

        size = Random::getSystemRandom().nextFloat() * 30.0f + 10.0f;

        interval = Random::getSystemRandom().nextInt (20) + 6;

        colour = Colour (Random::getSystemRandom().nextInt())
                    .withAlpha (0.5f)
                    .withBrightness (0.7f);

        // give the threads a random priority, so some will move more
        // smoothly than others..
        startThread (Random::getSystemRandom().nextInt (3) + 3);
    }

    ~DemoThread()
    {
        // allow the thread 2 seconds to stop cleanly - should be plenty of time.
        stopThread (2000);
    }

    void run()
    {
        // this is the code that runs this thread - we'll loop continuously,
        // updating the co-ordinates of our blob.

        // threadShouldExit() returns true when the stopThread() method has been
        // called, so we should check it often, and exit as soon as it gets flagged.
        while (! threadShouldExit())
        {
            coordinateLock.enter();

            x += dx;
            y += dy;

            if (x < 0)
                dx = fabsf (dx);

            if (x > w)
                dx = -fabsf (dx);

            if (y < 0)
                dy = fabsf (dy);

            if (y > h)
                dy = -fabsf (dy);

            coordinateLock.exit();

            sendChangeMessage (this); // causes all ChangeListeners to be called back - in
                                      // this case, the owner window will be called, and it'll
                                      // repaint itself
            wait (interval);
        }
    }
};

//==============================================================================
class ThreadingDemo  : public Component,
                       public ChangeListener
{
    OwnedArray<DemoThread> threads;

public:
    //==============================================================================
    ThreadingDemo()
    {
        setName (T("Multithreading"));

        setOpaque (true);
    }

    ~ThreadingDemo()
    {
        threads.clear(); // this will delete all the thread objects and stop them
                         // before our component is deleted.
    }

    // this gets called when a component is added or removed from a parent component.
    void parentHierarchyChanged()
    {
        // we'll use this as an opportunity to start and stop the threads, so that
        // we don't leave them going when the component's not actually visible.

        threads.clear();

        if (getParentComponent() != 0)
        {
            // create all the threads..
            for (int i = 0; i < 5; ++i)
            {
                DemoThread* t = new DemoThread();

                // register with our thread as a listener - the thread will send a
                // change message when its blob moves.
                t->addChangeListener (this);

                // add it to our list.
                threads.add (t);
            }

            resized(); // to tell all the threads how big the component is
        }
    }

    void paint (Graphics& g)
    {
        g.fillAll (Colours::white);

        // paint a blob for each of the threads..
        for (int i = 0; i < threads.size(); ++i)
        {
            DemoThread* t = threads[i];

            // locks this thread object's critical section for the scope of this block
            const ScopedLock sl (t->coordinateLock);

            g.setColour (t->colour);

            g.fillEllipse (t->x, t->y, t->size, t->size);
        }
    }

    void resized()
    {
        // when we're resized, tell all the threads what the new size is..
        for (int i = 0; i < threads.size(); ++i)
        {
            DemoThread* t = threads[i];

            // locks this thread object's critical section for the scope of this block
            const ScopedLock sl (t->coordinateLock);

            t->w = getWidth() - 40.0f;
            t->h = getHeight() - 40.0f;
        }
    }

    void changeListenerCallback (void* objectThatHasChanged)
    {
        repaint();
    }
};



//==============================================================================
Component* createThreadingDemo()
{
    return new ThreadingDemo();
}
