//--------------------------------------------------------------
bool ofxThreadedVideo::loadMovie(string fileName){

    // check if we're using a queue or only allowing one file to load at a time
    if (!bUseQueue && pathsToLoad.size() > 0){
        ofLogWarning() << "Ignoring loadMovie(" << fileName << ") as we're not using a queue and a movie is already loading. Returning false. You can change this behaviour with setUseQueue(true)";

        // send event notification
        ofxThreadedVideoEvent videoEvent = ofxThreadedVideoEvent(loadPath, VIDEO_EVENT_LOAD_BLOCKED, this);
        ofNotifyEvent(threadedVideoEvent, videoEvent, this);

        return false;
    }

    // put the movie path in a queue
    pathsToLoad.push(ofToDataPath(fileName));
    return true;

}
//--------------------------------------------------------------
void ofxThreadedVideo::threadedFunction(){

    while (isThreadRunning()){

        lock();
        ofxThreadedVideoGlobalMutex.lock();
        if(!ofxThreadedVideoGlobalCritical && !bCriticalSection){
            ofxThreadedVideoGlobalCritical = true;
            bCriticalSection = true;
            int videoID = currentVideoID;
            ofxThreadedVideoCommand c = getCommand();
            bool bCanLoad = !bLoaded;
            bool bPopCommand = false;
            unlock();
            ofxThreadedVideoGlobalMutex.unlock();
            
            if(c.getInstance() == instanceID){
                
                if(c.getCommand() == "play"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    video[videoID].play();
                    
                    lock();
                    bIsPlaying = true;
                    bIsPaused = false;
                    unlock();
                    
                    bPopCommand = true;
                }
                
                if(c.getCommand() == "setPosition"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    lock();
                    position = c.getArgument<float>(0);
                    unlock();
                    video[videoID].setPosition(position);
                    bPopCommand = true;
                }

                if(c.getCommand() == "setVolume"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    lock();
                    volume = c.getArgument<float>(0);
                    unlock();
                    video[videoID].setVolume(volume);
                    bPopCommand = true;
                }
                
#ifdef USE_QUICKTIME_7
                if(c.getCommand() == "setPan"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    lock();
                    pan = c.getArgument<float>(0);
                    unlock();
                    video[videoID].setPan(pan);
                    bPopCommand = true;
                }
#endif
                
                if(c.getCommand() == "setLoopState"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    lock();
                    loopState = (ofLoopType)c.getArgument<int>(0);
                    unlock();
                    video[videoID].setLoopState(loopState);
                    bPopCommand = true;
                }
                
//                if(c.getCommand() == "setSpeed"){
//                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
//                    lock();
//                    speed = c.getArgument<float>(0);
//                    unlock();
//                    video[videoID].setSpeed(speed);
//                    bPopCommand = true;
//                }
                
//                if(c.getCommand() == "setFrame"){
//                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
//                    int frameTarget = c.getArgument<int>(0);
//                    CLAMP(frameTarget, 0, frameTotal);
//                    video[videoID].setFrame(frameTarget);
//                    bForceFrameNew = true;
//                    bPopCommand = true;
//                }
                
//                if(c.getCommand() == "setFrame"){
//                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
//                    lock();
//                    int frameTarget = c.getArgument<int>(0);
//                    bForceFrameNew = true;
//                    frameTarget = CLAMP(frameTarget, 0, frameTotal);
//                    cout << "setframe A: " << frameTarget << " " << videoID << " " << bCriticalSection << endl;
//                    video[videoID].setFrame(frameTarget);
//                    cout << "setframe B: " << frameTarget << " " << videoID << " " << bCriticalSection << endl;
//                    unlock();
//                    bPopCommand = true;
//                }
                
                if(c.getCommand() == "setPaused"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    lock();
                    bIsPaused = c.getArgument<bool>(0);
                    unlock();
                    video[videoID].setPaused(bIsPaused);
                    bPopCommand = true;
                }
                
                if(c.getCommand() == "setAnchorPercent"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    video[videoID].setAnchorPercent(c.getArgument<float>(0), c.getArgument<float>(0));
                    bPopCommand = true;
                }

                if(c.getCommand() == "setAnchorPoint"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    video[videoID].setAnchorPercent(c.getArgument<float>(0), c.getArgument<float>(0));
                    bPopCommand = true;
                }
                
                if(c.getCommand() == "resetAnchor"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    video[videoID].resetAnchor();
                    bPopCommand = true;
                }
                
                if(c.getCommand() == "setFade"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    
                    int frameEnd;
                    int frameStart = c.getArgument<int>(0);
                    int durationMillis = c.getArgument<int>(1);
                    float fadeTarget = c.getArgument<float>(2);
                    bool fadeSound = c.getArgument<bool>(3);
                    bool fadeVideo = c.getArgument<bool>(4);
                    bool fadeOnce = c.getArgument<bool>(5);
                    
                    CLAMP(fadeTarget, 0.0f, 1.0f);
                    
                    if(frameStart == -1){ // fade is durationMillis from the end
                        frameEnd = frameTotal;
                        frameStart = frameTotal - ((float)durationMillis / 1000.0) * 25.0;
                    }else{
                        frameEnd = frameStart + ((float)durationMillis / 1000.0) * 25.0;
                    }
                    
                    if(frameStart == frameEnd){
                        _fade = fadeTarget;
                        if(fadeVideo) fade = _fade;
                        lock();
                        if(fadeSound) video[videoID].setVolume(_fade);
                        unlock();
                    }else{
                        frameEnd -= 1;
                        
                        // assert(frameStart >= 0);
                        // assert(frameEnd >= frameStart);
                        // assert(frameEnd <= frameTotal);
                        
                        fades.push_back(ofxThreadedVideoFade(frameStart, frameEnd, fadeTarget, fadeSound, fadeVideo, fadeOnce));
                    }
                    

                    
                    bPopCommand = true;
                }
                
#ifdef USE_JACK_AUDIO
                if(c.getCommand() == "setAudioTrackToChannel"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    video[videoID].setAudioTrackToChannel(c.getArgument<int>(0), c.getArgument<int>(1), c.getArgument<int>(2));
                    bPopCommand = true;
                }
                
                if(c.getCommand() == "setAudioDevice"){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    video[videoID].setAudioDevice(c.getArgument<string>(0));
                    bPopCommand = true;
                }
#endif
                
                if(c.getCommand() == "loadMovie" && bCanLoad){
                    if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString();
                    
                    if(video[videoID].loadMovie(c.getArgument<string>(0))){
                        
                        if(bVerbose) ofLogVerbose() << instanceID << " = " << c.getCommandAsString() << " executed in thread";;

//                        lock();
                        
                        fades.clear();
                        width = video[videoID].getWidth();
                        height = video[videoID].getHeight();
                        speed = video[videoID].getSpeed();
                        duration = video[videoID].getDuration();
                        position = video[videoID].getPosition();
                        frameCurrent = video[videoID].getCurrentFrame();
                        frameTotal = video[videoID].getTotalNumFrames();
#ifdef USE_QUICKTIME_7
                        volume = video[videoID].getVolume(); // we should implement for QT6
                        pan = video[videoID].getPan();
#endif
                        loopState = video[videoID].getLoopState();
                        
                        moviePath = c.getArgument<string>(0);
#ifdef TARGET_OSX
                        vector<string> pathParts = ofSplitString(moviePath, "/");
#else
                        vector<string> pathParts = ofSplitString(moviePath, "\\");
#endif
                        movieName = pathParts[pathParts.size() - 1];
                        
                        bIsPaused = true;
                        bIsPlaying = false;
                        bIsTextureReady = false;
                        bIsLoading = false;
                        bIsMovieDone = false;
                        bLoaded = true;
                        
                        pixels = &video[videoID].getPixelsRef();

                        unlock();
                        
                        bPopCommand = true;
                        
                        ofxThreadedVideoEvent e = ofxThreadedVideoEvent(moviePath, VIDEO_EVENT_LOAD_OK, this);
                        ofNotifyEvent(threadedVideoEvent, e, this);
                        
                        ofxThreadedVideoLoadOk++;
                        
                    }else{
                        
                        ofLogError() << "Could not load: " << instanceID << " + " << c.getCommandAsString();
                        
                        ofxThreadedVideoEvent e = ofxThreadedVideoEvent(moviePath, VIDEO_EVENT_LOAD_FAIL, this);
                        ofNotifyEvent(threadedVideoEvent, e, this);
                        
                        ofxThreadedVideoLoadFail++;
                    }
                    
                }
                
            }
            
            if(bPopCommand){
                video[videoID].update();
            }
        
            lock();
            
            if(bIsFrameNew){
                for(unsigned int i = 0; i < fades.size(); i++){
                    
                    ofxThreadedVideoFade& currentFade = fades.at(i);
                    
                    if(currentFade.getIsFading(frameCurrent)){
                        _fade = currentFade.getFade(_fade, frameCurrent);
                        
                        if(currentFade.fadeVideo){
                            if(fade != _fade) fade = _fade;
                        }
#ifdef USE_QUICKTIME_7
                        if(currentFade.fadeSound){ // we should implement for QT6
                            if(video[videoID].getVolume() != _fade) video[videoID].setVolume(_fade);
                        }
#endif
                    }
                    
                    if(currentFade.fadeOnce && currentFade.getFadeDone(frameCurrent)){
                        fades.erase(fades.begin() + i);
                        i--;
                    }

                }
            }
            
            ofxThreadedVideoGlobalMutex.lock();
            
            if(bPopCommand) popCommand();
            
            ofxThreadedVideoGlobalCritical = false;
            bCriticalSection = false;
            ofxThreadedVideoGlobalMutex.unlock();
            unlock();
        }else{
            ofxThreadedVideoGlobalMutex.unlock();
            unlock();
        }
        
        ofSleepMillis(1);

    }
        
}
//--------------------------------------------------------------
void ofxThreadedVideo::threadedFunction(){

    while (isThreadRunning()){

        if(lock()){

            // if there's a movie to load...
            if(loadPath != ""){

                loadVideoID = getNextLoadID();

                paths[loadVideoID] = loadPath;
                loadPath = "";
#ifdef TARGET_OSX
                vector<string> pathParts = ofSplitString(paths[loadVideoID], "/");
#else
                vector<string> pathParts = ofSplitString(paths[loadVideoID], "\\");
#endif
                names[loadVideoID] = pathParts[pathParts.size() - 1];

                // using a static mutex blocks all threads (including the main app) until we've loaded
                ofxThreadedVideoMutex.lock();

                // load that movie!
                if(videos[loadVideoID].loadMovie(paths[loadVideoID])){

                    ofLogVerbose() << "Loaded" << names[loadVideoID] << " " << loadVideoID;

                    // start rolling if AutoPlay is true
                    if (bUseAutoPlay) videos[loadVideoID].play();

                    // set pixel refs
                    pixels[loadVideoID] = &videos[loadVideoID].getPixelsRef();

                }else{

                    ofLogVerbose() << "Could not load video";
                    loadVideoID = VIDEO_NONE;

                    // send event notification
                    ofxThreadedVideoEvent videoEvent = ofxThreadedVideoEvent(paths[loadVideoID], VIDEO_EVENT_LOAD_FAIL, this);
                    ofNotifyEvent(threadedVideoEvent, videoEvent, this);
                }


                ofxThreadedVideoMutex.unlock();

            }

            // do threaded update of videos
            updateVideo(currentVideoID);
            updateVideo(loadVideoID);

            unlock();

            // sleep a bit so we don't thrash the cores!!
            ofSleepMillis(1000/30); // TODO: implement target frame rate? US might need 30 here?

        }
    }
}
//--------------------------------------------------------------
void ofxThreadedVideo::update(){

    if(lock()){
        
        // check if we're loading a video
        if(loadVideoID != VIDEO_NONE){
            
            videos[loadVideoID].update();
            
            float w = videos[loadVideoID].getWidth();
            float h = videos[loadVideoID].getHeight();

            // allocate a texture if the one we have is a different size
            if(bUseTexture && (textures[loadVideoID].getWidth() != w || textures[loadVideoID].getHeight() != h)){
                ofLogVerbose() << "Allocating texture" << loadVideoID << w << h;
                textures[loadVideoID].allocate(w, h, ofGetGLTypeFromPixelFormat(internalPixelFormat));
            }

            // check for a new frame before loading video
            if(bFrameNew[loadVideoID]){

                // switch the current movie ID to the one we just loaded
                int lastVideoID = currentVideoID;
                currentVideoID = loadVideoID;
                loadVideoID = VIDEO_NONE;

                // close the last movie - we do this here because
                // ofQuicktimeVideo chokes if you try to close in a thread
                if(lastVideoID != VIDEO_NONE){
                    ofLogVerbose() << "Closing last video" << lastVideoID;
                    paths[lastVideoID] = names[lastVideoID] = "";
                    videos[lastVideoID].stop();

                    // reset properties to defaults
                    newPosition[lastVideoID] = -1.0f;
                    newFrame[lastVideoID] = -1;
                    newSpeed[lastVideoID] = 1.0f;
                    newLoopType[lastVideoID] = -1;
                    frame[lastVideoID] = 0;

                    bFrameNew[lastVideoID] = false;
                    bPaused[lastVideoID] = false;
                    volume[lastVideoID] = 255;
                }

                // send event notification
                ofxThreadedVideoEvent videoEvent = ofxThreadedVideoEvent(paths[currentVideoID], VIDEO_EVENT_LOAD_OK, this);
                ofNotifyEvent(threadedVideoEvent, videoEvent, this);
            }
        }

        // check for a new frame for current video
        updateTexture(currentVideoID);

        // if there's a movie in the queue
        if(pathsToLoad.size() > 0 && loadPath == "" && loadVideoID == VIDEO_NONE){
            // ...let's start trying to load it!
            loadPath = pathsToLoad.front();
            pathsToLoad.pop();
        };

        // calculate frameRate -> taken from ofAppRunner
        prevMillis = ofGetElapsedTimeMillis();
        timeNow = ofGetElapsedTimef();
        double diff = timeNow-timeThen;
        if( diff  > 0.00001 ){
            fps			= 1.0 / diff;
            frameRate	*= 0.9f;
            frameRate	+= 0.1f*fps;
        }
        lastFrameTime	= diff;
        timeThen		= timeNow;

        unlock();
    }
}