/*
 Copyright (C) 2002 Andreas Thiede ( a.thiede@berlin.de )

 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <IO/Input/IOVideoInput.hpp>
#include <IO/Manager/IOManager.hpp>
#include <IO/Manager/IOBuffer.hpp>
#include <IO/Manager/IOModel.hpp>
#include <IO/osx/TVHardware.hpp>

#import <lib/bt848.h>
#import <lib/Bt8xxLib.h>
#import <lib/bt848.h>
#import <lib/i2c.h>
#import <lib/Tuner.h>
#import <lib/Audio.h>
#import <lib/Preferences.h>
#import <util/Loader.h>

#import <ctype.h>
#import <unistd.h>
#import <stdlib.h>

extern "C" int  MapVideoMem( void ** at , unsigned int * size );
extern "C" int  MapAudioMem( void ** at , unsigned int * size );
extern "C" void CurrentX_OnChange(int);

extern "C" int  TVFormat_OnChange(long NewValue);
extern "C" int  TVTuner_OnChange( int );
extern "C" int  TVCard_OnChange( int );
extern "C" void LoadSettingsFromIni();
extern "C" int  BT848_SetVideoSource( int );
extern "C" int  BT848_Hue_OnChange(long Hue);
extern "C" int  BT848_Contrast_OnChange(long Contrast);
extern "C" int  BT848_Saturation_OnChange(long Sat);
extern "C" int  BT848_SaturationU_OnChange(long SatU);
extern "C" int  BT848_SaturationV_OnChange(long SatV);
extern "C" int  BT848_Brightness_OnChange(long Brightness);

extern TVTUNERID TunerType;
extern TVCARDID CardType;
extern long CurrentX, CurrentY;
extern long AudioSource;

IOVideoInput::IOVideoInput( const string & aName, IODeviceID anID )
 : IOInput( aName, anID ), mWorkThread( aName, true )
{
    init();
}

IOVideoInput::~IOVideoInput()
{
    finit();
}

void * videoThreadEntry( void * _this )
{
    ((IOVideoInput*) _this )->grab();
    
    return 0;
}

bool IOVideoInput::isGrabbing()
{
    return mStarted;
}

void IOVideoInput::grab()
{    
    IOBuffer * buffer = IOBuffer::initWithConstPointer( (const char*) mVideoBuffer, mVideoBufferSize, getNextBlockNumber() );

    while( mWorkThread.serviceCancellation() != IOThread::ioStop ) {

        //int rc = BT848_Wait4Video();
        
        lock();
        
        BT848_Wait4Video();
        
        if( buffer ) {
            push( buffer );            
        }
        
        unlock();
    }

    buffer->release();

}

bool IOVideoInput::start() 
{ 
    if( mInited && ! mStarted && isAttached() && TVHardware::start( IODescription::ioIn, getDeviceID(), this ) ) {

        int video = GetPrivateProfileInt( "Hardware", "VideoSource", 0 );

        BT848_SetVideoSource( video );

        int audio = GetPrivateProfileInt( "Sound", "AudioSource", 0 );

        Audio_SetSource( (AUDIOMUXTYPE) audio );

        mWorkThread.start( videoThreadEntry, this );
    
        mStarted = true;
    }
    return mStarted;
}

bool IOVideoInput::stop() 
{ 
    if( mStarted ) {

        mWorkThread.stop();
        
        TVHardware::stop( IODescription::ioIn, getDeviceID(), this );
         
        mStarted = false;
    }
    
    return mStarted;
}

size_t IOVideoInput::frameSize()
{
    return mVideoBufferSize;
}

static void addSubject( IOView * aView , const string & aModelName )
{
    IOModel * m = new IOModel( aModelName );
    
    m->addView( aView );
}

void IOVideoInput::LoadSettings()
{
    int country = GetPrivateProfileInt( "Hardware", "Country", 0 );
        
    string iniFile = GetApplicationDirectory();
        
    iniFile += "/Contents/Resources/Channel.txt";
    
    //if( access( iniFile.str(), F_OK ) != 0 ) {
        iniFile = getenv("HOME");
        iniFile += "/Library/Preferences/Channel.txt";
    //}
    
    Load_Country_Settings( iniFile.c_str() );
    
    Load_Country_Specific_Settings( iniFile.c_str(), country );

    TuneChannel( Channels.freq[ mCurrentChannel ] );
}

void IOVideoInput::init()
{
    if( getDeviceID()  != 0 ) { // Init device connection only once
        return;
    }
    
    addSubject( this, "Hardware.CardType");
    addSubject( this, "Hardware.InitialBrightness");
    addSubject( this, "Hardware.InitialContrast");
    addSubject( this, "Hardware.InitialHue");
    addSubject( this, "Hardware.InitialSaturationU");
    addSubject( this, "Hardware.InitialSaturationV");
    addSubject( this, "Hardware.TVType");
    addSubject( this, "Hardware.TunerType");
    addSubject( this, "Hardware.VideoSource");
    addSubject( this, "Hardware.Country");
    addSubject( this, "Sound.AudioSource");
    
    LoadSettingsFromIni();
    
    if( TVHardware::init( IODescription::ioIn, getDeviceID(), this ) != true ) { 	

        printf( "IOVideoInput::init failed to initialize %s\n", getName().c_str());

        mInited = false;
    }
    else  {
    
        if( GetPrivateProfileInt( "Hardware", "CardType", TVCARD_UNKNOWN ) == TVCARD_UNKNOWN ) {   

            TVCard_FirstTimeSetupHardware(0,0);
        }
        
        mCurrentChannel  = 23;
        mVideoBuffer     = 0;
        mVideoBufferSize = 0;    
        mTuneOffset      = 0;

        LoadSettings();
    
        int rc = MapVideoMem( (void**) &mVideoBuffer, &mVideoBufferSize );
    
        printf( "MapMem() == %d videoBuf at %p with size %d\n", rc, (void*) mVideoBuffer, mVideoBufferSize );

        rc = MapAudioMem( (void**) &mAudioBuffer, &mAudioBufferSize );
    
        printf( "MapMem() == %d audioBuf at %p with size %d\n", rc, (void*) mAudioBuffer, mAudioBufferSize );

        puts("TVCard_OnChange");
        //getchar();
        
        TVCard_OnChange  ( GetPrivateProfileInt( "Hardware", "CardType" ,  0 ) );

        puts("TVFormat_OnChange");
        //getchar();

        TVFormat_OnChange( GetPrivateProfileInt( "Hardware", "TVType" ,    0 ) );

        puts("TVTuner_OnChange");
        //getchar();

        TVTuner_OnChange ( GetPrivateProfileInt( "Hardware", "TunerType" , 0 ) );


        puts("Tuner_Init");
        //getchar();

        Tuner_Init();


        puts("CurrentX_OnChange");
        //getchar();

        CurrentX_OnChange( 360 );

        puts("Audio_SetSource");
        //getchar();
        
        Audio_SetSource( (AUDIOMUXTYPE) 0 );

        mInited = true;

    }
}

void IOVideoInput::finit()
{
    if( TVHardware::finit( IODescription::ioIn, getDeviceID()) != true ) { 	
        printf( "IOVideoInput::finit failed to finitialize %s\n", getName().c_str());
    }
    
    stop();
    
    IOManager::instance().unregisterNode ( this );
}

bool IOVideoInput::command( const string & command, 
                            const string & param1, const string & param2, const string & param3, const string & param4 )
{
    if( command == "setChannel" ) {

        int c = atoi( param1.c_str() );
        
        if( c >= 0 && c < 125 ) {
        
            AUDIOMUXTYPE old = (AUDIOMUXTYPE) AudioSource;
            
            mCurrentChannel = c;
        
            Audio_SetSource( AUDIOMUX_MUTE );

            usleep( 100 * 1000 );
            
            TuneChannel( Channels.freq[ mCurrentChannel ] );

            usleep( 100 * 1000 );

            Audio_SetSource( old );

            WritePrivateProfileInt( "Tuner", "CurrentChannel", mCurrentChannel );                

            return true;
        }

    }
    else if( command == "changeChannel" ) {

        int c = atoi( param1.c_str() );
        
        if( mCurrentChannel + c < 0 ) {
            mCurrentChannel = 125;
        }
        else if( mCurrentChannel + c > 125  ) {
            mCurrentChannel = 0;
        }
        else {
            mCurrentChannel += c;
        }
        
        mTuneOffset = 0;

        AUDIOMUXTYPE old = (AUDIOMUXTYPE) AudioSource;

        Audio_SetSource( AUDIOMUX_MUTE );

        usleep( 100 * 1000 );
          
        TuneChannel( Channels.freq[ mCurrentChannel ] );

        usleep( 100 * 1000 );

        Audio_SetSource( old );

        WritePrivateProfileInt( "Tuner", "CurrentChannel", mCurrentChannel );                

        return true;
    }
    else if( command == "fineTune" ) {
    
        //mCurrentChannel = GetPrivateProfileInt( "Tuner", "CurrentChannel", 0 );
        
        mTuneOffset += atoi( param1.c_str() );
        
        UInt64 freq = (UInt64) Channels.freq[ mCurrentChannel ];
    
        freq *= 1000;
        freq *= 16;
        freq /= 1000000;
    
        freq += mTuneOffset;
        
        printf( "FineTuneChannel %d\n", (int)freq );

        Tuner_SetFrequency( (int) freq );
        
    }
    else if( command == "togglePause" ) {

        if( ! mWorkThread.isPaused() ) {
            return mWorkThread.pause();
        }
        else if( mWorkThread.isPaused() ) {
            return mWorkThread.resume();
        }
    }
    return false;
}

string IOVideoInput::getValue( const string & valueName )
{
    char buffer[256];
    
    sprintf( buffer, "0" );
    
    if( valueName == "channel" ) {
        sprintf( buffer, "%d", mCurrentChannel );
    }
    else if( valueName == "frameWidth" ) {
        sprintf( buffer, "%ld", CurrentX );
    }
    else if( valueName == "frameHeight" ) {
        sprintf( buffer, "%ld", CurrentY/2 );
    }
    else if( valueName == "isPaused" ) {
        sprintf( buffer, "%d", mWorkThread.isPaused() );
    }
    
    return buffer;
}

void IOVideoInput::updateFrom( IOModel * aModel )
{
    puts("IOVideoInput::updateFrom");
    
    lock();
    
    int val = aModel->getInt();
    
    if( aModel->getName() == "Hardware.CardType") {
        TVCard_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.InitialBrightness"){
        BT848_Brightness_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.InitialContrast"){
        BT848_Contrast_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.InitialHue"){
        BT848_Hue_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.InitialSaturationU"){
        BT848_SaturationU_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.InitialSaturationV"){
        BT848_SaturationV_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.TVType"){
        TVFormat_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.TunerType"){
        TVTuner_OnChange( val );
    }
    else if( aModel->getName() == "Hardware.VideoSource"){
        BT848_SetVideoSource( val );
    }
    else if( aModel->getName() == "Sound.AudioSource"){
        Audio_SetSource( (AUDIOMUXTYPE) val );
    }
    else if( aModel->getName() == "Hardware.Country"){
        LoadSettings();
    }
    
    unlock();

    puts("IOVideoInput::updateFrom done");

}
