require File.join(File.dirname(__FILE__), "base.rb")

class BuildWise::SCM::Git < BuildWise::SCM::Base

  def installed?
    exec_successful? "#{@config.bin_path}git --version"
  end

  def update!
    if test(?d, File.join(@path, '.git'))
      get_updates
      execute("reset", "--hard #{remote_head}") # if @config.scm_force_reset]
      branch = @config.scm_branch
      if branch
        puts "Checking out branch: #{branch}"
        execute("checkout", "#{branch}")

      end

    else
      initial_checkout

      execute('config', 'pager.diff false') # turn off git-diff $PAGER by default
      if branch = @config.scm_branch
        execute('branch', "--track #{branch} #{remote_head}")
        execute('checkout', branch)
      end
    end
  end

  def initial_checkout
    puts "[INFO] #{Time.now} Cloning ... #{@path}"




    
    if @config.scm_url.nil?
      puts "[ERROR] SCM not configured"
      return
    end
    
    FileUtils.rm_rf(@path) if test(?d, @path)    
    
    encoded_url = (@config.scm_url.include?(' ') ? "\"#{@config.scm_url}\"" : @config.scm_url)
    if RUBY_PLATFORM =~ /mingw/
      @status = execute("clone", "#{encoded_url} \"#{@path}\"", false)
    else
      @status = execute("clone", "#{encoded_url} #{@path}", false)
    end
    puts "[INFO] #{Time.now} Cloned at #{@path} #{@status}"

    unless @status


      manual_command = "git clone #{encoded_url} #{@path}"
      puts "==> #{manual_command}"
      system(manual_command)
    end

    return @status
  end

  def has_changes?
    extract_current_head_revision
    puts "#{Time.now} [DEBUG] check Git changes, current Revision: #{@revision} | last tested: #{last_tested_revision} | new: #{new?}"
    new? or (last_tested_revision != @revision)
  end

  def new?
    @new == true
  end

  def current_revision(_full=false)
    begin
      _full ? @revision : @revision.slice(0, 8)
    rescue
      @revision
    end
  end

  def last_commit_message
    @message
  end

  def last_commit_date
    @date
  end

  def last_author
    @author
  end

  def output
    @status
  end

  def determine_changesets(log_file)
    last_good_build_at = last_successful_build_timestamp

    begin
      if last_good_build_at.nil?
        last_good_build_at = "1 week ago" # HACK
      else
        last_good_build_at = last_good_build_at.strftime("%Y-%m-%d %H:%M:%S")
      end
    rescue => e
      puts "[!!!] Error on convert last build at: #{e}"
      last_good_build_at = "1 week ago" # HACK
    end
    puts "#{Time.now} [DEBUG] Git determining change sets: --stat --since=\"#{last_good_build_at}\" > #{log_file}"
    execute('log', "--stat --since=\"#{last_good_build_at}\" > \"#{log_file}\"") # turn off git-diff $PAGER by default
  end

  def last_commit_changed_files
    scm_output = execute('log', "--stat -1")
    changed_files = []
    scm_output.split("\n").each do |line|
      if line =~ /^\s+([\w|\.|\/]*)\s*\|\s*\d+\s*[+-]*\s*$/
        changed_files << $1
      end
    end
    return changed_files.join(",")
  end

  def url
    if info && info["origin"]
      remote_url = info["origin"]
      if remote_url
        return remote_url
      end
    end
    return @path
  end




  def changed_files_since(revision_or_num_checkins)
    if  revision_or_num_checkins && revision_or_num_checkins.to_s =~ /^\d+$/
      scm_output = execute('diff', "--name-only HEAD~#{revision_or_num_checkins} HEAD") 
    else
      scm_output = execute('diff', "--name-only #{revision_or_num_checkins} HEAD") 
    end
    return scm_output
  end
  
  private

  def info

    unless @info


      @info = {}
      output = execute("remote", "-v")
      output.split("\n").each do |line|
        if line =~ /(\w+)\s+(.*)\s+\(fetch\)$/
          @info[$1] = $2
        end
      end
    end


    @info
  end

  def get_updates
    execute("fetch")
  end

  def remote_head
    branch = @config.scm_branch
    branch ? "origin/#{branch}" : "origin"
  end

  def execute(command, parameters = nil, with_path = true)
    current_working_dir = File.expand_path(".")
    if with_path
      root_path = File.expand_path(@config.application_root) if @config.application_root

      if root_path.nil?
        root_path = File.expand_path(@path)
        puts "[INFO] application_root.nil, use => #{root_path} | path => #{@path}"
      end

      if RUBY_PLATFORM =~ /mingw/ then
        FileUtils.chdir(root_path) if root_path && File.exist?(root_path)
        cmd = "#{@config.bin_path}git --git-dir=\"#{@path}/.git\" #{command} #{parameters}"
      else
        cmd = "cd #{root_path} && #{@config.bin_path}git --git-dir=#{@path}/.git #{command} #{parameters}"
      end
    else
      cmd = "#{@config.bin_path}git #{command} #{parameters}"
    end
    puts "#{Time.now} [INFO] Execute Git Command => #{cmd}"
    output = `#{cmd}`

    FileUtils.chdir(File.expand_path(current_working_dir)) if RUBY_PLATFORM =~ /mingw/
    return output
  end

  def extract_commit_info(commit=remote_head)
    the_commit_info = {}
    
    $scm_cache ||= {}
    if $scm_cache[commit]
      return $scm_cache[commit]
    end
    

    if false && @g

      if revision.nil? || revision == "master"
        the_revision = nil
        if origin
          the_commit = @g.branches[origin + "/" + the_branch].gcommit
          the_revision = the_commit.sha
        else
          the_commit = @g.branches[the_branch].gcommit
          the_revision = the_commit.sha
        end

        the_commit_info = {:author => the_commit.author.name, :date => the_commit.date, :revision => the_revision, :message => the_commit.message}
      else
        begin
          the_commit = @g.gcommit(revision)
        rescue => e
          $logger.warn("failed to latest commit: #{e}, revert to command line")
          begin

            git_log_format = "%an|%ad|%H|%s"
            message = String.new.respond_to?(:force_encoding) ?
                execute("log", "#{ commit } -1 --pretty=format:\"#{git_log_format}\"").force_encoding('utf-8').split("|") :
                execute("log", "#{ commit } -1 --pretty=format:\"#{git_log_format}\"").split("|")

            return {:author => message[0], :date => message[1], :revision => message[2], :message => message[3]}
          rescue => e
            puts "Error on extract commit info: #{e}"
            return {}
          end

        end
        the_revision = the_commit.sha
        the_commit_info = {:author => the_commit.author.name, :date => the_commit.date, :revision => the_revision, :message => the_commit.message}
      end

    else

      begin


        git_log_format = "%an|%ad|%H|%s"
        message = String.new.respond_to?(:force_encoding) ?
            execute("log", "#{ commit } -1 --pretty=format:\"#{git_log_format}\"").force_encoding('utf-8').split("|") :
            execute("log", "#{ commit } -1 --pretty=format:\"#{git_log_format}\"").split("|")

        the_commit_info =  {:author => message[0], :date => message[1], :revision => message[2], :message => message[3]}
        $scm_cache[commit] = the_commit_info
        return the_commit_info
      rescue => e
        puts "Error on extract commit info: #{e}"
        return {}
      end

    end

    puts "[DEBUG] [GIT] extract_commit_info: #{the_commit_info.inspect}"
    return the_commit_info

  end

  def last_tested_revision

    app_name = @config.application_name
    app_root = "#{BUILDWISE_HOME}/work/#{app_name}"
    status = BuildWise::Status.new("#{app_root}/status.log")
    commit_info = extract_commit_info(status.revision)
    @last_tested_revision ||= commit_info[:revision]
  end

  def last_successful_build_timestamp

    app_name = @config.application_name
    app_root = "#{BUILDWISE_HOME}/work/#{app_name}"
    status = BuildWise::Status.new("#{app_root}/status.log")
    commit_info = extract_commit_info(status.revision)
    begin
      @last_successful_build_timestamp = status.successful_build_timestamp
    rescue => e
      puts "[WARN] Error on determining Git last successful build timestamp => #{e}"
    end
    return @last_successful_build_timestamp
  end


  def extract_current_head_revision
    commit_info = extract_commit_info
    @author = commit_info[:author]
    @date = commit_info[:date]
    @revision = commit_info[:revision]
    @message = commit_info[:message]
  end
end