/*----------------------------------------------------------------------------
miniAudicle
GUI to chuck audio programming environment

Copyright (c) 2005 Spencer Salazar.  All rights reserved.
http://chuck.cs.princeton.edu/
http://soundlab.cs.princeton.edu/

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
U.S.A.
-----------------------------------------------------------------------------*/

//-----------------------------------------------------------------------------
// file: mAConsoleMonitor.cpp
// desc: console monitor
//
// author: Spencer Salazar (ssalazar@cs.princeton.edu)
// date: Summer 2006
//-----------------------------------------------------------------------------

#include "chuck_def.h"

#include "wx/wx.h"
#include "wx/config.h"

#include "mAParentFrame.h"
#include "mAConsoleMonitor.h"

#include "chuck_errmsg.h"

#ifndef __PLATFORM_WIN32__
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#else
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#endif

DECLARE_EVENT_TYPE( mAEVT_CONSOLE_DATA_AVAILABLE, -1 );
DEFINE_EVENT_TYPE( mAEVT_CONSOLE_DATA_AVAILABLE );

#ifdef __LINUX__
mAConsoleMonitor::mAConsoleMonitor( wxWindow * parent, 
    wxWindowID id, const wxString & title, const wxPoint & pos,
    const wxSize & size, long style )
    : wxFrame( parent, id, title, pos, size, style ) 
#else
mAConsoleMonitor::mAConsoleMonitor( wxMDIParentFrame * parent, 
    wxWindowID id, const wxString & title, const wxPoint & pos,
    const wxSize & size, long style )
    : wxMDIChildFrame( parent, id, title, pos, size, style ) 
#endif /* __LINUX__ */

{

#ifdef __LINUX__
    /* GTK specific default positioning for 1280x1024 resolution */
    Maximize( false );
    SetPosition( wxPoint( 20, 575 ) );
#endif

    int x, y, font_size;
    bool maximize;
    wxConfigBase * config = wxConfigBase::Get();
    
	if( config->Read( _T( "/GUI/ConsoleMonitor/width" ), &x ) &&
		config->Read( _T( "/GUI/ConsoleMonitor/height" ), &y ) )
		SetSize( x, y );

	if( config->Read( _T( "/GUI/ConsoleMonitor/x" ), &x ) &&
		config->Read( _T( "/GUI/ConsoleMonitor/y" ), &y ) )
		Move( x, y );
	
	maximize = false;
	if( config->Read( _T( "/GUI/ConsoleMonitor/maximize" ), &maximize ) )
		Maximize( maximize );

	text = new wxTextCtrl( this, wxID_ANY, _T( "" ), wxDefaultPosition, 
		wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY );

    config->Read( _T( "/GUI/ConsoleMonitor/FontSize" ), &font_size, 9 );
	wxFont font( font_size, wxMODERN, wxNORMAL, wxNORMAL );
	text->SetFont( font );

	scrollback_size = 100000;
	config->Read( _T( "/GUI/ConsoleMonitor/ScrollbackBufferSize" ), (int * )&scrollback_size, 100000 );

#ifndef __DISABLE_CONSOLEMONITOR__
    
#ifndef __PLATFORM_WIN32__
	int fd[2];

	if( pipe( fd ) )
	{
		EM_log( CK_LOG_SEVERE, "(console monitor): pipe error, disabling console monitor" );
		return;
	}

	dup2( fd[1], STDOUT_FILENO );

	out_fd = fd[0];

	if( pipe( fd ) )
	{
		EM_log( CK_LOG_SEVERE, "(console monitor): pipe error, disabling console monitor" );
		return;
	}

	dup2( fd[1], STDERR_FILENO );

	err_fd = fd[0];
	
	int fd_flags = fcntl( out_fd, F_GETFL );
	if( fcntl( out_fd, F_SETFL, fd_flags | O_NONBLOCK ) )
	{
		EM_log( CK_LOG_WARNING, "(console monitor): unable to set stdout to non-blocking" );
	}
	
	fd_flags = fcntl( err_fd, F_GETFL );
	if( fcntl( err_fd, F_SETFL, fd_flags | O_NONBLOCK ) )
	{
		EM_log( CK_LOG_WARNING, "(console monitor): unable to set stderr to non-blocking" );
	}
    
#else
	HANDLE hRead, hWrite;
	int fd_read, fd_write;
	//FILE * stdlib_fd;

	if( !CreatePipe( &hRead, &hWrite, NULL, 8192 ) )
	{
		EM_log( CK_LOG_SEVERE, "(console monitor): pipe error %d, disabling console monitor", GetLastError() );
		return;
	}
	
	/* WARNING: Breaks under Win64! */
	fd_read = _open_osfhandle( ( long ) hRead, _O_RDONLY | _O_TEXT );
	fd_write = _open_osfhandle( ( long ) hWrite, _O_WRONLY | _O_TEXT );

	out_fd = fd_read;

	stdlib_fd = _fdopen( fd_write, "w" );

	setvbuf( stdlib_fd, NULL, _IONBF, 0 );
	
	*stderr = *stdlib_fd;
	*stdout = *stdlib_fd;
#endif

    fd_mutex = new XMutex();
	th_go = TRUE;
	th.start( mAConsoleMonitor::callback, this );

	Connect( wxEVT_CLOSE_WINDOW, 
		wxCloseEventHandler( mAConsoleMonitor::OnClose ) );

    Connect( mAEVT_CONSOLE_DATA_AVAILABLE,
        wxCommandEventHandler( mAConsoleMonitor::OnDataAvailable ) );

#ifdef __LINUX__
/*    Connect( wxID_ANY, wxEVT_CHAR, 
        wxCharEventHandler( mAConsoleMonitor::OnChar ) );*/
    menubar = NULL;
#endif

#endif // __DISABLE_CONSOLEMONITOR__
    
	Show( true );
}

mAConsoleMonitor::~mAConsoleMonitor()
{
    SetMenuBar( NULL );

#ifndef __DISABLE_CONSOLEMONITOR__

	th_go = FALSE;

	// close down the fake stdout/stderr so the ConsoleMon thread doesnt hang

    //fd_mutex->release();
	//fclose( stdout );
    
    th.wait();
    
    SAFE_DELETE( fd_mutex );
#endif // __DISABLE_CONSOLEMONITOR__
    
	wxConfigBase * config = wxConfigBase::Get();

	int x, y;

	config->Write( _T( "/GUI/ConsoleMonitor/maximize" ), IsMaximized() );

	if( !IsMaximized() )
	{
		GetSize( &x, &y );
		config->Write( _T( "/GUI/ConsoleMonitor/width" ), x );
		config->Write( _T( "/GUI/ConsoleMonitor/height" ), y );

		GetPosition( &x, &y );
		config->Write( _T( "/GUI/ConsoleMonitor/x" ), x );
		config->Write( _T( "/GUI/ConsoleMonitor/y" ), y );
	}
    
	//SAFE_DELETE( text );
	//close( err_fd );
	//close( out_fd );
}

void mAConsoleMonitor::OnClose( wxCloseEvent & event )
{
	if( event.CanVeto() )
	{
		event.Veto();
		Hide();
	}
	else
		Destroy();
}

#define BUF_SIZE 8192
static char buf[BUF_SIZE];
void mAConsoleMonitor::OnDataAvailable( wxCommandEvent & event )
{
    int len = 0;
    
    text->Freeze();
    
	len = read( event.GetInt(), buf, BUF_SIZE );
    fd_mutex->release();
    
	if( len > 0 )
	{
		buf[len] = 0;
		text->AppendText( wxString( buf, wxConvUTF8 ) );
	}
    
    // chop out the beginning lines if there is too much data
    /*
    // currently disabled, as it slows down liniAudicle waaay too much
    t_CKUINT text_len = text->GetValue().Length();
	if( text_len > scrollback_size )
	{
		t_CKUINT cut = text_len - scrollback_size;
		while( text->GetRange( cut, cut + 1 ) != _T( "\n" ) )
			cut++;
		text->Remove( 0, cut + 1 );
	}*/
    
    text->SetInsertionPointEnd();
    
    text->Thaw();
}

#ifdef __LINUX__
/*
void mAConsoleMonitor::OnChar( wxKeyEvent & event )
{
    event.ResumePropagation( wxEVENT_PROPAGATE_MAX );
    //event.Skip();
    GetParent()->ProcessEvent( event );
    text->SetFocus();
}
*/
/*
void mAConsoleMonitor::SetMenuBar( mAMenuBar * menubar )
{
    if( this->menubar )
    {
        wxGetApp().RemoveMenuBar( menubar );
        PopEventHandler();
        SAFE_DELETE( menubar );
    }
    
    if( menubar )
    {
        this->menubar = menubar;
        PushEventHandler( menubar );
    }
}
*/
#endif

#if defined( __LINUX__ ) || defined( __WINDOWS_PTHREAD__ )
void * mAConsoleMonitor::callback( void * d )
#else
unsigned __stdcall mAConsoleMonitor::callback( void * d )
#endif
{
	if( d == NULL )
		return 0;

	mAConsoleMonitor * console_monitor = ( mAConsoleMonitor * ) d;
	
#ifndef __PLATFORM_WIN32__
	pollfd pfd[2];

	pfd[0].fd = console_monitor->err_fd;
	pfd[0].events = POLLIN;

	pfd[1].fd = console_monitor->out_fd;
	pfd[1].events = POLLIN;

	while( console_monitor->th_go )
	{
		poll( pfd, 2, -1 );
        
		while( poll( pfd, 2, 0 ) > 0 )
		{
			if( pfd[0].revents & POLLIN )
			{
			    /* For thread-safety, miniAudicle can only add data to the console 
                   monitor text buffer in the main thread, thus we send the console
                   monitor a wxCommandEvent that tells it to read a specific file
                   descriptor and append any available data to its wxTextCtrl.  The
                   mutex dance that is done here acts as a simple semaphore so that
                   this thread wont start polling again until the main thread is done
                   reading from the file descriptor (it will call release()).   */
                wxCommandEvent event( mAEVT_CONSOLE_DATA_AVAILABLE, pfd[0].fd );
                event.SetInt( pfd[0].fd );
                console_monitor->AddPendingEvent( event );
                console_monitor->fd_mutex->acquire();
			}

			if( pfd[1].revents & POLLIN )
			{
                wxCommandEvent event( mAEVT_CONSOLE_DATA_AVAILABLE, pfd[1].fd );
                event.SetInt( pfd[1].fd );
                console_monitor->AddPendingEvent( event );
                console_monitor->fd_mutex->acquire();
			}
		}
	}

#else
	FILE * f = _fdopen( console_monitor->out_fd, "r" );

	int len = 0;

	while( console_monitor->th_go )
	{		
		buf[0] = 0;

		if( ( len = _read( console_monitor->out_fd, buf, BUF_SIZE ) ) <= 0 )
			break;
		buf[len] = 0;
		console_monitor->text->AppendText( wxString( buf, wxConvUTF8 ) );
/*
		wxString text = console_monitor->text->GetValue();
		t_CKUINT len = text.Length();
		if( len > console_monitor->scrollback_size )
		{
			t_CKUINT cut = len - console_monitor->scrollback_size;
			while( console_monitor->text->GetRange( cut, cut + 1 ) != _T( "\n" ) )
				cut++;
			console_monitor->text->Freeze();
			console_monitor->text->Remove( 0, cut + 2 );
			console_monitor->text->Thaw();
		}
*/
		console_monitor->text->SetInsertionPointEnd();
	}

#endif /* __PLATFORM_WIN32__ */

	return 0;
}

