/**************************************************************************

    tdemidclient.cpp  - The main client widget of KMid
    Copyright (C) 1997,98  Antonio Larrosa Jimenez

    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.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Send comments and bug fixes to larrosa@kde.org
    or to Antonio Larrosa, Rio Arnoya, 10 5B, 29006 Malaga, Spain

***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <signal.h>   // kill is declared on signal.h on bsd, not sys/signal.h
#include <sys/signal.h>

#include <tqkeycode.h>
#include <tqfiledialog.h>
#include <tqstring.h>
#include <tqlabel.h>
#include <tqfile.h>
#include <tqcombobox.h>
#include <tqlayout.h>

#include <tdeapplication.h>
#include <kcharsets.h>
#include <tdeconfig.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <tdeaction.h>
#include <kdebug.h>

#include <libtdemid/midimapper.h>
#include <libtdemid/fmout.h>
#include <libtdemid/track.h>
#include <libtdemid/midispec.h>
#include <libtdemid/deviceman.h>
#include <libtdemid/mt32togm.h>
#include "tdemidclient.h"
#include "klcdnumber.h"
#include "randomlist.h"
#include "channelview.h"
#include "channel.h"
#include "version.h"
#include "rhythmview.h"

//#define TEMPHACK


tdemidClient::tdemidClient(TQWidget *parent, TDEActionCollection *ac, const char *name)
    :  DCOPObject("KMidIface"), TQWidget(parent,name)
{
    actionCollection=ac;
    TDEConfig *cfg=kapp->config();
    cfg->setGroup("KMid");
    midifile_opened=0L;
    loopsong=cfg->readNumEntry("Loop",0);
    collectionplaymode=0;
    collectionplaylist=0L;
    channelView=0L;
    noteArray=0L;
    shuttingdown=false;

    TDEConfig *tdeconf=TDEGlobal::instance()->config();

    tdeconf->setGroup("KMid");
    TQString tmp2 = locateLocal("appdata", "collections");
    collectionsfile=tdeconf->readPathEntry("CollectionsFile",tmp2);
    slman=new SLManager();
    slman->loadConfig(TQFile::encodeName(collectionsfile));
    currentsl=NULL;
    //	currentsl=slman->getCollection(activecollection);
    itsme=0;
    m_kMid.pid=0;
    timebar = new TQSlider(0,240000,30000,60000,TQt::Horizontal, this);
    timebar->setSteps(30000,60000);
    timebar->setValue(0);
    connect (timebar,TQ_SIGNAL(valueChanged(int)),this,TQ_SLOT(slotSeek(int)));

    timetags = new QSliderTime(timebar,this);
    timetags->setMinimumSize(timetags->sizeHint());

    qlabelTempo= new TQLabel(i18n("Tempo:"), this,"tempolabel",
                            TQLabel::NoFrame);

    tempoLCD = new KLCDNumber( true, 3, this, "TempoLCD");
    tempoLCD->setValue(120);
    tempoLCD->display(120);
    currentTempo=120;
    tempoLCD->setRange(3,999);
    tempoLCD->setDefaultValue(120);
    tempoLCD->setUserSetDefaultValue(true);
    tempoLCD->setMinimumSize(tempoLCD->sizeHint());
    connect(tempoLCD,TQ_SIGNAL(valueChanged(double)),this,TQ_SLOT(slotSetTempo(double)));

    comboSongs = new TQComboBox(false, this,"Songs");
    connect (comboSongs,TQ_SIGNAL(activated(int)),this,TQ_SLOT(slotSelectSong(int)));
    comboSongs->setMinimumWidth(200);
    
    comboEncodings = new TQComboBox(false, this, "Encodings");
    connect (comboEncodings,TQ_SIGNAL(activated(int)),this,TQ_SLOT(slotSelectEncoding(int)));
    comboEncodings->insertItem(i18n("Default"));
    comboEncodings->insertStringList( TDEGlobal::charsets()->descriptiveEncodingNames() );
    comboEncodings->setCurrentItem(0);

    rhythmview= new RhythmView( this, "RhythmView");
    rhythmview->setMaximumHeight(7);
    rhythmview->setMinimumHeight(7);

    volumebar = new TQSlider(0,200,10,100,TQt::Vertical, this );
    volumebar->setSteps(10,20);
    volumebar->setValue(100);
    volumebar->setTickmarks(TQSlider::NoMarks);
    volumebar->setTickInterval(50);
    connect (volumebar,TQ_SIGNAL(valueChanged(int)),this,TQ_SLOT(slotSetVolume(int)));

    visiblevolumebar=cfg->readNumEntry("ShowVolumeBar",0);
    if (visiblevolumebar) volumebar->show();
    else volumebar->hide();

    typeoftextevents=1;
    kdispt=new KDisplayText( this, "KaraokeWindow");
    kdispt->show();

    timer4timebar=new TQTimer(this);
    connect (timer4timebar,TQ_SIGNAL(timeout()),this,TQ_SLOT(timebarUpdate()));
    timer4events=new TQTimer(this);
    connect (timer4events,TQ_SIGNAL(timeout()),this,TQ_SLOT(processSpecialEvent()));

    TQString samplefile =
      TDEGlobal::dirs()->findAllResources("appdata", "fm/*.o3").last();
    samplefile.truncate(samplefile.findRev('/'));
    FMOut::setFMPatchesDirectory(TQFile::encodeName(samplefile));

    m_kMid.pctlsmID=shmget(IPC_PRIVATE,sizeof(PlayerController),0666 | IPC_CREAT );
    if (m_kMid.pctlsmID==-1)
    {
        printf("ERROR: Cannot allocate shared memory !!!\n"
               "Please report to larrosa@kde.org\n");
	exit(1);
    }

    m_kMid.pctl=(PlayerController *)shmat(m_kMid.pctlsmID,0L,0);
    if (!m_kMid.pctl)
        printf("ERROR: Cannot get shared memory !!! "
               "Please report to larrosa@kde.org\n");
    m_kMid.pctl->playing=0;
    m_kMid.pctl->gm=1;
    m_kMid.pctl->volumepercentage=100;
    m_kMid.pctl->tempo=500000;
    m_kMid.pctl->ratioTempo=1.0;
    for (int i=0;i<16;i++)
    {
        m_kMid.pctl->forcepgm[i]=0;
        m_kMid.pctl->pgm[i]=0;
    }


    tdeconf->setGroup("KMid");
    int mididev=tdeconf->readNumEntry("MidiPortNumber",-1);

    midi = new DeviceManager(mididev);
    midi->initManager();
    m_kMid.midi=midi;
    player= new MidiPlayer(midi,m_kMid.pctl);

    tdeconf->setGroup("Midimapper");
    TQCString qs=TQFile::encodeName(tdeconf->readPathEntry("Loadfile","gm.map"));

#ifdef TDEMidDEBUG
    printf("Read Config file: %s\n",qs.data());
#endif
    setMidiMapFilename(qs.data());

    initializing_songs=1;
    tdeconf->setGroup("KMid");
    setActiveCollection(tdeconf->readNumEntry("ActiveCollection",0));
    initializing_songs=0;

    TQVBoxLayout *lv=new TQVBoxLayout( this );
    lv->addWidget( timebar );
    lv->addWidget( timetags );
    lv->addSpacing(5);
    TQHBoxLayout *lh=new TQHBoxLayout( lv );
    lh->addWidget( qlabelTempo );
    lh->addWidget( tempoLCD );
    lh->addWidget( comboSongs, 6 );
    lv->addSpacing(5);    
    lh->addWidget( comboEncodings, 1 );    
    lv->addSpacing(5);        
    lv->addWidget( rhythmview );
    lv->addSpacing(2);
    TQHBoxLayout *lh2=new TQHBoxLayout( lv );
    lh2->addWidget( volumebar );
    lh2->addWidget( kdispt );
}

/*void tdemidClient::resizeEvent(TQResizeEvent *)
{
    //timebar->resize(width()-5,timebar->height());
    timebar->setGeometry(5,10,width()-5,timebar->height());
    timetags->setGeometry(5,10+timebar->height(),width()-5,timetags->getFontHeight());
    comboSongs->setGeometry(tempoLCD->x()+tempoLCD->width()+15,tempoLCD->y(),width()-(tempoLCD->x()+tempoLCD->width()+25),tempoLCD->height());
    rhythmview->setGeometry(5,10+timebar->height()+timetags->height()+5+tempoLCD->height()+2,width()-10,7);
    volumebar->setGeometry(5,10+timebar->height()+timetags->height()+5+tempoLCD->height()+10,15,height()-(10+timebar->height()+timetags->height()+5+tempoLCD->height()+15));
    kdispt->setGeometry(((visiblevolumebar)?25:5),10+timebar->height()+timetags->height()+5+tempoLCD->height()+10,width()-(5+((visiblevolumebar)?25:5)),height()-(10+timebar->height()+timetags->height()+5+tempoLCD->height()+10));
}
*/

tdemidClient::~tdemidClient()
{
    if (m_kMid.pctl->playing==1)
    {
        stop();
        //    sleep(1);
    }

    if (m_kMid.pid!=0)
    {
        kill(m_kMid.pid,SIGTERM);
        waitpid(m_kMid.pid, 0L, 0);
	m_kMid.midi->closeDev();
        m_kMid.pid=0;
    }

    allNotesOff();

    delete midifile_opened;
    delete player;
    delete midi;
    delete [] collectionplaylist;

    saveCollections();
    delete slman;

// Let's detach and delete shared memory
    shmdt((char *)m_kMid.pctl);
    shmctl(m_kMid.pctlsmID, IPC_RMID, 0L);
    m_kMid.pctl=0L;
}

// Use KURL::filename ! (David)
char *extractFilename(const char *in,char *out)
{
    char *p=(char *)in;
    char *result=out;
    char *filename=(char *)in;
    while (*p!=0)
    {
        if (*p=='/') filename=p+1;
        p++;
    }
    while (*filename!=0)
    {
        *out=*filename;
        out++;
        filename++;
    }
    *out=0;
    return result;
}

int tdemidClient::openFile(const char *filename)
{
    m_kMid.pctl->message|=PLAYER_HALT;
    stop();
    int r;
    player->setGenerateBeats(true);
    if ((r=player->loadSong(filename))!=0)
    {
        TQString errormsg;
        switch (r)
        {
        case (-1) : errormsg =
                            i18n("The file %1 does not exist or cannot be opened.").arg(filename);
        break;
        case (-2) : errormsg =
                            i18n("The file %1 is not a MIDI file.").arg(filename);break;
        case (-3) : errormsg =
                            i18n("Ticks per quarter note is negative. Please send this file to larrosa@kde.org");break;
        case (-4) : errormsg =
                            i18n("Not enough memory.");break;
        case (-5) : errormsg =
                            i18n("This file is corrupted or not well built.");break;
        case (-6) : errormsg =
                            i18n("%1 is not a regular file.").arg(filename);break;
        default :   errormsg = i18n("Unknown error message");break;
        }
        KMessageBox::error(this, errormsg);
        //	player->loadSong(midifile_opened);
        if (midifile_opened) delete midifile_opened;
        midifile_opened=0L;
        timebar->setRange(0,240000);
        timebar->setValue(0);
        timetags->repaint(true);
        kdispt->ClearEv();
        kdispt->repaint(true);
        topLevelWidget()->setCaption("KMid");

        return -1;
    }

    if (midifile_opened) delete midifile_opened;
    midifile_opened=new char[strlen(filename)+1];
    strcpy(midifile_opened,filename);
#ifdef TDEMidDEBUG
    printf("TOTAL TIME: %g milliseconds\n",player->information()->millisecsTotal);
#endif
    //    noteArray=player->parseNotes();
    noteArray=player->noteArray();
    timebar->setRange(0,(int)(player->information()->millisecsTotal));
    timetags->repaint(true);
    kdispt->ClearEv();
    spev=player->specialEvents();
    while (spev)
    {
        if ((spev->type==1) || (spev->type==5))
        {
            kdispt->AddEv(spev);
        }
        spev=spev->next;
    }

    kdispt->calculatePositions();
    kdispt->CursorToHome();
//    kdispt->updateScrollBars();
    emit mustRechooseTextEvent();
    kdispt->repaint(true);
    tempoLCD->display(tempoToMetronomeTempo(m_kMid.pctl->tempo));
    currentTempo=tempoLCD->getValue();
    tempoLCD->setDefaultValue(tempoToMetronomeTempo(m_kMid.pctl->tempo)*m_kMid.pctl->ratioTempo);

    char *fn=new char[strlen(filename)+20];
    extractFilename(filename,fn);
    char *capt=new char[strlen(fn)+20];
    sprintf(capt,"KMid - %s",fn);
    delete fn;
    topLevelWidget()->setCaption(capt);
    delete capt;

    timebar->setValue(0);
    return 0;
}

int tdemidClient::openURL(const TQString _url)
{
    KURL u(_url);
    if (!u.isValid()) {printf("Malformed URL\n");return -1;};

    TQString filename;
    bool deleteFile=false;
    if (!u.isLocalFile())
    {
	filename = TQString("/tmp/") + u.filename();
	TDEIO::Job *iojob = TDEIO::copy( u, KURL::fromPathOrURL( filename ) );
	downloaded=false;
	connect( iojob, TQ_SIGNAL( result( TDEIO::Job *) ), this, TQ_SLOT(downloadFinished( TDEIO::Job * ) ) );

	if (!downloaded)
            kapp->enter_loop();
	deleteFile=true;

    }
    else
    {
	filename=u.path();
    }

    TQCString filename_8bit = TQFile::encodeName(filename);
    int r=-1;
    if (!filename_8bit.isEmpty())
    {
        r=openFile(filename_8bit.data());

        TDEConfig *cfg=TDEGlobal::instance()->config();
        if (cfg->readBoolEntry("deleteTmpNonLocalFiles",false))
        {
            unlink(filename_8bit.data());
        }
    }
    return r;
}

ulong tdemidClient::timeOfNextEvent(int *type)
{
    int t=0;
    ulong x=0;


    if (!channelView)
    {
        if ((spev)&&(spev->type!=0))
        {
            t=1;
            x=spev->absmilliseconds;
        }
    }
    else
    {
        if (noteArray)
        {
            NoteArray::noteCmd *ncmd=noteArray->get();
            if (!ncmd)
            {
                if ((spev)&&(spev->type!=0))
                {
                    t=1;
                    x=spev->absmilliseconds;
                }
            }
            else
            {
                if ((!spev)||(spev->type==0))
                {
                    t=2;
                    x=ncmd->ms;
                }
                else
                {
                    if (spev->absmilliseconds<ncmd->ms)
                    {
                        t=1;
                        x=spev->absmilliseconds;
                    }
                    else
                    {
                        t=2;
                        x=ncmd->ms;
                    }

                }
            }
        }
    }

    if (type) *type=t;
    return x;
    /*

     if (type!=NULL) *type=0;
     if (channelView==NULL)
     {
     if ((spev!=NULL)&&(spev->type!=0))
     {
     if (type!=NULL) *type=1;
     return spev->absmilliseconds;
     }
     else return 0;
     }

    if (noteArray==NULL) return 0;
    noteCmd *ncmd=noteArray->get();
    if (ncmd==NULL)
    {
        if ((spev!=NULL)&&(spev->type!=0))
        {
            if (type!=NULL) *type=1;
            return spev->absmilliseconds;
        }
        else return 0;
    }
    else
    {
        if ((spev==NULL)||(spev->type==0))
        {
            if (type!=NULL) *type=2;
            return ncmd->ms;
        }
        else
        {
            if (spev->absmilliseconds<ncmd->ms)
            {
                if (type!=NULL) *type=1;
                return spev->absmilliseconds;
            }
            else
            {
                if (type!=NULL) *type=2;
                return ncmd->ms;
            }

        }
    }
    */
}

void tdemidClient::slotPlay()
{
    if (!player->isSongLoaded())
    {
        KMessageBox::sorry(this,
                         i18n("You must load a file before playing it."));
        return;
    }
    if (m_kMid.pctl->playing==1)
    {
        KMessageBox::sorry(this,
                         i18n("A song is already being played."));
        return;
    }
    if (midi->checkInit()==-1)
    {
        KMessageBox::error(this,
			i18n("Could not open /dev/sequencer.\nProbably there is another program using it."));
        return;
    }

    kdispt->CursorToHome();
    m_kMid.pctl->message=0;
    m_kMid.pctl->playing=0;
    m_kMid.pctl->finished=0;
    m_kMid.pctl->error=0;
    m_kMid.pctl->SPEVplayed=0;
    m_kMid.pctl->SPEVprocessed=0;
#ifdef TDEMidDEBUG
    passcount=0;
#endif
    noteArray->iteratorBegin();

    TQApplication::flushX();
    if ((m_kMid.pid=fork())==0)
    {
#ifdef TDEMidDEBUG
        printf("PlayerProcessID: %d\n",getpid());
#endif
        player->play(0,(void (*)(void))tdemidOutput);
#ifdef TDEMidDEBUG
        printf("End of child process\n");
#endif
        _exit(0);
    }
    m_kMid.pctl->millisecsPlayed=0;


    spev=player->specialEvents();
#ifdef TDEMidDEBUG
    printf("writing SPEV\n");
    player->debugSpecialEvents();
    printf("writing SPEV(END)\n");
#endif

    while ((m_kMid.pctl->playing==0)&&(m_kMid.pctl->error==0)) ;

    if (m_kMid.pctl->error==1) return;
    beginmillisec=m_kMid.pctl->beginmillisec;

    int type;
    ulong x=timeOfNextEvent(&type);
    if (type!=0)
        timer4events->start(x,true);

    timer4timebar->start(1000);

#ifdef TDEMidDEBUG
    printf("PlayerProcess: %d . ParentProcessID: %d\n",m_kMid.pid,getpid());
    printf("******************************-\n");
#endif
}

void tdemidClient::timebarUpdate()
{
    itsme=1;
    if (m_kMid.pctl->playing==0)
    {
        timer4timebar->stop();
    }

    timeval tv;
    gettimeofday(&tv, NULL);
    ulong currentmillisec=tv.tv_sec*1000+tv.tv_usec/1000;
    m_kMid.pctl->millisecsPlayed=(currentmillisec-beginmillisec);

    timebar->setValue((int)(m_kMid.pctl->millisecsPlayed));
    itsme=0;
    if ((m_kMid.pctl->playing==0)&&(m_kMid.pctl->finished==1))
    {
        waitpid(m_kMid.pid, NULL, 0);
        if (loopsong)
        {
            play();
            return;
        }
        else
            nextSong();
    }
}

void tdemidClient::slotSeek(int i)
{
    if (itsme) return;

    if (m_kMid.pctl->playing==0)
    {
        itsme=1;
        timebar->setValue(0);
        itsme=0;
        return;
    }

    if (m_kMid.pctl->paused) return;

    if (m_kMid.pid!=0)
    {
        kill(m_kMid.pid,SIGTERM);
#ifdef TDEMidDEBUG
        printf("Waiting for Process %d to be killed\n",m_kMid.pid);
#endif
        waitpid(m_kMid.pid, NULL, 0);
	m_kMid.midi->closeDev();
        m_kMid.pid=0;
    }
    allNotesOff();


#ifdef TDEMidDEBUG
    printf("change Time: %d\n",i);
#endif

    timer4events->stop();
    if (channelView!=NULL) channelView->reset(0);

    moveEventPointersTo((ulong)i);

    m_kMid.pctl->playing=0;
    m_kMid.pctl->OK=0;
    m_kMid.pctl->error=0;
    m_kMid.pctl->gotomsec=i;
    m_kMid.pctl->message|=PLAYER_SETPOS;

    TQApplication::flushX();
    if ((m_kMid.pid=fork())==0)
    {
#ifdef TDEMidDEBUG
        printf("Player_ProcessID: %d\n",getpid());
#endif

        player->play(0,(void (*)(void))tdemidOutput);

#ifdef TDEMidDEBUG
        printf("End of child process\n");
#endif
        _exit(0);
    }

    while ((m_kMid.pctl->playing==0)&&(m_kMid.pctl->error==0)) ;

    if (m_kMid.pctl->error==1) return;
    beginmillisec=m_kMid.pctl->beginmillisec-i;
    ulong currentmillisec=m_kMid.pctl->beginmillisec;

    int type;
    ulong x=timeOfNextEvent(&type);
    if (type!=0)
        timer4events->start(x-(currentmillisec-beginmillisec),true);

    /*
     if (spev==NULL) return;
     ulong delaymillisec=spev->absmilliseconds-(currentmillisec-beginmillisec);
     timer4events->start(delaymillisec,true);
     */

    m_kMid.pctl->OK=0;
/*
    tempoLCD->display(tempoToMetronomeTempo(m_kMid.pctl->tempo));
    currentTempo=tempoLCD->getValue();
    tempoLCD->setDefaultValue(tempoToMetronomeTempo(m_kMid.pctl->tempo)*m_kMid.pctl->ratioTempo);
*/
}

void tdemidClient::moveEventPointersTo(ulong ms)
{
#ifdef TDEMidDEBUG
    printf("Move To: %lu\n",ms);
#endif
    spev=player->specialEvents();

    ulong tempo=(ulong)(500000 * m_kMid.pctl->ratioTempo);
    int num=4;
    int den=4;

    while ((spev!=NULL)&&(spev->absmilliseconds<ms))
    {
        if (spev->type==3) tempo=spev->tempo;
        else if (spev->type==6) {num=spev->num;den=spev->den;}
        spev=spev->next;
    }
    tempoLCD->display(tempoToMetronomeTempo(tempo));
    currentTempo=tempoLCD->getValue();
    tempoLCD->setDefaultValue(tempoToMetronomeTempo(tempo)*m_kMid.pctl->ratioTempo);

    rhythmview->setRhythm(num,den);

    kdispt->gotomsec(ms);
//    if (noteArray!=NULL) noteArray->moveIteratorTo(ms);
    if (noteArray!=NULL)
    {
        int pgm[16];
        noteArray->moveIteratorTo(ms,pgm);
        if (channelView!=NULL)
        {
            for (int j=0;j<16;j++)
            {
                if (!m_kMid.pctl->forcepgm[j]) channelView->changeInstrument(j,(m_kMid.pctl->gm==1)?(pgm[j]):(MT32toGM[pgm[j]]));
                else channelView->changeInstrument(j,(m_kMid.pctl->pgm[j]));
            }
        }
    }

    /*
     if (noteArray!=NULL)
     {
     noteCmd *ncmd;
     noteArray->iteratorBegin();
     ncmd=noteArray->get();
     while ((ncmd!=NULL)&&(ncmd->ms<ms))
     {
     noteArray->next();
     ncmd=noteArray->get();
     }
     }
     */
}

void tdemidClient::slotSetVolume(int i)
{
    int autochangemap=0;
    if ((m_kMid.pctl->playing==1)&&(m_kMid.pctl->paused==0)) autochangemap=1;

    if (autochangemap)
    {
        pause();
    }
    i=200-i;
    m_kMid.pctl->volumepercentage=i;

    if (autochangemap)
    {
        pause();
    }
}


void tdemidClient::slotPrevSong()
{
    if (currentsl==NULL) return;
    if (collectionplaylist==NULL) generateCPL();
    if (collectionplaylist==NULL) return;
    /*
     if (collectionplaymode==0)
     {
     if (currentsl->getActiveSongID()==1) return;
     currentsl->previous();
     }
     else
     {
     int r;
     while ((r=1+(int) ((double)(currentsl->NumberOfSongs())*rand()/(RAND_MAX+1.0)))==currentsl->getActiveSongID()) ;

     currentsl->setActiveSong(r);
     }
     */
    int idx=searchInCPL(currentsl->getActiveSongID());
    if (idx==0) return;
    idx--;
    currentsl->setActiveSong(collectionplaylist[idx]);

    if (currentsl->getActiveSongID()==-1)
    {
        //    comboSongs->setCurrentItem(0);
        //    currentsl->setActiveSong(1);
        return;
    }

    if (m_kMid.pctl->paused) emit stopPause();
    comboSongs->setCurrentItem(currentsl->getActiveSongID()-1);
    if (openURL(currentsl->getActiveSongName())==-1) return;
    play();

}

void tdemidClient::slotNextSong()
{
    if (currentsl==NULL) return;
    if (collectionplaylist==NULL) generateCPL();
    if (collectionplaylist==NULL) return;

    /*if (collectionplaymode==0)
     {
     if (currentsl->getActiveSongID()==currentsl->NumberOfSongs()) return;
     currentsl->next();
     }
     else
     {
     int r;
     while ((r=1+(int) ((double)(currentsl->NumberOfSongs())*rand()/(RAND_MAX+1.0)))==currentsl->getActiveSongID()) ;

     #ifdef TDEMidDEBUG
     printf("random number:%d\n",r);
     #endif
     currentsl->setActiveSong(r);
     }
     */
    int idx=searchInCPL(currentsl->getActiveSongID());
    idx++;
    if (idx==currentsl->NumberOfSongs()) return;
    currentsl->setActiveSong(collectionplaylist[idx]);
    if (currentsl->getActiveSongID()==-1)
    {
        ////    comboSongs->setCurrentItem(0);
        //    currentsl->setActiveSong(1);
        return;
    }

    if (m_kMid.pctl->paused) emit stopPause();
    comboSongs->setCurrentItem(currentsl->getActiveSongID()-1);
    if (openURL(currentsl->getActiveSongName())==-1) return;
    play();
}

void tdemidClient::slotPause()
{
    if (m_kMid.pctl->playing==0) return;
#ifdef TDEMidDEBUG
    printf("song Pause\n");
#endif
    if (m_kMid.pctl->paused==0)
    {
        if (m_kMid.pid!=0)
        {
            kill(m_kMid.pid,SIGTERM);
            waitpid(m_kMid.pid, NULL, 0);
	    m_kMid.midi->closeDev();
            m_kMid.pid=0;
        }
        pausedatmillisec=(ulong)m_kMid.pctl->millisecsPlayed;
        m_kMid.pctl->paused=1;
        timer4timebar->stop();
        timer4events->stop();
        allNotesOff();
        //    kill(m_kMid.pid,SIGSTOP);
        //   The previous line doesn't work because it stops the two processes (!?)
    }
    else
    {
        m_kMid.pctl->playing=0;
        m_kMid.pctl->OK=0;
        m_kMid.pctl->error=0;
        m_kMid.pctl->gotomsec=pausedatmillisec;
        m_kMid.pctl->message|=PLAYER_SETPOS;

	TQApplication::flushX();
        if ((m_kMid.pid=fork())==0)
        {
#ifdef TDEMidDEBUG
            printf("PlayerProcessID: %d\n",getpid());
#endif
            player->play(0,(void (*)(void))tdemidOutput);
#ifdef TDEMidDEBUG
            printf("End of child process\n");
#endif
            _exit(0);
        }

        while ((m_kMid.pctl->playing==0)&&(m_kMid.pctl->error==0)) ;

        if (m_kMid.pctl->error) return;

        m_kMid.pctl->OK=0;
        m_kMid.pctl->paused=0;

        beginmillisec=m_kMid.pctl->beginmillisec-pausedatmillisec;
        ulong currentmillisec=m_kMid.pctl->beginmillisec;

        int type;
        ulong x=timeOfNextEvent(&type);
        if (type!=0)
            timer4events->start(x-(currentmillisec-beginmillisec),true);
        timer4timebar->start(1000);

        if (noteArray!=NULL)
        {
            int pgm[16];
            noteArray->moveIteratorTo(pausedatmillisec,pgm);
            if (channelView!=NULL)
            {
                for (int j=0;j<16;j++)
                {
                    if (!m_kMid.pctl->forcepgm[j]) channelView->changeInstrument(j,(m_kMid.pctl->gm==1)?(pgm[j]):(MT32toGM[pgm[j]]));
                    else channelView->changeInstrument(j,(m_kMid.pctl->pgm[j]));
                }
            }

        }

    }
}

void tdemidClient::shuttingDown(void)
{
    shuttingdown=true;
    stop();
}

void tdemidClient::slotStop()
{
    if (!m_kMid.pctl) return;

    if (!shuttingdown)
    {
         for (int i=0;i<16;i++) m_kMid.pctl->forcepgm[i]=false;
       if (channelView) channelView->reset();
       if (tempoLCD)
       {
         tempoLCD->display(tempoToMetronomeTempo(m_kMid.pctl->tempo));
         currentTempo=tempoLCD->getValue();
         tempoLCD->setDefaultValue(tempoToMetronomeTempo(m_kMid.pctl->tempo)*m_kMid.pctl->ratioTempo);
       }
    }

    if (m_kMid.pctl->playing==0) return;

    if (m_kMid.pctl->paused) return;
#ifdef TDEMidDEBUG
    printf("song Stop\n");
#endif
    if (m_kMid.pid!=0)
    {
        kill(m_kMid.pid,SIGTERM);
#ifdef TDEMidDEBUG
	printf("Killing\n");
#endif
        waitpid(m_kMid.pid, NULL, 0);
	m_kMid.midi->closeDev();
        m_kMid.pid=0;
    }

    m_kMid.pctl->playing=0;
    ////////m_kMid.pctl->OK=0;
    ////////m_kMid.pctl->message|=PLAYER_HALT;
    timer4timebar->stop();
    timer4events->stop();

    allNotesOff();

    //m_kMid.pctl->playing=0;
    //m_kMid.pctl->paused=0;
    ////////while (m_kMid.pctl->OK==0) ;
}

void tdemidClient::slotRewind()
{
    if ((m_kMid.pctl->playing)&&(!m_kMid.pctl->paused))
    {
        timebar->subtractPage();
        slotSeek(timebar->value());
    }
}

void tdemidClient::slotForward()
{
    if ((m_kMid.pctl->playing)&&(!m_kMid.pctl->paused))
    {
        timebar->addPage();
        slotSeek(timebar->value());
    }
}


void tdemidClient::allNotesOff()
{
    bool done=false;
    m_kMid.pctl->isSendingAllNotesOff=true;
    DeviceManager *_midi=new DeviceManager();
    _midi->initManager();
    _midi->openDev();
    _midi->allNotesOff();
    _midi->closeDev();
    delete _midi;
    done=true;
    m_kMid.pctl->isSendingAllNotesOff=false;
}

void tdemidClient::tdemidOutput(void)
{
// Should do nothing
    /*
    Midi_event *ev=pctl->ev;

    timeval tv;
    gettimeofday(&tv, NULL);
    ulong currentmillisec=tv.tv_sec*1000+tv.tv_usec/1000;

    if ((ev->command==MIDI_SYSTEM_PREFIX)&&((ev->command|ev->chn)==META_EVENT))
    {
        if ((ev->d1==5)||(ev->d1==1))
        {
            char *text=new char[ev->length+1];
            strncpy(text,(char *)ev->data,ev->length);
            text[ev->length]=0;
#ifdef TDEMidDEBUG
            printf("%s , played at: %ld\n",text,currentmillisec-beginmillisec);
#endif
        }
        else if (ev->d1==ME_SET_TEMPO)
        {
            int tempo=(ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2]);
            //	    printf("Change tempo: %d , %g, played at:%ld\n",tempo,tempoToMetronomeTempo(tempo),currentmillisec-beginmillisec);
        }

    }
     */
}


void tdemidClient::processSpecialEvent()
{
/*
    if (spev==NULL)
    {
        printf("SPEV == NULL !!!!!\n");
        return;
    }
*/

//#ifdef TDEMidDEBUG
//    printf(":::: %ld",passcount++);
//    printf("%d %s %ld",spev->type,spev->text,spev->absmilliseconds);
//#endif

    int processNext=1;
    int type;
    ulong x;

    long delaymillisec=~0;

    while (processNext)
    {
        /*
         timeval tv;
         gettimeofday(&tv, NULL);
         ulong currentmillisec=tv.tv_sec*1000+tv.tv_usec/1000;
         */

        x=timeOfNextEvent(&type);

        if (type==0) return;
        if (type==1)
        {
            if ((spev->type==1) || (spev->type==5))
            {
                kdispt->PaintIn(spev->type);
            }
            else if (spev->type==3)
            {
                tempoLCD->display(tempoToMetronomeTempo(spev->tempo));
#ifdef TDEMidDEBUG
                printf("Changing lcd tempo: spev->tempo: %d , ratio: %.9g\n",spev->tempo,m_kMid.pctl->ratioTempo);
                printf("Result: %g %.9g %d\n",tempoToMetronomeTempo(spev->tempo),tempoToMetronomeTempo(spev->tempo),(int)tempoToMetronomeTempo(spev->tempo));
#endif
                currentTempo=tempoLCD->getValue();
                tempoLCD->setDefaultValue(tempoToMetronomeTempo(spev->tempo)*m_kMid.pctl->ratioTempo);
            }
            else if (spev->type==6)
            {
                rhythmview->setRhythm(spev->num,spev->den);
            }
            else if (spev->type==7)
            {
#ifdef TDEMidDEBUG
                printf("Beat: %d/%d\n",spev->num,spev->den);
#endif
                rhythmview->Beat(spev->num);
            }
            m_kMid.pctl->SPEVprocessed++;
            spev=spev->next;
        }
        if (type==2)
        {
            NoteArray::noteCmd *ncmd=noteArray->get();
            if (ncmd==NULL) {printf("ncmd is NULL !!!");return;}
            if (channelView!=NULL)
            {
                if (ncmd->cmd==1) channelView->noteOn(ncmd->chn,ncmd->note);
                else if (ncmd->cmd==0) channelView->noteOff(ncmd->chn,ncmd->note);
                else if (ncmd->cmd==2)
                    if (!m_kMid.pctl->forcepgm[ncmd->chn]) channelView->changeInstrument(ncmd->chn,(m_kMid.pctl->gm==1)?(ncmd->note):(MT32toGM[ncmd->note]));
                    else channelView->changeInstrument(ncmd->chn,(m_kMid.pctl->pgm[ncmd->chn]));

                noteArray->next();
            }
        }
        processNext=0;

        x=timeOfNextEvent(&type);

        if (type==0) return;

        timeval tv;
        ulong currentmillisec;
        gettimeofday(&tv, NULL);
        currentmillisec=tv.tv_sec*1000+tv.tv_usec/1000;
        delaymillisec=x-(currentmillisec-beginmillisec);
        if (delaymillisec<10) processNext=1;
    }

    if (delaymillisec!=~(long)0) timer4events->start(delaymillisec,true);

}

void tdemidClient::repaintText(int type)
{
    kdispt->ChangeTypeOfTextEvents(type);
    typeoftextevents=type;
    kdispt->repaint(true);
}

int tdemidClient::ChooseTypeOfTextEvents(void)
{
    return kdispt->ChooseTypeOfTextEvents();
}

void tdemidClient::setSongType(int i)
{
    int autochangetype=0;
    if ((m_kMid.pctl->playing==1)&&(m_kMid.pctl->paused==0)) autochangetype=1;

    if (autochangetype)
    {
        pause();
    }
    m_kMid.pctl->gm=i;

    if (autochangetype)
    {
        pause();
    }

}


TQFont * tdemidClient::getFont(void)
{
return kdispt->getFont();
}

void  tdemidClient::fontChanged(void)
{
    kdispt->fontChanged();
}

void tdemidClient::setMidiDevice(int i)
{
    midi->setDefaultDevice(i);
}

void tdemidClient::setMidiMapFilename(const char *mapfilename)
{
    MidiMapper *map=new MidiMapper(mapfilename);
    if (map->ok()==-1)
    {
        TQString tmp = locate("appdata", TQString("maps/") + mapfilename);
        delete map;
        map=new MidiMapper(tmp.local8Bit());
        if (map->ok()!=1)
        {
            delete map;
            map=new MidiMapper(NULL);
        }
    }
    int autochangemap=0;
    if ((m_kMid.pctl->playing==1)&&(m_kMid.pctl->paused==0)) autochangemap=1;

    if (autochangemap)
    {
        pause();
    }
    midi->setMidiMap(map);
    if (autochangemap)
    {
        pause();
    }
}

void tdemidClient::setSLManager(SLManager *slm)
{
    if (slman!=NULL) delete slman;
    slman=slm;
}

void tdemidClient::setActiveCollection(int i)
{
    activecollection=i;
    TDEConfig *tdeconf=TDEGlobal::instance()->config();

    tdeconf->setGroup("KMid");
    tdeconf->writeEntry("ActiveCollection",activecollection);
    currentsl=slman->getCollection(activecollection);
    generateCPL();
    initializing_songs=1;
    fillInComboSongs();
    initializing_songs=0;
}

void tdemidClient::fillInComboSongs(void)
{
    //int oldselected=comboSongs->currentItem();
    comboSongs->clear();
    //comboSongs->setCurrentItem(-1);
    if (currentsl==NULL) return;
    currentsl->iteratorStart();
    char temp[FILENAME_MAX];
    char temp2[FILENAME_MAX];
    TQString qs;
    while (!currentsl->iteratorAtEnd())
    {
	qs=currentsl->getIteratorName();
	//KURL::decode(qs);
        sprintf(temp,"%d - %s",currentsl->getIteratorID(),
                extractFilename(KURL::decode_string(qs).ascii(),temp2));
        comboSongs->insertItem(temp);
        currentsl->iteratorNext();
    }
    if (currentsl->getActiveSongID()==-1) return;
    comboSongs->setCurrentItem(currentsl->getActiveSongID()-1);
    /*
     if (oldselected==currentsl->getActiveSongID()-1)
     {
     slotSelectSong(currentsl->getActiveSongID()-1);
     }
     */
    slotSelectSong(currentsl->getActiveSongID()-1);
}

void tdemidClient::slotSelectSong(int i)
{
    if (currentsl==NULL) return;
    i++;
    if ((i<=0))  // The collection may be empty, or it may be just a bug :-)
    {
#ifdef TDEMidDEBUG
        printf("Empty\n");
#endif
        emit stopPause();
        if (m_kMid.pctl->playing) stop();
        if (midifile_opened!=NULL) delete midifile_opened;
        midifile_opened=NULL;
        player->removeSong();
        timebar->setRange(0,240000);
        timebar->setValue(0);
        timetags->repaint(true);
        kdispt->ClearEv();
        kdispt->repaint(true);
        comboSongs->clear();
        comboSongs->repaint(true);
        topLevelWidget()->setCaption("KMid");
        return;
    }

    if ((i==currentsl->getActiveSongID())&&(!initializing_songs)) return;
    int pl=0;
    if (m_kMid.pctl->playing==1) pl=1;

    if (m_kMid.pctl->paused) emit stopPause();
    if (/*(i!=currentsl->getActiveSongID())&&*/(pl==1)) stop();
    currentsl->setActiveSong(i);
    if (openURL(currentsl->getActiveSongName())==-1) return;
    if (pl) play();

}


int tdemidClient::getSelectedSong(void)
{
    if (currentsl==NULL) return -1;
    return currentsl->getActiveSongID();
}


void tdemidClient::setSongLoop(int i)
{
    loopsong=i;
}


void tdemidClient::generateCPL(void)
{
    delete [] collectionplaylist;
    collectionplaylist=0;

    if (currentsl==NULL) return;

    if (collectionplaymode==0)
        collectionplaylist=generate_list(currentsl->NumberOfSongs());
    else
        collectionplaylist=generate_random_list(currentsl->NumberOfSongs());
}


void tdemidClient::setCollectionPlayMode(int i)
{
    collectionplaymode=i;
    generateCPL();
}

void tdemidClient::saveCollections(void)
{
    if (slman==NULL) return;
#ifdef TDEMidDEBUG
    printf("Saving collections in: %s\n",collectionsfile.ascii());
#endif
    slman->saveConfig(TQFile::encodeName(collectionsfile));
}

void tdemidClient::saveLyrics(FILE *fh)
{
    if (kdispt!=NULL) kdispt->saveLyrics(fh);
}

int tdemidClient::searchInCPL(int song)
{
    if (currentsl==NULL) return -1;
    int i=0;
    int n=currentsl->NumberOfSongs();
    while ((i<n)&&(collectionplaylist[i]!=song)) i++;
    if (i<n) return i;
    return -1;
}

void tdemidClient::visibleVolumeBar(int i)
{
#ifndef TEMPHACK
    visiblevolumebar=i;

    if (visiblevolumebar)
        volumebar->show();
    else
        volumebar->hide();
#endif
}

void tdemidClient::visibleChannelView(int i)
{
    if ((channelView==NULL)&&(i==1))
    {
        channelView=new ChannelView();
        if (noteArray!=NULL)
        {
            int pgm[16],j;
            noteArray->moveIteratorTo((ulong)m_kMid.pctl->millisecsPlayed,pgm);
            for (j=0;j<16;j++)
            {
                if (!m_kMid.pctl->forcepgm[j]) channelView->changeInstrument(j,(m_kMid.pctl->gm==1)?(pgm[j]):(MT32toGM[pgm[j]]));
                else channelView->changeInstrument(j,(m_kMid.pctl->pgm[j]));
                channelView->changeForceState(j,m_kMid.pctl->forcepgm[j]);
            }
        }
        channelView->show();
        connect(channelView,TQ_SIGNAL(signalToKMidClient(int *)),this,TQ_SLOT(communicationFromChannelView(int *)));
	connect(kapp,TQ_SIGNAL(shutDown()),parentWidget(),TQ_SLOT(shuttingDown()));

    }
    else if ((channelView!=NULL)&&(i==0))
    {
        delete channelView;
        channelView=NULL;

    }
    rethinkNextEvent();
}

void tdemidClient::channelViewDestroyed()
{
    channelView=NULL;
    rethinkNextEvent();
}


void tdemidClient::rethinkNextEvent(void)
{
    if (m_kMid.pctl->playing==0) return;
    timer4events->stop();

    int type;
    ulong delaymillisec;
    ulong x=timeOfNextEvent(&type);

    if (type==0) return;

    timeval tv;
    ulong currentmillisec;
    gettimeofday(&tv, NULL);
    currentmillisec=tv.tv_sec*1000+tv.tv_usec/1000;
    delaymillisec=x-(currentmillisec-beginmillisec);

    timer4events->start(delaymillisec,true);
}

void tdemidClient::communicationFromChannelView(int *i)
{
    if (i==NULL) return;
    int autocontplaying=0;
    if ((i[0]==CHN_CHANGE_PGM)||((i[0]==CHN_CHANGE_FORCED_STATE)&&(i[3]==1)))
    {
        if ((m_kMid.pctl->playing==1)&&(m_kMid.pctl->paused==0)) autocontplaying=1;

        if (autocontplaying)
        {
            pause();
        }
    }
    if (i[0]==CHN_CHANGE_PGM)
        m_kMid.pctl->pgm[i[1]-1]=i[2];
    else if (i[0]==CHN_CHANGE_FORCED_STATE)
        m_kMid.pctl->forcepgm[i[1]-1]=i[2];
    if ((i[0]==CHN_CHANGE_PGM)||((i[0]==CHN_CHANGE_FORCED_STATE)&&(i[3]==1)))
    {
        if (autocontplaying)
        {
            pause();
        }
    }

}

void tdemidClient::slotSetTempo(double value)
{
    if (!player->isSongLoaded())
    {
        tempoLCD->display(120);
        currentTempo=120;
        tempoLCD->setDefaultValue(120);
        return;
    }

#ifdef TDEMidDEBUG
    printf("Change tempo to %g\n",value);
#endif
    int autocontplaying=0;

    if ((m_kMid.pctl->playing==1)&&(m_kMid.pctl->paused==0)) autocontplaying=1;


    if (autocontplaying)
    {
        pause();
    }

//    double ratio=(tempoToMetronomeTempo(m_kMid.pctl->tempo)*m_kMid.pctl->ratioTempo)/(value);
//    double ratio=(tempoLCD->getOldValue()*m_kMid.pctl->ratioTempo)/(value);
    double ratio=(currentTempo*m_kMid.pctl->ratioTempo)/value;

    char s[20];
    sprintf(s,"%g",ratio);
    if (strcmp(s,"1")!=0) tempoLCD->setLCDColor (255,100,100);
    else tempoLCD->setLCDColor (100,255,100);
#ifdef TDEMidDEBUG
    printf("ratio: (%.9g = %g ) tempo now: %g , new tempo %g\n",ratio,ratio,tempoToMetronomeTempo(m_kMid.pctl->tempo),value);
    printf("OldValue: %g , value %g\n",tempoLCD->getOldValue(),value);
#endif

    if (m_kMid.pctl->paused==1)
    {
        pausedatmillisec=(long)(((double)pausedatmillisec/m_kMid.pctl->ratioTempo)*ratio);
#ifdef TDEMidDEBUG
        printf("pausedat: %ld\n",pausedatmillisec);
#endif
    }
    player->setTempoRatio(ratio);

    timebar->setRange(0,(int)(player->information()->millisecsTotal));
    timebar->setValue(pausedatmillisec);
    timetags->repaint(true);

    kdispt->ClearEv(false);

    noteArray=player->noteArray();
    spev=player->specialEvents();
    currentTempo=value;

    while (spev!=NULL)
    {
        if ((spev->type==1) || (spev->type==5))
        {
            kdispt->AddEv(spev);
        }
        spev=spev->next;
    }

    kdispt->calculatePositions();
    kdispt->CursorToHome();
    if (m_kMid.pctl->paused==1)
        moveEventPointersTo(pausedatmillisec);

    if (autocontplaying)
    {
        pause();
    }

}

void tdemidClient::downloadFinished(TDEIO::Job *)
{
    downloaded=true;
    kapp->exit_loop();
}

TQSize tdemidClient::sizeHint() const
{
    TQSize sh = TQWidget::sizeHint();
    return sh.expandedTo(TQSize(560,420));
}

TQSizePolicy tdemidClient::sizePolicy()
{
    return TQSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding);
}


void tdemidClient::play()
{
  slotPlay();
}
void tdemidClient::pause()
{
  slotPause();
}
void tdemidClient::stop()
{
  slotStop();
}
void tdemidClient::rewind()
{
  slotRewind();
}
void tdemidClient::forward()
{
  slotForward();
}
void tdemidClient::seek(int ms)
{
  slotSeek(ms);
}
void tdemidClient::prevSong()
{
  slotPrevSong();
}
void tdemidClient::nextSong()
{
  slotNextSong();
}
void tdemidClient::setVolume(int i)
{
  slotSetVolume(200-i);
}
void tdemidClient::setTempo(int i)
{
  slotSetTempo(i);
}
void tdemidClient::setSongEncoding( int i )
{
   TDEListAction *tmplistaction=
    ((TDEListAction*)actionCollection->action("file_type"));

  tmplistaction->setCurrentItem(i);
}
void tdemidClient::setLyricEvents( int i )
{
   TDEListAction *tmplistaction=
    ((TDEListAction*)actionCollection->action("display_events"));
  tmplistaction->setCurrentItem(i);
}
void tdemidClient::setCurrentSong(int i)
{
  getComboSongs()->setCurrentItem(i-1);
  slotSelectSong(i-1);
}
void tdemidClient::setPlayListMode(int i)
{
  ((TDEListAction*)actionCollection->action("play_order"))->setCurrentItem(i);
}
void tdemidClient::slotSelectEncoding(int i)
{
  if (i == 0)
     kdispt->setLyricsEncoding(TQString()); // Default
  else
     kdispt->setLyricsEncoding(TDEGlobal::charsets()->encodingForName(comboEncodings->text(i)));
}
#include "tdemidclient.moc"
