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

Copyright (c) 1997-2004, Perforce Software, Inc.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1.  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTR
IBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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

/*******************************************************************************
 * Name		: p4.cc
 *
 * Author	: Tony Smith <tony@perforce.com> or <tony@smee.org>
 *
 * Description	: Ruby bindings for the Perforce API. 
 *
 ******************************************************************************/
#include <ruby.h>

#if defined ( OS_NT ) && defined( SetPort )
   // Stupid "#define SetPort SetPortA" in winspool.h
#  undef SetPort
#endif

#include <clientapi.h>
#include <spec.h>
#include "p4rb_version.h"
#include "p4result.h"
#include "clientuserruby.h"
#include "p4clientapi.h"
#include "extconf.h"


/*******************************************************************************
 * Utility class for self identification
 ******************************************************************************/

class Ident : public StrBuf
{
    public:
	Ident( const char *fmt ) { this->fmt = fmt; }

	void AddVar( const char *name, const char *value )
	{
	    char buf[ 256 ];
	    sprintf( buf, fmt, name, value );
	    Append( buf );
	}

	VALUE ToValue()
	{
	    return rb_str_new2( Text() );
	}

    private:
	const	char *fmt;
};

/*******************************************************************************
 * Our Ruby classes.
 ******************************************************************************/
VALUE 	cP4;	// Base P4 Class
VALUE	eP4;	// Exception class


extern "C"
{

//
// Construction/destruction
//

static void p4_free( P4ClientApi *p4 )
{
    delete p4;
}

static void p4_mark( P4ClientApi *p4 )
{
    p4->GCMark();
}

static VALUE p4_new( VALUE pClass )
{
    VALUE  	argv[ 1 ];
    P4ClientApi	*p4 = new P4ClientApi();
    VALUE	self;

    self = Data_Wrap_Struct( pClass, p4_mark, p4_free, p4 );
    rb_obj_call_init( self, 0, argv );
    return self;
}


//
// Session connect/disconnect
//
static VALUE p4_connect( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return p4->Connect();
}

static VALUE p4_disconnect( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return p4->Disconnect();
}


// 
// Protocol settings
//
static VALUE p4_set_tagged( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->Tagged();
    return Qtrue;
}

static VALUE p4_set_parse_forms( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->ParseForms();
    return Qtrue;
}

//
// Getting/Setting Perforce environment
//
static VALUE p4_get_cwd( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    StrPtr cwd = p4->GetCwd();
    return rb_str_new2( cwd.Text() );
}

static VALUE p4_set_cwd( VALUE self, VALUE cwd )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetCwd( STR2CSTR( cwd ) );
    return Qtrue;
}

static VALUE p4_get_client( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    StrPtr client = p4->GetClient();
    return rb_str_new2( client.Text() );
}

static VALUE p4_set_client( VALUE self, VALUE client )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetClient( STR2CSTR( client ) );
    return Qtrue;
}

static VALUE p4_get_host( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    StrPtr host = p4->GetHost();
    return rb_str_new2( host.Text() );
}

static VALUE p4_set_host( VALUE self, VALUE host )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetHost( STR2CSTR( host ) );
    return Qtrue;
}

static VALUE p4_get_password( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    StrPtr passwd = p4->GetPassword();
    return rb_str_new2( passwd.Text() );
}

static VALUE p4_set_password( VALUE self, VALUE passwd )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetPassword( STR2CSTR( passwd ) );
    return Qtrue;
}

static VALUE p4_get_port( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    StrPtr port = p4->GetPort();
    return rb_str_new2( port.Text() );
}

static VALUE p4_set_port( VALUE self, VALUE port )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetPort( STR2CSTR( port ) );
    return Qtrue;
}

static VALUE p4_get_user( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    StrPtr user = p4->GetUser();
    return rb_str_new2( user.Text() );
}

static VALUE p4_set_user( VALUE self, VALUE user )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetUser( STR2CSTR( user ) );
    return Qtrue;
}

/*******************************************************************************
 * Running commands.  General purpose Run method and method for supplying
 * input to "p4 xxx -i" commands
 ******************************************************************************/

static VALUE p4_run( VALUE self, VALUE args )
{
    int 	i;
    int		argc = 0;
    ID		idFlatten = rb_intern( "flatten" );
    ID		idLength = rb_intern( "length" );
    ID		idTo_S	 = rb_intern( "to_s" );

    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );

    // Flatten the args array, and extract the Perforce command leaving
    // the remaining args in the array.
    VALUE flatArgs = rb_funcall( args, idFlatten, 0 );

    if ( ! NUM2INT( rb_funcall( flatArgs, idLength, 0 ) ) )
	rb_raise( eP4, "P4#run requires an argument" );

    char *cmd = STR2CSTR( rb_funcall( flatArgs, rb_intern( "shift" ), 0 ) );
    argc = NUM2INT( rb_funcall( flatArgs, idLength, 0 ) );

    // Allocate storage on the stack so it's automatically reclaimed 
    // when we exit.
    char **p4args = ALLOCA_N( char *, argc + 1 );

    // Copy the args across
    for ( i = 0; i < argc; i++ )
    {
	VALUE	entry = rb_ary_entry( flatArgs, i );
	p4args[ i ] = STR2CSTR( rb_funcall( entry, idTo_S, 0 ) );
    }
    p4args[ i ] = 0;

    // Run the command
    VALUE res =  p4->Run( cmd, argc, p4args );
    return res;
}

static VALUE p4_set_input( VALUE self, VALUE input )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return p4->SetInput( input );
}

static VALUE p4_get_output( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return p4->GetOutput();
}

static VALUE p4_get_errors( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return p4->GetErrors();
}

static VALUE p4_get_warnings( VALUE self )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return p4->GetWarnings();
}

static VALUE p4_set_except_level( VALUE self, VALUE level )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->ExceptionLevel( NUM2INT(level) );
    return level;
}

static VALUE p4_get_except_level( VALUE self )
{
    int		level;
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    return INT2NUM( p4->ExceptionLevel() );
}

/*******************************************************************************
 * Self identification
 ******************************************************************************/
static VALUE p4_identify( VALUE self )
{
    Ident	ident( "%24s : %s\n" ); 

    ident.Append( "\nP4/Ruby Build Summary:\n\n" );
    ident.AddVar( "Author","Tony Smith <tony@smee.org> or <tony@perforce.com>");
    ident.AddVar( "Version", P4RUBY_VERSION );
    ident.AddVar( "Perforce API Version", P4APIVER );
    ident.AddVar( "CFLAGS", P4CFLAGS );
    ident.AddVar( "LIBS", P4LIBS );
    ident.AddVar( "LIBPATH", P4LIBPATH );
    ident.Append( "\n" );
    return ident.ToValue();
}

/*******************************************************************************
 * Debugging support
 ******************************************************************************/
static VALUE p4_debug( VALUE self, VALUE debug )
{
    P4ClientApi	*p4;
    Data_Get_Struct( self, P4ClientApi, p4 );
    p4->SetDebug( NUM2INT(debug) );
    return Qtrue;
}

/******************************************************************************
 * Extension initialisation
 ******************************************************************************/

void	Init_P4()
{
    // Ruby instantiation
    eP4 = rb_define_class( "P4Exception", rb_eRuntimeError );

    cP4 = rb_define_class( "P4", rb_cObject );
    rb_define_singleton_method( cP4, "new", RUBY_METHOD_FUNC(p4_new), 0 );
    
    // Protocol options
    rb_define_method( cP4, "tagged",	RUBY_METHOD_FUNC(p4_set_tagged), 0 );
    rb_define_method( cP4, "parse_forms", RUBY_METHOD_FUNC(p4_set_parse_forms), 0);

    // Perforce client settings. Two sets of interfaces. One with
    // parameter passing, and one as assignment. e.g.
    //
    //  	p4.port( "localhost:1666" )
    //  or 
    //  	p4.port = "localhost:1666" 
    // 
    // the latter is preferred.
    //
    rb_define_method( cP4, "cwd=", 	RUBY_METHOD_FUNC(p4_set_cwd)     , 1 );
    rb_define_method( cP4, "client=", 	RUBY_METHOD_FUNC(p4_set_client)  , 1 );
    rb_define_method( cP4, "host=", 	RUBY_METHOD_FUNC(p4_set_host)    , 1 );
    rb_define_method( cP4, "password=", RUBY_METHOD_FUNC(p4_set_password), 1 );
    rb_define_method( cP4, "port=", 	RUBY_METHOD_FUNC(p4_set_port)    , 1 );
    rb_define_method( cP4, "user=", 	RUBY_METHOD_FUNC(p4_set_user)    , 1 );

    rb_define_method( cP4, "cwd?", 	RUBY_METHOD_FUNC(p4_get_cwd)     , 0 );
    rb_define_method( cP4, "client?", 	RUBY_METHOD_FUNC(p4_get_client)  , 0 );
    rb_define_method( cP4, "host?", 	RUBY_METHOD_FUNC(p4_get_host)    , 0 );
    rb_define_method( cP4, "password?", RUBY_METHOD_FUNC(p4_get_password), 0 );
    rb_define_method( cP4, "port?", 	RUBY_METHOD_FUNC(p4_get_port)    , 0 );
    rb_define_method( cP4, "user?", 	RUBY_METHOD_FUNC(p4_get_user)    , 0 );

    // Session Connect/Disconnect
    rb_define_method( cP4, "connect", 	RUBY_METHOD_FUNC(p4_connect)     , 0 );
    rb_define_method( cP4, "disconnect", RUBY_METHOD_FUNC(p4_disconnect) , 0 );

    // Running commands - general purpose commands
    rb_define_method( cP4, "run", 	RUBY_METHOD_FUNC(p4_run)         ,-2 );
    rb_define_method( cP4, "input", 	RUBY_METHOD_FUNC(p4_set_input)   , 1 );
    rb_define_method( cP4, "output", 	RUBY_METHOD_FUNC(p4_get_output)  , 0 );
    rb_define_method( cP4, "errors", 	RUBY_METHOD_FUNC(p4_get_errors)  , 0 );
    rb_define_method( cP4, "warnings",	RUBY_METHOD_FUNC(p4_get_warnings), 0 );
    rb_define_method( cP4, "exception_level?", RUBY_METHOD_FUNC(p4_get_except_level), 0 );
    rb_define_method( cP4, "exception_level=", RUBY_METHOD_FUNC(p4_set_except_level), 1 );

    // Identification
    rb_define_const( cP4, "VERSION", rb_str_new2( P4RUBY_VERSION ) );
    rb_define_singleton_method( cP4, "identify", RUBY_METHOD_FUNC(p4_identify), 0 );

    // Debugging support
    rb_define_method( cP4, "debug=", RUBY_METHOD_FUNC(p4_debug), 1 );
}


} // Extern C
