#*******************************************************************************
# 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 CONTRIBUTORS "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.
#*******************************************************************************

#*******************************************************************************
#* Ruby interface to the Perforce SCM System
#* Tony Smith <tony@perforce.com> or <tony@smee.org>
#*******************************************************************************

#*******************************************************************************
#* Supporting classes
#*******************************************************************************

#
# P4Integration objects hold details about the integrations that have
# been performed on a particular revision. Used primarily with the
# P4Revision class
#
class P4Integration
  def initialize( how, file, srev, erev )
    @how = how
    @file = file
    @srev = srev
    @erev = erev
  end

  attr_reader :how, :file, :srev, :erev
end

#
# Each P4Revision object holds details about a particular revision
# of a file. It may also contain the history of any integrations 
# to/from the file
#

class P4Revision
  def initialize( depotFile )
    @depot_file = depotFile
    @integrations = Array.new
    @revno = @change = @action = @type = @time = @user = @client = @desc = nil
  end

  attr_reader :depot_file

  attr_accessor :revno, :change, :action, :type, 
		:time, :user, :client, :desc, :integrations

  def integration( how, file, srev, erev )
    rec = P4Integration.new( how, file, srev, erev )
    @integrations.push( rec )
    return rec
  end

  def each_integration
    @integrations.each { |i| yield( i ) }
  end
end

#
# Each DepotFile entry contains details about one depot file. 
# 
class P4DepotFile
  def initialize( name )
    @depot_file = name
    @revisions = Array.new
    @headAction = @head_type = @head_time = @head_rev = @head_change = nil
  end

  attr_reader :depot_file, :revisions
  attr_accessor :head_action, :head_type, :head_time, :head_rev, :head_change

  def new_revision
    r = P4Revision.new( @depot_file )
    @revisions.push( r )
    return r
  end

  def each_revision
    @revisions.each { |r| yield( r ) }
  end
end


#*******************************************************************************
#* P4 class
#*******************************************************************************

#
# Get the bulk of the definition of the P4 class from the API interface
#
require "P4.so"

#
# Add the extra's written purely in ruby.
#
class P4
  def method_missing( m, *a )
    if ( m.to_s =~ /^run_(.*)/ )
      return self.run( $1, a )
    elsif ( m.to_s =~ /^fetch_(.*)/ )
      return self.run( $1, "-o", a ).shift
    elsif ( m.to_s =~ /^save_(.*)/ )
      if ( a.length == 0 )
	raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller)
      else
	self.input( a.shift )
	return self.run( $1, "-i", a )
      end
    elsif ( m.to_s =~ /^delete_(.*)/ )
      if ( a.length == 0 )
	raise( P4Exception, "Method P4##{m.to_s} requires an argument", caller)
      else
	return self.run( $1, "-d", a )
      end
    else
      raise NameError, "No such method #{m.to_s} in class P4", caller
    end
  end

  # Aliases
  alias cwd		cwd=
  alias client		client=
  alias host		host=
  alias port		port=
  alias user		user=

  alias debug 		debug=
  alias exception_level	exception_level=

  # 
  # Simple interface for submitting. Just supply the change form either
  # as a text string, or as the edit hash from a previous p4.fetch_change()
  # or p4.run_change( "-o" ).
  #
  def submit_spec( spec )
    self.input( spec )
    return self.run_submit( "-i" )
  end

  #
  # The following methods convert the standard output of some common
  # Perforce commands into more structured form to make using the
  # data easier.  
  #  
  # (Currently only run_filelog is defined. More to follow)
  

  #
  # run_filelog: convert "p4 filelog" responses into objects with useful
  #              methods
  #
  # Requires tagged output to be of any real use. If tagged output it not 
  # enabled then you just get the raw data back
  #
  def run_filelog( *args )
    raw = self.run( 'filelog', args.flatten )
    raw.collect do
      |h|
      if ( ! h.kind_of?( Hash ) )
	h
      else
	df = P4DepotFile.new( h[ "depotFile" ] )
	h[ "rev" ].each_index do
	  |n|
	
	  # If rev is nil, there's nothing here for us
	  next unless h[ "rev" ][ n ]

	  # Create a new revision of this file ready for populating
	  r = df.new_revision

	  # Populate the base attributes of each revision
	  r.revno = h[ "rev" ][ n ].to_i
	  r.change = h[ "change" ][ n ].to_i
	  r.action = h[ "action" ][ n ]
	  r.type = h[ "type" ][ n ]
	  r.time = Time.at( h[ "time" ][ n ].to_i )
	  r.user = h[ "user" ][ n ]
	  r.client = h[ "client" ][ n ]
	  r.desc = h[ "desc" ][ n ]

	  # Now if there are any integration records for this revision,
	  # add them in too
	  next unless ( h[ "how" ] )
	  next unless ( h[ "how" ][ n ] )

	  h[ "how" ][ n ].each_index do
	    |m|
	    how = h[ "how" ][ n ][ m ]
	    file = h[ "file" ][ n ][ m ]
	    srev = h[ "srev" ][ n ][ m ]
	    erev = h[ "erev" ][ n ][ m ]
	    srev.gsub!( /^#/, "" )
	    erev.gsub!( /^#/, "" )
	    srev = ( srev == "none" ? 0 : srev.to_i )
	    erev = ( erev == "none" ? 0 : erev.to_i )

	    r.integration( how, file, srev, erev )
	  end
	end
	df
      end
    end
  end
end
