# redMine - project management software
# Copyright (C) 2006-2007  Jean-Philippe Lang
#
# 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.

require 'cgi'

module Redmine
  module Scm
    module Adapters    
      class CommandFailed < StandardError #:nodoc:
      end
      
      class AbstractAdapter #:nodoc:
        def initialize(url, root_url=nil, login=nil, password=nil)
          @url = url
          @login = login if login && !login.empty?
          @password = (password || "") if @login
          @root_url = root_url.blank? ? retrieve_root_url : root_url
        end
        
        def adapter_name
          'Abstract'
        end
        
        def supports_cat?
          true
        end
        
        def root_url
          @root_url
        end
      
        def url
          @url
        end
      
        # get info about the svn repository
        def info
          return nil
        end
        
        # Returns the entry identified by path and revision identifier
        # or nil if entry doesn't exist in the repository
        def entry(path=nil, identifier=nil)
          e = entries(path, identifier)
          e ? e.first : nil
        end
        
        # Returns an Entries collection
        # or nil if the given path doesn't exist in the repository
        def entries(path=nil, identifier=nil)
          return nil
        end
    
        def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
          return nil
        end
        
        def diff(path, identifier_from, identifier_to=nil, type="inline")
          return nil
        end
        
        def cat(path, identifier=nil)
          return nil
        end

        def with_leading_slash(path)
          path ||= ''
          (path[0,1]!="/") ? "/#{path}" : path
        end
        
        def shell_quote(str)
          if RUBY_PLATFORM =~ /mswin/
            '"' + str.gsub(/"/, '\\"') + '"'
          else
            "'" + str.gsub(/'/, "'\"'\"'") + "'"
          end
        end
              
      private
        def retrieve_root_url
          info = self.info
          info ? info.root_url : nil
        end
        
        def target(path)
          path ||= ""
          base = path.match(/^\//) ? root_url : url    
          " \"" << "#{base}/#{path}".gsub(/["?<>\*]/, '') << "\""
        end
            
        def logger
          RAILS_DEFAULT_LOGGER
        end
      
        def shellout(cmd, &block)
          logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
          IO.popen(cmd, "r+") do |io|
            io.close_write
            block.call(io) if block_given?
          end
        end  
      end
      
      class Entries < Array
        def sort_by_name
          sort {|x,y| 
            if x.kind == y.kind
              x.name <=> y.name
            else
              x.kind <=> y.kind
            end
          }   
        end
        
        def revisions
          revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
        end
      end
      
      class Info
        attr_accessor :root_url, :lastrev
        def initialize(attributes={})
          self.root_url = attributes[:root_url] if attributes[:root_url]
          self.lastrev = attributes[:lastrev]
        end
      end
      
      class Entry
        attr_accessor :name, :path, :kind, :size, :lastrev
        def initialize(attributes={})
          self.name = attributes[:name] if attributes[:name]
          self.path = attributes[:path] if attributes[:path]
          self.kind = attributes[:kind] if attributes[:kind]
          self.size = attributes[:size].to_i if attributes[:size]
          self.lastrev = attributes[:lastrev]
        end
        
        def is_file?
          'file' == self.kind
        end
        
        def is_dir?
          'dir' == self.kind
        end
        
        def is_text?
          Redmine::MimeType.is_type?('text', name)
        end
      end
      
      class Revisions < Array
        def latest
          sort {|x,y|
            unless x.time.nil? or y.time.nil?
              x.time <=> y.time
            else
              0
            end
          }.last
        end 
      end
      
      class Revision
        attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
        def initialize(attributes={})
          self.identifier = attributes[:identifier]
          self.scmid = attributes[:scmid]
          self.name = attributes[:name] || self.identifier
          self.author = attributes[:author]
          self.time = attributes[:time]
          self.message = attributes[:message] || ""
          self.paths = attributes[:paths]
          self.revision = attributes[:revision]
          self.branch = attributes[:branch]
        end
    
      end
    
      # A line of Diff
      class Diff  
        attr_accessor :nb_line_left
        attr_accessor :line_left
        attr_accessor :nb_line_right
        attr_accessor :line_right
        attr_accessor :type_diff_right
        attr_accessor :type_diff_left
        
        def initialize ()
          self.nb_line_left = ''
          self.nb_line_right = ''
          self.line_left = ''
          self.line_right = ''
          self.type_diff_right = ''
          self.type_diff_left = ''
        end
    
        def inspect
          puts '### Start Line Diff ###'
          puts self.nb_line_left
          puts self.line_left
          puts self.nb_line_right
          puts self.line_right
        end
      end
    
      class DiffTableList < Array  
        def initialize (diff, type="inline")
            diff_table = DiffTable.new type
            diff.each do |line|
                if line =~ /^(---|\+\+\+) (.*)$/
                    self << diff_table if diff_table.length > 1
                    diff_table = DiffTable.new type
                end
                a = diff_table.add_line line
            end
            self << diff_table
        end
      end
    
      # Class for create a Diff
      class DiffTable < Hash  
        attr_reader :file_name, :line_num_l, :line_num_r    
    
        # Initialize with a Diff file and the type of Diff View
        # The type view must be inline or sbs (side_by_side)
        def initialize (type="inline")
          @parsing = false
          @nb_line = 1
          @start = false
          @before = 'same'
          @second = true
          @type = type
        end
    
        # Function for add a line of this Diff
        def add_line(line)
          unless @parsing
            if line =~ /^(---|\+\+\+) (.*)$/
              @file_name = $2
              return false
            elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
              @line_num_l = $5.to_i
              @line_num_r = $2.to_i
              @parsing = true
            end
          else
            if line =~ /^[^\+\-\s@\\]/
              self.delete(self.keys.sort.last)
              @parsing = false
              return false
            elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
              @line_num_l = $5.to_i
              @line_num_r = $2.to_i
            else
              @nb_line += 1 if parse_line(line, @type)          
            end
          end
          return true
        end
    
        def inspect
          puts '### DIFF TABLE ###'
          puts "file : #{file_name}"
          self.each do |d|
            d.inspect
          end
        end
    
      private  
        # Test if is a Side By Side type
        def sbs?(type, func)
          if @start and type == "sbs"
            if @before == func and @second
              tmp_nb_line = @nb_line
              self[tmp_nb_line] = Diff.new
            else
                @second = false
                tmp_nb_line = @start
                @start += 1
                @nb_line -= 1
            end
          else
            tmp_nb_line = @nb_line
            @start = @nb_line
            self[tmp_nb_line] = Diff.new
            @second = true
          end
          unless self[tmp_nb_line]
            @nb_line += 1
            self[tmp_nb_line] = Diff.new
          else
            self[tmp_nb_line]
          end
        end
    
        # Escape the HTML for the diff
        def escapeHTML(line)
            CGI.escapeHTML(line)
        end
    
        def parse_line (line, type="inline")
          if line[0, 1] == "+"
            diff = sbs? type, 'add'
            @before = 'add'
            diff.line_left = escapeHTML line[1..-1]
            diff.nb_line_left = @line_num_l
            diff.type_diff_left = 'diff_in'
            @line_num_l += 1
            true
          elsif line[0, 1] == "-"
            diff = sbs? type, 'remove'
            @before = 'remove'
            diff.line_right = escapeHTML line[1..-1]
            diff.nb_line_right = @line_num_r
            diff.type_diff_right = 'diff_out'
            @line_num_r += 1
            true
          elsif line[0, 1] =~ /\s/
            @before = 'same'
            @start = false
            diff = Diff.new
            diff.line_right = escapeHTML line[1..-1]
            diff.nb_line_right = @line_num_r
            diff.line_left = escapeHTML line[1..-1]
            diff.nb_line_left = @line_num_l
            self[@nb_line] = diff
            @line_num_l += 1
            @line_num_r += 1
            true
          elsif line[0, 1] = "\\"
            true
          else
            false
          end
        end
      end
    end
  end
end