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

   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 "../../../../../juce_Config.h"

#if JUCE_QUICKTIME && ! defined (LINUX)

#ifdef _MSC_VER
  #pragma warning (disable: 4514)
#endif

#ifdef _WIN32
 #include <QTML.h>
 #include <Movies.h>
 #include <Windows.h>
#else
 #include <Carbon/Carbon.h>
 #include <QuickTime/Movies.h>
#endif

#include "../../../../juce_core/basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE

#include "juce_QuickTimeMovieComponent.h"
#include "../../../../juce_core/misc/juce_PlatformUtilities.h"
#include "../../../events/juce_MessageManager.h"
#include "../../graphics/geometry/juce_RectangleList.h"


//==============================================================================
static bool isQTAvailable = false;
static bool hasLoadedQT = false;
static VoidArray activeQTWindows (2);

#ifdef JUCE_WIN32

static uint8* makePascalString (const String& s)
{
    uint8* const p = (uint8*) juce_malloc (s.length() + 4);
    s.copyToBuffer ((char*) p + 1, 4096);
    p[0] = (uint8) s.length();
    return p;
}

void OfferMessageToQuickTime (HWND h, UINT message, WPARAM wParam, LPARAM lParam, Component* component)
{
    if (hasLoadedQT && activeQTWindows.size() > 0)
    {
        MSG msg = { 0 };
        msg.hwnd = h;
        msg.message = message;
        msg.wParam = wParam;
        msg.lParam = lParam;
        msg.time = GetMessageTime();

        LONG points = GetMessagePos();
        msg.pt.x = LOWORD (points);
        msg.pt.y = HIWORD (points);

        EventRecord macEvent;
        WinEventToMacEvent (&msg, &macEvent);

        for (int i = activeQTWindows.size(); --i >= 0;)
        {
            QuickTimeMovieComponent* const qtw = (QuickTimeMovieComponent*) activeQTWindows[i];

            if (qtw->isVisible()
                 && component->isParentOf (qtw)
                 && (qtw->isControllerVisible() || (message != WM_LBUTTONDOWN
                                                     && message != WM_MBUTTONDOWN
                                                     && message != WM_RBUTTONDOWN
                                                     && message != WM_LBUTTONUP
                                                     && message != WM_MBUTTONUP
                                                     && message != WM_RBUTTONUP)))
            {
                qtw->handleMCEvent (&macEvent);
            }
        }
    }
}

#else

struct MacClickEventData
{
    ::Point where;
    long when;
    long modifiers;
};

void OfferMouseClickToQuickTime (WindowRef window,
                                 ::Point where, long when, long modifiers,
                                 Component* topLevelComp)
{
    if (hasLoadedQT)
    {
        for (int i = activeQTWindows.size(); --i >= 0;)
        {
            QuickTimeMovieComponent* const qtw = (QuickTimeMovieComponent*) activeQTWindows[i];

            if (qtw->isVisible() && topLevelComp->isParentOf (qtw))
            {
                MacClickEventData data;
                data.where = where;
                data.when = when;
                data.modifiers = modifiers;

                qtw->handleMCEvent (&data);
            }
        }
    }
}

#endif

//==============================================================================
struct InternalData
{
    Movie movie;
    MovieController controller;
};

static const double dontGoToATime = -12345.0;

//==============================================================================
QuickTimeMovieComponent::QuickTimeMovieComponent()
    : internal (new InternalData()),
      associatedWindow (0),
      playing (false),
      looping (false),
      controllerVisible (false),
      controllerAssignedToWindow (false),
      reentrant (false),
      pendingTimeToGoTo (dontGoToATime)
{
    InternalData* const id = (InternalData*) internal;
    id->movie = 0;
    id->controller = 0;

    if (! hasLoadedQT)
    {
        hasLoadedQT = true;

#ifdef JUCE_WIN32
        isQTAvailable = (InitializeQTML (0) == noErr
                          && EnterMovies() == noErr);
#else
        isQTAvailable = EnterMovies() == noErr;
#endif
    }

    setOpaque (true);
    setVisible (true);

    activeQTWindows.add (this);
}

QuickTimeMovieComponent::~QuickTimeMovieComponent()
{
    closeMovie();

    activeQTWindows.removeValue ((void*) this);

    InternalData* const id = (InternalData*) internal;
    delete id;
}

void QuickTimeMovieComponent::shutdownQuicktime()
{
    if (activeQTWindows.size() == 0 && isQTAvailable)
    {
        isQTAvailable = false;
        hasLoadedQT = false;

        ExitMovies();
#ifdef JUCE_WIN32
        TerminateQTML();
#endif
    }
}

bool QuickTimeMovieComponent::loadMovie (const File& f,
                                         const bool controllerVisible_)
{
    if (! (isQTAvailable && f.existsAsFile()))
        return false;

    closeMovie();

    controllerVisible = controllerVisible_;

    InternalData* const id = (InternalData*) internal;

    GrafPtr savedPort;
    GetPort (&savedPort);
    bool result = false;

    FSSpec fsSpec;

#ifdef JUCE_WIN32
    StringPtr filename = makePascalString (f.getFullPathName());
    FSMakeFSSpec (0, 0L, filename, &fsSpec);
    juce_free (filename);
#else
    PlatformUtilities::makeFSSpecFromPath (&fsSpec, f.getFullPathName());
#endif

    short refNum = -1;
    OSErr err;

    if ((err = OpenMovieFile (&fsSpec, &refNum, fsRdWrPerm)) == noErr
         || (err = OpenMovieFile (&fsSpec, &refNum, fsRdPerm)) == noErr)
    {
        id->controller = 0;

        short resID = 0;

        if (NewMovieFromFile (&id->movie, refNum, &resID, 0, newMovieActive, 0) == noErr
             && id->movie != 0)
        {
            void* window = getWindowHandle();

            if (window != associatedWindow && window != 0)
            {
                associatedWindow = window;

#ifdef JUCE_WIN32
                if (window != 0)
                    CreatePortAssociation (window, 0, kQTMLNoIdleEvents);
#endif
            }

            assignMovieToWindow();

            SetMovieActive (id->movie, true);
            SetMovieProgressProc (id->movie, (MovieProgressUPP) -1, 0);

            movieFile = f;
            startTimer (1000 / 50); // this needs to be quite a high frequency for smooth playback
            result = true;
        }
    }

    MacSetPort (savedPort);

    return result;
}

void QuickTimeMovieComponent::closeMovie()
{
    stop();

    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0)
    {
        DisposeMovieController (id->controller);
        id->controller = 0;
    }

    if (id->movie != 0)
    {
        DisposeMovie (id->movie);
        id->movie = 0;
    }

    stopTimer();
    movieFile = File::nonexistent;
    pendingTimeToGoTo = dontGoToATime;
}

bool QuickTimeMovieComponent::isMovieOpen() const
{
    InternalData* const id = (InternalData*) internal;
    return id->movie != 0 && id->controller != 0;
}

const File QuickTimeMovieComponent::getCurrentMovieFile() const
{
    return movieFile;
}

static GrafPtr getPortForWindow (void* window)
{
    if (window == 0)
        return 0;

#ifdef JUCE_MAC
    return (GrafPtr) GetWindowPort ((WindowRef) window);
#else
    GrafPtr port = GetNativeWindowPort (window);

    if (port == 0)
    {
        CreatePortAssociation (window, 0, kQTMLNoIdleEvents);
        port = GetNativeWindowPort (window);
    }

    return port;
#endif
}

void QuickTimeMovieComponent::assignMovieToWindow()
{
    if (reentrant)
        return;

    reentrant = true;

    InternalData* const id = (InternalData*) internal;
    if (id->controller != 0)
    {
        DisposeMovieController (id->controller);
        id->controller = 0;
    }

    controllerAssignedToWindow = false;

    void* window = getWindowHandle();
    GrafPtr port = getPortForWindow (window);

    if (port != 0)
    {
        GrafPtr savedPort;
        GetPort (&savedPort);

        SetMovieGWorld (id->movie, (CGrafPtr) port, 0);
        MacSetPort (port);

        Rect r;
        r.top = 0;
        r.left = 0;
        r.right  = (short) jmax (1, getWidth());
        r.bottom = (short) jmax (1, getHeight());
        SetMovieBox (id->movie, &r);

        // create the movie controller
        id->controller = NewMovieController (id->movie, &r,
                                             controllerVisible ? mcTopLeftMovie
                                                               : mcNotVisible);

        if (id->controller != 0)
        {
            MCEnableEditing (id->controller, true);

            MCDoAction (id->controller, mcActionSetUseBadge, (void*) false);

            // set the initial looping state of the movie
            MCDoAction (id->controller, mcActionSetLooping, (void*) looping);
            MCDoAction (id->controller, mcActionSetLoopIsPalindrome, (void*) false);

            MCDoAction (id->controller, mcActionSetFlags,
                        (void*) (pointer_sized_int) (mcFlagSuppressMovieFrame | (controllerVisible ? 0 : (mcFlagSuppressStepButtons | mcFlagSuppressSpeakerButton))));

            resized();

            controllerAssignedToWindow = true;

            if (pendingTimeToGoTo != dontGoToATime)
                setPosition (pendingTimeToGoTo);
        }

        MacSetPort (savedPort);
    }

    reentrant = false;
}

void QuickTimeMovieComponent::play()
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
    {
        playing = true;
        StartMovie (id->movie);
    }
}

void QuickTimeMovieComponent::stop()
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
        StopMovie (id->movie);

    playing = false;
}

bool QuickTimeMovieComponent::isPlaying() const
{
    return playing && internal != 0;
}

void QuickTimeMovieComponent::goToStart()
{
    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0)
    {
        GoToBeginningOfMovie (id->movie);
        timerCallback(); // to call MCIdle
    }
}

void QuickTimeMovieComponent::setPosition (const double seconds)
{
    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0)
    {
        TimeRecord time;
        time.base = GetMovieTimeBase (id->movie);
        time.scale = 100000;
        const uint64 t = (uint64) (100000.0 * seconds);
        time.value.lo = (UInt32) (t & 0xffffffff);
        time.value.hi = (UInt32) (t >> 32);

        SetMovieTime (id->movie, &time);
        timerCallback(); // to call MCIdle

        pendingTimeToGoTo = dontGoToATime;
    }
    else
    {
        // if there's no controller yet, save this value for later
        pendingTimeToGoTo = seconds;
    }
}

double QuickTimeMovieComponent::getPosition() const
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
    {
        TimeRecord time;
        GetMovieTime (id->movie, &time);

        return ((int64) (((uint64) time.value.hi << 32) | (uint64) time.value.lo))
                    / (double) time.scale;
    }

    return 0.0;
}

double QuickTimeMovieComponent::getMovieDuration() const
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
        return GetMovieDuration (id->movie) / (double) GetMovieTimeScale (id->movie);

    return 0.0;
}

void QuickTimeMovieComponent::setLooping (const bool shouldLoop)
{
    InternalData* const id = (InternalData*) internal;

    looping = shouldLoop;

    if (id->controller != 0)
        MCDoAction (id->controller, mcActionSetLooping, (void*) looping);
}

void QuickTimeMovieComponent::setMovieVolume (const float newVolume)
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
        SetMovieVolume (id->movie, jlimit ((short) 0, (short) 0x100, (short) (newVolume * 0x0100)));
}

float QuickTimeMovieComponent::getMovieVolume() const
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
        return jmax (0.0f, GetMovieVolume (id->movie) / (float) 0x0100);

    return 0.0f;
}

void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const
{
    width = 0;
    height = 0;

    InternalData* const id = (InternalData*) internal;

    if (id->movie != 0)
    {
        Rect r;
        GetMovieNaturalBoundsRect (id->movie, &r);
        width = r.right - r.left;
        height = r.bottom - r.top;
    }
}

void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle& spaceToFitWithin,
                                                               const Justification& justification)
{
    int w, h;
    getMovieNormalSize (w, h);

    if (w > 0 && h > 0 && ! spaceToFitWithin.isEmpty())
    {
        int newW, newH;

        const double sourceRatio = h / (double) w;
        const double targetRatio = spaceToFitWithin.getHeight() / (double) spaceToFitWithin.getWidth();

        if (sourceRatio <= targetRatio)
        {
            newW = spaceToFitWithin.getWidth();
            newH = jmin (spaceToFitWithin.getHeight(), roundDoubleToInt (newW * sourceRatio));
        }
        else
        {
            newH = spaceToFitWithin.getHeight();
            newW = jmin (spaceToFitWithin.getWidth(), roundDoubleToInt (newH / sourceRatio));
        }

        if (newW > 0 && newH > 0)
        {
            int newX, newY;
            justification.applyToRectangle (newX, newY, newW, newH,
                                            spaceToFitWithin.getX(),
                                            spaceToFitWithin.getY(),
                                            spaceToFitWithin.getWidth(),
                                            spaceToFitWithin.getHeight());

            setBounds (newX, newY, newW, newH);
        }
    }
    else
    {
        setBounds (spaceToFitWithin);
    }
}

void QuickTimeMovieComponent::paint (Graphics& g)
{
    InternalData* const id = (InternalData*) internal;

    if (id->movie == 0 || id->controller == 0)
    {
        g.fillAll (Colours::black);
        return;
    }

#ifdef JUCE_MAC
    GrafPtr savedPort;
    GetPort (&savedPort);

    MacSetPort (GetWindowPort ((WindowRef) getWindowHandle()));
    MCDraw (id->controller, (WindowRef) getWindowHandle());

    MacSetPort (savedPort);
#endif

    ComponentPeer* const peer = getPeer();

    if (peer != 0)
    {
        peer->addMaskedRegion (getScreenX() - peer->getScreenX(),
                               getScreenY() - peer->getScreenY(),
                               getWidth(), getHeight());
    }

    timerCallback();
}

const Rectangle QuickTimeMovieComponent::getMoviePos() const
{
    return Rectangle (getScreenX() - getTopLevelComponent()->getScreenX(),
                      getScreenY() - getTopLevelComponent()->getScreenY(),
                      jmax (1, getWidth()),
                      jmax (1, getHeight()));
}

void QuickTimeMovieComponent::resized()
{
    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0 && isShowing())
    {
        checkWindowAssociation();

        GrafPtr port = getPortForWindow (getWindowHandle());

        if (port != 0)
        {
            GrafPtr savedPort;
            GetPort (&savedPort);

            SetMovieGWorld (id->movie, (CGrafPtr) port, 0);
            MacSetPort (port);

            lastPositionApplied = getMoviePos();

            Rect r;
            r.left   = (short) lastPositionApplied.getX();
            r.top    = (short) lastPositionApplied.getY();
            r.right  = (short) lastPositionApplied.getRight();
            r.bottom = (short) lastPositionApplied.getBottom();

            if (MCGetVisible (id->controller))
                MCSetControllerBoundsRect (id->controller, &r);
            else
                SetMovieBox (id->movie, &r);

            if (! isPlaying())
                timerCallback();

            MacSetPort (savedPort);

#ifdef JUCE_MAC
            InvalWindowRect ((WindowRef) getWindowHandle(), &r);
#endif
        }
    }
}

void QuickTimeMovieComponent::timerCallback()
{
    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0)
    {
        if (isTimerRunning())
            startTimer (getTimerInterval());

        MCIdle (id->controller);

        if (lastPositionApplied != getMoviePos())
            resized();

        if (playing && (! looping) && IsMovieDone (id->movie))
            stop();
    }
}

void QuickTimeMovieComponent::checkWindowAssociation()
{
    void* const window = getWindowHandle();

    if (window != associatedWindow
         || (window != 0 && ! controllerAssignedToWindow))
    {
        associatedWindow = window;
        assignMovieToWindow();
    }
}

void QuickTimeMovieComponent::parentHierarchyChanged()
{
    checkWindowAssociation();
}

#ifdef JUCE_WIN32

void QuickTimeMovieComponent::handleMCEvent (void* ev)
{
    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0 && isShowing())
        MCIsPlayerEvent (id->controller, (EventRecord*) ev);
}

#else

void QuickTimeMovieComponent::handleMCEvent (void* ev)
{
    InternalData* const id = (InternalData*) internal;

    if (id->controller != 0 && isShowing())
    {
        MacClickEventData* data = (MacClickEventData*) ev;

        data->where.h -= getTopLevelComponent()->getScreenX();
        data->where.v -= getTopLevelComponent()->getScreenY();

        Boolean b = false;
        MCPtInController (id->controller, data->where, &b);

        if (b)
        {
            const int oldTimeBeforeWaitCursor = MessageManager::getInstance()->getTimeBeforeShowingWaitCursor();
            MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0);

            MCClick (id->controller,
                     (WindowRef) getWindowHandle(),
                     data->where,
                     data->when,
                     data->modifiers);

            MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (oldTimeBeforeWaitCursor);
        }
    }
}

#endif

END_JUCE_NAMESPACE

#endif
