require 'active_support/core_ext/time'
require 'pstore'

Buildwise::App.controllers :projects do
  


  get '/' do

    start_time = Time.now
    cache_key { request.path_info + "?" + params.slice('last_build_time').to_param + "&" + params.slice('sort_direction').to_param }
    expires 10

    if defined?(SERVER_DIGEST)
      @projects = Project.where(:server_digest => SERVER_DIGEST).where("active = ?", true)
    else
      @projects = Project.where("active = ?", true)
    end    
        
    @sort_column = session[:sort_column]
    session[:sort_column] =  @sort_column = params[:sort] if params[:sort]        
    @sort_direction = session[:sort_direction]    
    session[:sort_direction] = @sort_direction = params[:sort_direction] if params[:sort_direction] 

    @sort_column ||= "last_build_time"
    @sort_direction ||= "DESC"        

    @all_projects = @projects = @projects.order("#{@sort_column} #{@sort_direction}")

    @project_per_page = settings.num_projects_per_page.to_i  || 7
    
    @projects = @projects.limit(@project_per_page).offset(0)
    @offset = @projects.count 
    

    @autorefresh = false
    @refresh_interval =  @projects.any? { |x| x.is_building? }  ? 60 : 30  if @autorefresh
    logger.debug("[Benchmark] Home page: #{Time.now - start_time}s") #very fast here, but slow in rendering
    render "/projects/index".to_sym
  end

  get '/load_more' do
    if defined?(SERVER_DIGEST)
      @projects = Project.where(:server_digest => SERVER_DIGEST).where("active = ?", true)
    else
      @projects = Project.where("active = ?", true)
    end    
    
    @sort_column = session[:sort_column]
    @sort_direction = session[:sort_direction]        
    @sort_column ||= "last_build_time"
    @sort_direction ||= "DESC"       

    @projects = @projects.order("#{@sort_column} #{@sort_direction}")

    @project_per_page = settings.num_projects_per_page.to_i  || 7    
    @offset = params[:offset].to_i rescue @project_per_page
    @projects = @projects.limit(@project_per_page).offset(@offset)    
    
    @offset += @projects.count    
    render "/projects/load_more.js".to_sym, :layout => false      
  end
  



  post '/' do
    authenticate!    
    Buildwise::App.cache.clear
    
    the_param_project_dir = params[:project_dir] rescue nil
    the_param_project_dir = File.expand_path(the_param_project_dir) if the_param_project_dir
    
    if params[:identifier].blank?
		  @error = "You must specify project identifier"	
   		redirect "/projects/new?error=#{@error}"
      return
    elsif Project.retrieve_by_identifier( params[:identifier] ).first
		  @error = "The project identifier '#{params[:identifier]}' already exists."	
   		redirect "/projects/new?error=#{@error}"
      return      
    elsif the_param_project_dir && !Dir.exist?(the_param_project_dir)
		  @error = "The project working dir: #{ the_param_project_dir } does not exist"	
   		redirect "/projects/new?error=#{@error}"      
    elsif the_param_project_dir && !Dir.exist?(File.join(the_param_project_dir, ".git")) 
		  @error = "The project working dir: #{ the_param_project_dir } does not have a Git repository."	
   		redirect "/projects/new?error=#{@error}"      
    end
    
    params["retry_on_another_machine"] = "false" unless params["retry_on_another_machine"] 
    
    project_dir = params[:project_dir]
    project_dir = File.expand_path(project_dir) if project_dir
    if project_dir && File.exist?(project_dir.strip)
      

        options = {:application_name => params[:identifier]}
        
        if Project.retrieve_by_identifier(params[:identifier]).first 

          flash[:alert] = "The project '#{params[:identifier]}' already existed in database"
          redirect "/"
          return
        end
              
        options[:builder] = {}
        if params[:custom_build_steps_json] && params[:custom_build_steps_json].strip.start_with?("[")
          
          options[:builder]["steps_json"] = params[:custom_build_steps_json].strip
          
        else
          
          if params[:main_testing_step_task]
            options[:builder]["main_testing_step_task"] = params[:main_testing_step_task]          
          elsif params[:rake_task]
            options[:builder]["main_testing_step_task"] = options[:builder]["task"] = params[:rake_task]
  				elsif params[:ui_test_task]
            options[:builder]["main_testing_step_task"] = options[:builder]["ui_test_task"] = params[:ui_test_task]
          elsif params[:load_test_task]
            options[:builder]["main_testing_step_task"] = options[:builder]["load_test_task"] = params[:load_test_task]
          end           
        
          if params["main_testing_step_framework"]
            options[:builder]["main_testing_step_framework"] = params["main_testing_step_framework"]
          else           
            options[:builder]["ui_test_framework"] = params["ui_test_task_framework"] if params["ui_test_task_framework"]
            options[:builder]["main_testing_step_framework"] =  options[:builder]["ui_test_framework"]
          end
        
          if params["main_testing_step_name"]
            options[:builder]["main_testing_step_name"] = params["main_testing_step_name"]
           else
            options[:builder]["main_testing_step_name"] = "UI Test"
          end
        
        end
        
				options[:scm] = { "user_name" => params[:scm_user], "password" => params[:scm_pass] } if params[:scm_user]

        if params[:project_template] && params[:project_template] == "Ruby on Rails"
          options[:builder]["migrate_database_task"] = params[:migrate_database_task] if params[:migrate_database_task]
          options[:builder]["unit_test_task"] = params[:unit_test_task] if params[:unit_test_task]
        end
        
        if params[:builder_prepare_command] && !params[:builder_prepare_command].blank?
          options[:builder]["prepare_command"] = params[:builder_prepare_command]
        end
        

        options[:ui_tests] ||= {}
        options[:ui_tests]["test_dir"] = params[:ui_test_dir] if params[:ui_test_dir]
        options[:ui_tests]["report_dir"] = params[:ui_test_report_dir] if params[:ui_test_report_dir]

        project_dir.strip!

        scm = determine_scm(project_dir)
        if scm.nil?
          @error = "No supported SCM not found in #{project_dir}"
          redirect "/projects/new?error=#{@error}"
          return
        end

        command = BuildWise::AddCommand.new(project_dir, options)
        begin          
          command.run        
        rescue => e
          logger.error("Failed to create project: #{e}")
          @error = e.message
          redirect "/projects/new?error=#{@error}"
          erb "/projects/new".to_sym
          return
        end

        project_identifier = params[:identifier]
        project_name = params[:name]
        Project.load_projects_from_working_dir([project_identifier], [project_name])  
        
        @project = Project.retrieve_by_identifier(params[:identifier]).first
        
        if @project.nil?
          redirect "/projects/new?error=#{@error}"
          erb "/projects/new".to_sym
          return
        end
        
        project_config = @project.config
        project_config.environment_variables["BUILDWISE_MASTER"] ||= root_url
        project_config.environment_variables["DISTRIBUTED_MAX_BUILD_TIME"] ||= 3600
        project_config.environment_variables["DISTRIBUTED_BUILD_CHECK_INTERVAL"] ||= 15
        
        if params[:is_performance_testing] && params[:is_performance_testing].to_s == "true"
          project_config.is_parallel = false
          project_config.is_performance_testing = true
        end
        
        if params[:is_load_testing] && params[:is_load_testing].to_s == "true"
          project_config.is_parallel = true
          project_config.is_load_testing = true
          
          project_config.app_name = params[:app_name] rescue "AgileTravel-Load"
          project_config.agent_work_dir = project_dir
          project_config.load_max_iteration_count =  params[:load_max_iteration_count] if params[:load_max_iteration_count]
          project_config.load_server_url =  params[:load_server_url] if params[:load_server_url]
          project_config.load_max_agent_count =  params[:load_max_agent_count] if params[:load_max_agent_count]
          
        end
        





        if (params[:is_load_testing] && params[:is_load_testing].to_s == "true") && project_dir.end_with?("/work/projects/agiletravel-ui-tests")          

          special_linux_work_dir =  special_mac_work_dir = ""
          
          if RUBY_PLATFORM =~ /linux/i
            special_linux_work_dir = File.expand_path("~/work/projects/agiletravel-ui-tests")     
            special_mac_work_dir = special_linux_work_dir.gsub("/home/", "/Users/")
          elsif RUBY_PLATFORM =~ /darwin/i
            special_mac_work_dir = File.expand_path("~/work/projects/agiletravel-ui-tests")     
            special_linux_work_dir = special_mac_work_dir.gsub("/Users/", "/home/")
          else
            special_mac_work_dir = ("/Users/YOU/work/projects/agiletravel-ui-tests")     
            special_linux_work_dir = ("/home/YOU/work/projects/agiletravel-ui-tests")     
          end
          special_win_work_dir = "C:\\work\\agiletravel-ui-tests"
            
          project_config.agent_work_dir = [special_linux_work_dir, special_mac_work_dir, special_win_work_dir].join(";")             
        end
      
        
        project_config.save
        
        project_configuration_xml = project_config.to_xml
        @project.configuration = project_configuration_xml
        
        @project.repository_url = project_config.scm_url
        @project.save
        
        redirect "/projects"
    elsif params[:project]

      @project = Project.new(params[:project])
      @project.server_digest = SERVER_DIGEST if defined?(SERVER_DIGEST)
      @project.identifier.strip!
      @project.working_dir = File.join(BUILDWISE_HOME, "work", @project.identifier)
      if @project.save
				app_config = {
					'scm' => {'url' => params[:scm_url], 'type' => params[:scm_type], "user_name" => params[:scm_user], "password" => params[:scm_pass]}
			  }

				config_name = "#{BUILDWISE_HOME}/config/#{@project.identifier}.xml"

				the_config = BuildWise::ProjectConfiguration.new(config_name)
        the_config.refresh(params)                
        the_config.environment_variables["BUILDWISE_MASTER"] ||= root_url                
        the_config.environment_variables["DISTRIBUTED_MAX_BUILD_TIME"] ||= 3600   
        the_config.environment_variables["DISTRIBUTED_BUILD_CHECK_INTERVAL"] ||= 15                
        the_config.save

        project_configuration_xml = project_config.to_xml
        @project.configuration = project_configuration_xml
        @project.repository_url = project_config.scm_url
        @project.save
        
        redirect "/projects/#{@project.id}"
      else
        puts "There was a problem saving that..."
      end

    else

      if project_dir && !File.exist?(project_dir.strip)
        @error = "The project working directory '#{project_dir}' does not exist on the server."
      else
			  @error = "You must specify project dir (option 1) or project details"
			end
			
 			redirect "/projects/new?error=#{@error}"
    end

  end


  get '/refresh' do
    Project.load_projects_from_working_dir
    redirect "/projects"
  end

  get '/new' do
    authenticate!    
    delete_cache_projects

    @project = Project.new
    @project.server_digest = SERVER_DIGEST if defined?(SERVER_DIGEST)    
    @config = ::BuildWise::ProjectConfiguration.new("tmp")
    if params[:error]
      @error = params[:error]
    end
    render "projects/new".to_sym
  end




  get '/:id/edit', :cache => false do
        
    @project = find_project(params[:id])

    
    
    @config = @project.config    
    if @config.environment_variables["BUILDWISE_MASTER"].blank?
      @config.environment_variables["BUILDWISE_MASTER"] = root_url
      @config.save
    end         

    if @config.environment_variables["DISTRIBUTED_MAX_BUILD_TIME"].blank?
      @config.environment_variables["DISTRIBUTED_MAX_BUILD_TIME"] = 3600
      @config.environment_variables["DISTRIBUTED_BUILD_CHECK_INTERVAL"] = 15
      @config.save
    end         
    
    if @config.environment_variables
      str = ""
      @config.environment_variables.each do |key, val|
        str += "#{key}=#{val}\n"
      end
      @env_vars_string = str.strip
    end
          
    @project_available_agents = @config.is_parallel ?  @project.available_agents.sort : []
    
    render '/projects/edit'.to_sym
  end



  get '/:id/ui_test_priority' do
    @project = find_project(params[:id])
    @ui_test_order = @project.ui_test_priorities.join(",") rescue nil
    return @ui_test_order
  end



  get '/add_build/:id' do
    delete_cache_projects

    @project = find_project(params[:id])
    if @project
      new_build = Build.new(:label => "", :category => "manual", :created_at => Time.now)
      new_build.server_digest = @project.server_digest if @project.server_digest
      if @project.config.is_load_testing        
        new_build.load_server_url = @project.config.load_server_url
        new_build.load_max_agent_count = @project.config.load_max_agent_count
      end
      @project.builds << new_build
      @project.save
    end
    redirect "/projects"
  end



  post '/:id/cancel_build' do
    authenticate!
    delete_cache_projects

    logger.info("Calling Cancel build:#{params[:id]}")
    @build = find_build(params[:id])
    @build.cancel if @build
    redirect "/projects"
  end



  post '/:id' do
    authenticate!
    delete_cache_projects

    @project = find_project(params[:id])
    delete_cache_project_edit(@project)
    
    @config = @project.config
    logger.info("Update project config: #{@project.name rescue params[:id]}")
    params.delete("id")
    @config.reset

    params["retry_on_another_machine"]  = "false" unless params["retry_on_another_machine"] 
    edit_env_vars = params["environment_variables_edit"]
    
    if params["env_var_key"] && params["env_var_key"].size > 0      
      env_vars = {}
      key_hash = params["env_var_key"]
      value_hash = params["env_var_value"]
      key_hash.each do |key_id, key|
        val = value_hash[key_id]
        val = val.strip if val
        env_vars[key] = val
      end
      params["environment_variables"] = env_vars      
    end
    
    if edit_env_vars
      params.delete("environment_variables")
      env_vars = {}
      env_entries = edit_env_vars.split("\n")
      env_entries.each do |entry|
        if entry =~ /^(\w+)=(.*)$/
          a = $1
          b = $2
          a.strip!
          b.strip!
          env_vars[a] = b
        end
      end
      params["environment_variables"] = env_vars
    end

    @config.refresh(params)
    @config.is_parallel = @config.is_parallel.to_s == "true"
    if params[:default_agents] && params[:default_agents].class == Array
      if params[:default_agents].include?("")
        @config.default_agents = nil # selected all agents
      else
        @config.default_agents = params[:default_agents].join(",")
      end

    else
      @config.default_agents = nil # all agents
    end

    @config.environment_variables["BUILDWISE_MASTER"] ||= root_url
    @config.environment_variables["DISTRIBUTED_MAX_BUILD_TIME"] ||= 3600 # one hour
    @config.environment_variables["DISTRIBUTED_BUILD_CHECK_INTERVAL"] ||= 15
  
    if params[:execution_mode] == "load_testing"
      @config.is_parallel = true
      @config.is_load_testing = true
    elsif params[:execution_mode] == "parallel"
      @config.is_load_testing = false    
      @config.is_parallel = true
    elsif params[:execution_mode] == "performance_testing"
      @config.is_performance_testing = true    
      @config.is_parallel = false
    else
      @config.is_load_testing = false    
      @config.is_parallel = false
    end
    
    @config.load_max_agent_count = params[:load_max_agent_count].to_i rescue 2
    @config.load_max_iteration_count = params[:load_max_iteration_count].to_i rescue 2
    @config.load_max_duration = params[:load_max_duration].to_i rescue 2
    @config.load_repeat_times_within_test = params[:load_repeat_times_within_test].to_i rescue 2
    
    if params[:build_steps]
      @config.build_steps = []
      params[:build_steps].each do |step_identifier, hash|        
        next if step_identifier == "template_step_identifier" # special one for clone
        
        puts "\n\n build step || ===> #{hash.inspect}"
        @config.build_steps << ::BuildWise::BuildStep.new_from_hash(hash)                       
      end
    end
        
    @config.load_criteria_error_rate = params[:load_criteria_error_rate].to_f.round(2)
    @config.load_operation_criteria = {}
    if params[:load_criteria_operation_names]
      params[:load_criteria_operation_names].each_with_index do |entry, idx|
        @config.load_operation_criteria[entry] = {
          :average_time => params[:load_criteria_operation_average_time][idx],
          :slowest_time => params[:load_criteria_operation_slowest_time][idx],
          :is_active => params[:load_criteria_operation_is_active][idx].to_i == 1
        }
      end
    end
    save_successful, @errors = @config.save # (@project.identifier)
    if save_successful          
      
      project_configuration_xml = @config.to_xml
      @project.configuration = project_configuration_xml
      
      if @config.ui_tests_dir || @config_builder_ui_test_task
        @project.ui_tests = true
        @project.save
      else
        @project.ui_tests = false
        @project.save
      end      
      redirect "/projects"

    else

      render "projects/edit".to_sym  
    end

  end



  post '/:id/update_config' do
    @project = find_project(params[:id])
    logger.info("Update project config: #{@project.name rescue params[:id]}")
    @project.update_config(params[:config])

    @project.configuration = params[:config]
    @project.save
    
    delete_cache_project_edit(@project)
    redirect "/projects"
  end  
  

  get '/:id/start_build' do
    redirect "/"
  end
  
  post '/:id/start_build' do
    authenticate!
    delete_cache_projects

    @project = find_project(params[:id])
    if @project



      logger.info("Force a build")    
      if !BuildRunner.busy?
        next_project_build = BuildRunner.force_build(@project)        
        next_project_build.update_column(:triggered_by_user_id, current_user.id) if next_project_build
        
        BuildRunner.build_now(next_project_build)
        Buildwise::App.cache.clear
        
        @autorefresh = true # just refresh after starting a new build
        if (next_project_build.id)
          logger.info("next project build: #{next_project_build.id}")
          return {:build_id => next_project_build.id}.to_json
        end
        
      else        
        logger.info("Build runner is busy, maybe another build is still in progress")
        return {:build_id => nil}.to_json
      end
      
    else
      return {:build_id => nil}.to_json
    end

  end


  get "/:id/stats" do
    @project = find_project(params[:id])
    @test_error_lookups = {}
    project_last_complete_build = @project.last_complete_build
    if project_last_complete_build && project_last_complete_build.test_files.any?
      project_last_complete_build.test_files.each do |test_file|
        @test_error_lookups[File.basename(test_file.filename)] = test_file.failed? ? 0 : 1
      end
    end
    render "/projects/stats".to_sym
  end

  get '/help/rakefile' do
    render "projects/help_rakefile".to_sym
  end

  get '/:id/help/rakefile' do
    @project = find_project(params[:id])
    render "projects/help_rakefile".to_sym
  end



  get '/:id/report', :cache => true do
        
    expires 60

    cache_key { 
      the_key = request.path_info + "?" + params.slice('starts_at').to_param + "&" + params.slice('ends_at').to_param 
      the_key
    }
    
    determine_reportable_time
    
    start_time = Time.now
    @project = find_project(params[:id])
    @chart_height = settings.chart_height  || 480
    
    @build_times = []
    @pass_build_times = []
    @fail_build_times = []
    @build_test_cases = []
    
    @build_test_cases = []
    @build_time_chart_colors = []
    
    min_time = 10 # seconds
    max_time = 5 * 60 * 60 # max time five hours
    build_ui_test_case_count_array = []
    
    the_builds = Build.where("project_id = ? AND (category IS NULL OR category != ?) AND status IS NOT NULL AND status != ?  AND status != ?", @project.id, 'poll', 'unchanged', 'new')
  
    the_builds = the_builds.where("is_invalid != ?", true)
    @stats = {}
    all_valid_project_builds_array = the_builds.pluck(:duration, :start_time, :id, :successful)    
    @stats[:first_build_time] = all_valid_project_builds_array.first[1] rescue nil
    @stats[:last_build_time] = all_valid_project_builds_array.last[1] rescue nil
    @stats[:date_range] =  ( format_seconds(@stats[:last_build_time] - @stats[:first_build_time]) ) rescue nil

    if @starts_at 
      the_builds = the_builds.where("start_time > ?", @starts_at)
    end
  
    if @query_ends_at
      the_builds = the_builds.where("start_time < ?", @query_ends_at)
    end
  
    the_builds = the_builds.order("start_time ASC")
  

    the_valid_project_builds_array = the_builds.pluck(:duration, :start_time, :id, :successful)

    duration_array = the_valid_project_builds_array.collect { |x| x[0] ? x[0] : 0 }
    average_duration = (duration_array.inject(:+) / duration_array.length) rescue 0


    if the_valid_project_builds_array
      most_recent_build_id = the_valid_project_builds_array.last[2] rescue nil
      if the_valid_project_builds_array.size > 25 # don't worry in small set
        the_valid_project_builds_array.reject! { |x| x[0] && x[0] > 3 * average_duration  && x[2] < (most_recent_build_id - 50) }
      end
    end
    
    the_valid_project_builds_array.each_with_index do |bd, idx|
      next if bd.nil? || bd[0].nil?


      the_build = Build.find_by_id(bd[2]) # by build id
      a_build_test_case_count = the_build.ui_test_count
      a_build_failed_test_case_count = the_build.ui_test_error_count rescue 0

      next if bd[0] < min_time || bd[0] > max_time # check for duration
      rounded_time = (bd[0] / 60.0).round(1)  
      build_at_local_time_str = bd[1].localtime.strftime('%Y-%m-%d %H:%M')
      
      if bd[1].localtime > 1.month.ago
        @build_times << {local_time: bd[1].localtime, date: bd[1].localtime.strftime('%Y-%m-%d %H:%M'), time: build_at_local_time_str, duration: rounded_time, result: (bd[3] ? 1 : 0), color: (bd[3] ? '#89A54E' : '#AA4643') }
      else
        @build_times << {local_time: bd[1].localtime, date: bd[1].localtime.strftime('%Y-%m-%d'), time: build_at_local_time_str, duration: rounded_time, result: (bd[3] ? 1 : 0), color: (bd[3] ? '#89A54E' : '#AA4643') }     
      end

      @build_time_chart_colors << (bd[3] ? '#89A54E' : '#AA4643')

      if a_build_test_case_count > 0 && a_build_test_case_count
        build_ui_test_case_count_array << a_build_test_case_count
        @build_test_cases << {:id => bd[2], time: bd[1], 
           test_case_count: a_build_test_case_count,
           failed_test_case_count:  a_build_failed_test_case_count, 
           :successful => bd[3]}
      end
    end


  if @project.config.is_load_testing
    
    @load_test_results = []    
    @all_operations = []
    
    the_builds.each do |a_build|
      hash = {}
      a_build.analyse_load_results
    
      next if  a_build.load_hits == 0 
      
      hash[:build_id] = a_build.id
      hash[:build_start_time] = a_build.start_time
      hash[:build_duration] = a_build.load_duration
      hash[:hits] = a_build.load_hits 
      hash[:hits_per_second] = a_build.load_hits_per_second
      hash[:peak_hits_per_second] = a_build.load_peak_hits_per_second
      hash[:vu_count] = a_build.load_vu_count

      build_load_test_results = LoadTestResult.where(:build_id => a_build.id)
      raw_operation_timings = build_load_test_results.group_by(&:operation)
      timings = []
      raw_operation_timings.each do |key, val|
        duration_array =  val.collect{|x| (x.duration / 1000.0).round(2)} 
        @all_operations << key
        timings << {:operation => key, :count => val.size,  
                   :average_time => (duration_array.sum / val.count).round(3),   
                   :fastest_time => duration_array.min,
                   :slowest_time => duration_array.max }        
      end
      hash[:timings] = timings
      @load_test_results << hash
    end
    
    @all_operations.uniq!
    render "projects/report_load_testing".to_sym
    
  else
    

    total_test_file_executions = TestFile.joins(:build).where("builds.project_id = #{@project.id}").pluck(:duration, :successful)
    total_test_file_duration = 0
    total_test_file_executions.each { |x| total_test_file_duration += x[0] if x[0] }      
    @stats[:total_execution_time] = format_seconds(total_test_file_duration.round(1))
    
    range_test_file_executions = TestFile.joins(:build).where("builds.project_id = #{@project.id}").where("test_files.created_at > ? AND test_files.created_at < ?", @starts_at, @query_ends_at).pluck(:duration, :successful)
    range_test_file_duration = 0
    range_test_file_executions.each { |x| range_test_file_duration += x[0] if x[0] }
    @stats[:range_execution_time] = format_seconds(range_test_file_duration.round(1))
    


      total_test_case_executions =  TestCase.where(:project_id => @project.id).pluck(:duration, :successful)

      range_test_case_executions = TestCase.joins(:test_file).where(:project_id => @project.id).where("test_files.created_at > ? AND test_files.created_at < ?", @starts_at, @query_ends_at).pluck(:duration, :successful)
      

      @stats[:total_executions_count] = total_test_case_executions.size
      @stats[:total_pass_rate] = (total_test_case_executions.count { |x| x[1] } * 100.0 / @stats[:total_executions_count]).round(1)
    
      @stats[:range_executions_count] = range_test_case_executions.size
      @stats[:range_pass_rate] = (range_test_case_executions.count { |x| x[1] } * 100.0 / @stats[:range_executions_count]).round(1)
          
      @stats[:range_first_build_time] = the_valid_project_builds_array.first[1] rescue nil
      @stats[:range_last_build_time] = the_valid_project_builds_array.last[1] rescue nil
      @stats[:selected_date_range] =  ( format_seconds(@stats[:range_last_build_time] - @stats[:range_first_build_time]) ) rescue nil

      build_ui_test_case_count_array.flatten!
      avg_ui_test_case_count = build_ui_test_case_count_array.reduce(:+).to_f / build_ui_test_case_count_array.size
      @build_test_cases.select! { |x| x[:test_case_count] > avg_ui_test_case_count / 2 }
      logger.debug("generate report time: #{Time.now - start_time}")

      render "projects/report_functional_testing".to_sym
    end
    
  end


  get '/:id/duplicate' do
    authenticate!    
    @project = find_project(params[:id])
    @config = @project.config
    render "projects/duplicate".to_sym
  end


  post '/:id/duplicate' do
    authenticate_admin!
    
    @existing_project = find_project(params[:id])
    new_project_name = params[:new_project_name]
    new_project_identifier = params[:new_project_identifier]
    new_scm_branch = params[:new_scm_branch]

    @project = Project.new
    @project.server_digest = SERVER_DIGEST if defined?(SERVER_DIGEST)
    @project.identifier = new_project_identifier
    @project.name = new_project_name
    @project.working_dir = File.join(File.dirname(@existing_project.working_dir), @project.identifier)
    @project.active = true

    if @project.save


      FileUtils.mkdir(@project.working_dir) unless File.exist?(@project.working_dir)
      FileUtils.mkdir(@project.working_dir + "/artifacts") unless File.exist?(@project.working_dir + "/artifacts")
      FileUtils.mkdir(@project.working_dir + "/logs") unless File.exist?(@project.working_dir + "/logs")

      logger.debug("About to copy #{@existing_project.working_dir + '/sources'}")
      FileUtils.cp_r(@existing_project.working_dir + "/sources", @project.working_dir + "/sources")


      existing_project_config_file = "#{BUILDWISE_HOME}/config/#{@existing_project.identifier}.xml"
      new_config_file = "#{BUILDWISE_HOME}/config/#{@project.identifier}.xml"
      the_config = BuildWise::ProjectConfiguration.load_from(existing_project_config_file)
      the_config.filename = new_config_file
      
      the_config.is_parallel = params[:is_parallel].to_s == "true" rescue false
      the_config.app_name = params[:app_name]
      the_config.agent_work_dir = params[:agent_work_dir]

      if params[:ui_test_task] 
        the_ui_test_step = nil        
        if @project.config.is_load_testing 
          the_ui_test_step = the_config.load_test_step          
        else           
          the_ui_test_step = the_config.ui_test_step
        end
        if the_ui_test_step
          the_ui_test_step.task = params[:ui_test_task]
        end        
      end
    

      if new_scm_branch && !new_scm_branch.strip.empty?
        the_config.scm_branch = new_scm_branch

        sources_dir = @project.working_dir + "/sources"
        if RUBY_PLATFORM =~ /mingw/
          sources_dir = sources_dir.gsub("/", "\\")
        end

        current_dir = File.expand_path(".")
        begin
          FileUtils.chdir(sources_dir)
          output1 = `git fetch`
          output2 = `git checkout #{new_scm_branch}`
        rescue => be
          puts "ERROR: failed to check out to new branch"
        ensure
          FileUtils.chdir(current_dir)
        end
      end
      the_config.save

      redirect "/projects"
      
    else

      render "projects/duplicate".to_sym      
    end
  end


  get "/:id/failed_tests" do
    @project = find_project(params[:id])
    @project ? @project.get_build_tests_by_status(false) : nil
  end

  get "/:id/successful_tests" do
    @project = find_project(params[:id])
    @project ? @project.get_build_tests_by_status(true) : nil
  end

  get "/:id/changed_files" do
    @project = find_project(params[:id])
    if @project.nil? || @projects.builds.empty?
      return []
    end

    return @project.builds.last.last_commit_changed_files
  end


  get "/:id/envs" do
    @project = find_project(params[:id])
    if @project.config.environment_variables
      serialize_str = ""
      @project.config.environment_variables.each do |k, v|
        serialize_str += (k + ":" + v + ";")
      end
      return serialize_str
    else
      return ""
    end
  end






  post "/:id/distribution_rules" do
    @project = find_project(params[:id])
    @distribution_rule = DistributionRule.new
    @distribution_rule.project = @project
    @distribution_rule.test_file_names = params[:test_file_names].to_json
    if params[:build_agent_names] && params[:build_agent_names].any?
      @distribution_rule.build_agent_names = params[:build_agent_names].to_json
    end
    @distribution_rule.constraint = params[:constraint]
    @distribution_rule.active = true
    @distribution_rule.save

    redirect "/projects/#{@project.id}/edit"
  end

  get "/:id/delete_rule" do
    @project = find_project(params[:id])
    authenticate_admin_or_project_builder!(@project)
    delete_cache_project_edit(@project)
    
    @distribution_rule = DistributionRule.where(:project_id => @project.id, :id => params[:rule_id]).first
    if @distribution_rule
      @distribution_rule.destroy
    end
    render "/projects/delete_rule.js".to_sym, :layout => false
  end

  get "/:id/toggle_rule_active" do
    @project = find_project(params[:id])
    authenticate_admin_or_project_builder!(@project)
    delete_cache_project_edit(@project)
    @distribution_rule = DistributionRule.where(:project_id => @project.id, :id => params[:rule_id]).first
    if @distribution_rule
      current_active = @distribution_rule.active
      @distribution_rule.update_column(:active, !current_active)
      @distribution_rule.reload
    end
    render "/projects/toggle_rule_active.js".to_sym, :layout => false
  end



  post "/:id/load_operation_criteria" do
    @project = find_project(params[:id])
    operation_name = params["operation"]
    operation_average_time = params["average_time"].to_i
    operation_slowest_time = params["slowest_time"].to_i
    the_config =  @project.config
    the_config.load_operation_criteria[operation_name] = {average_time: operation_average_time, slowest_time: operation_slowest_time, is_active: "true"}
    the_config.save  
    redirect "/projects/#{@project.id}/edit"
  end



  post "/:id/update_name" do
    @project = find_project(params[:id])
    authenticate_admin_or_project_builder!(@project)
    delete_cache_project_edit(@project)
    if params[:value] && !params[:value].empty?
      @project.name = params[:value]
      @project.save
      @project.reload
    end    
    erb "/projects/update_name.js".to_sym, :layout => false    
  end

  get "/:id/update_name" do
    @project = find_project(params[:id])
    authenticate_admin_or_project_builder!(@project)

    if params[:value] && !params[:value].empty?
      @project.name = params[:value]
      @project.save
      @project.reload
    end    
    render "/projects/update_name.js".to_sym, :layout => false    
  end
  



  get '/:id' do
    authenticate!    
    @project = find_project(params[:id])
    begin
      @project.config(true)
      render "/projects/show".to_sym
    rescue => e
      @error = e
      render "/projects/config_error".to_sym
    end
  end


  get '/:id/slack_send_test_notification' do
    @project = find_project(params[:id])
    require 'slack-notifier' # https://github.com/stevenosloan/slack-notifier
    project_config = @project.config
    @notifier = ::Slack::Notifier.new project_config.publisher_slack_webhook_url, channel: project_config.publisher_slack_channel, username: project_config.publisher_slack_username
    @notifier.ping("Test message from BuildWise: #{Time.now}")        
    render "/projects/slack_send_test_notification.js".to_sym, :layout => false        
  end
  
  get '/:id/http_callback_send_test_notification' do
    @project = find_project(params[:id])
    webhook = params[:webhook]  
    @error = nil
    begin
      require 'httparty'
      HTTParty.post(webhook, body: { result: 'success'} )    
    rescue => e
      @error = "'#{webhook}', #{e}'"
    end
    render "/projects/http_callback_send_test_notification.js".to_sym, :layout => false            
  end
  
  


  get "/:id/user_stories" do
    @project = find_project(params[:id])
    @user_stories = @project.user_stories
    
    @traceability_lookups = nil
    begin
      store_file = File.join(Padrino.root, "tmp", "traceability", "traceability-#{@project.id}.pstore")      
      store = PStore.new(store_file)
      @traceability_lookups =  store.transaction { store.fetch(:traceability, nil) }
    rescue => e

    end
    
    render "projects/user_stories".to_sym      
  end

  post "/:id/upload_user_stories" do
    @project = find_project(params[:id])    
    imported = UserStory.import_csv(@project, params[:user_stories_csv])
    if imported.count > 0
      @project.user_stories.destroy_all
      imported.each do |us|
        us.save
      end
    end     
    redirect "/projects/#{@project.id}/user_stories" 
  end
  
  post "/:id/generate-traceability-matrix" do

    @project = find_project(params[:id])    
    
    traceability_lookups = @project.generate_traceability_matrix();    
    traceability_lookups ||= {}
    traceability_lookups[:updated_at] = Time.now
    
    if traceability_lookups[:requirement_tests]
      
      if traceability_lookups[:requirement_tests]



        begin
          requirements_covered_list = traceability_lookups[:requirement_tests].keys.uniq
          count = 0
          @project.user_stories.each do |story|
            story_traceability_id = story.external_id.to_s
            count += 1 if requirements_covered_list.include?(story_traceability_id)
          end
          traceability_lookups[:coverage] = count * 100 / @project.user_stories.size
          if traceability_lookups[:coverage]  >= 100
            traceability_lookups[:coverage]  = 100
          end
        rescue => e
          puts "Error on parsing : #{e}"
          traceability_lookups[:coverage] = 0.0
        end
      else
        traceability_lookups[:error] = "No user story id found in test cases, please see above example."
      end

      store_dir = File.join(Padrino.root, "tmp", "traceability")
      FileUtils.mkdir_p(store_dir) unless File.exist?(store_dir)
      
      store_file = File.join(store_dir, "traceability-#{@project.id}.pstore")
      store = PStore.new(store_file)
      store.transaction do
        store[:traceability] = traceability_lookups
      end
      
      puts "Coverage => #{traceability_lookups[:coverage]}"      
    end
      
    redirect "/projects/#{@project.id}/user_stories" 
  end
  
  

  post "/:id/export-traceability-matrix-spreadseet" do
    @project = find_project(params[:id])    
    
    @traceability_lookups = nil
    begin
      store_file = File.join(Padrino.root, "tmp", "traceability",  "traceability-#{@project.id}.pstore")      
      store = PStore.new(store_file)
      @traceability_lookups =  store.transaction { store.fetch(:traceability, nil) }
    rescue => e

    end
    
    begin
      require 'spreadsheet'
      matrix_template_file = File.join(::Padrino.root, 'config', 'traceability_matrix_template.xls')
      book = Spreadsheet.open matrix_template_file
      sheet = book.worksheet 0
      generated_at = Time.now.strftime('Date: %Y-%m-%d %H:%M:%S')
      sheet.row(2).replace [sheet.cell(2, 0), "", "", "", "", generated_at]
      sheet.row(4).replace ["Requirement test coverage:", @traceability_lookups[:coverage] * 1.0 / 100 ]
      sheet.row(5).replace ["User Story count:", @project.user_stories.count]
      sheet.row(6).replace ["Test script files count:", @traceability_lookups[:test_files].size]

      requirement_sheet = book.worksheet("Requirements")
      test_case_sheet = book.worksheet("Test Cases")




      all_test_cases = []
      case_idx = 0
      @traceability_lookups[:spec_files].each do |spec_file|
        spec_file.spec_contexts.each do |context|
          context.spec_behaviours.each do |test_case|
            case_idx += 1
            test_case_sheet.row(case_idx).replace([case_idx, spec_file.file_name, test_case.name])
            all_test_cases << test_case
          end
        end
      end
      all_test_case_count = all_test_cases.size

      sheet.row(7).replace ["Test case count:", all_test_case_count]
      test_lookups = @traceability_lookups[:requirement_tests]

      row_headings = ["Test Cases / Requirment IDs"]

      story_ids = []
      uncovered_row_cells = []
			@project.user_stories.each_with_index {|story, idx|
				 row_headings << story.external_id
				 requirement_sheet.row(idx + 1).replace([story.external_id, story.title])
				 tests_for_this_story = test_lookups[story.external_id.to_s]
         if tests_for_this_story.nil? || tests_for_this_story.empty?

					  uncovered_row_cells << idx
				 end
				 story_ids << story.external_id.to_s
			}

      row = 1
      all_test_cases.each_with_index { |test_case, idx|
        results = ["TC " + (idx + 1).to_s + ": " + test_case.name] # propbably name
        covered = false

        story_ids.each do |story_id|
          tests_for_this_story = test_lookups[story_id.to_s]
          if tests_for_this_story && tests_for_this_story.include?(test_case)
             results << "x"
             covered = true
          else
             results << ""
          end
        end

        sheet.row(11 + idx).replace results

      }

       new_row = sheet.row(9).replace row_headings
       matrix_sheet_story_count = @project.user_stories.size
       matrix_sheet_column_count = sheet.column_count
       (matrix_sheet_story_count+1..matrix_sheet_column_count).each do |col|
         sheet.columns[col].hidden  =true
       end

      require 'tempfile'
      temp_file = Tempfile.new('traceability_matrix_report.xls')
      book.write(temp_file.path)
      send_file temp_file.path,  :filename =>  "traceability_matrix_report.xls"
    rescue => e
      puts "\nError => #{e}\n "
      redirect "/projects/#{@project.id}/user_stories" 
    end
  end
  
  
  get "/:id/latest-build" do
    @project = find_project(params[:id])    
    @build = @project.builds.last
    redirect "/builds/#{@build.id}"
  end
  
  
  get "/:id/report_operation", :provides => :js do
    determine_reportable_time
    @project = find_project(params[:id])    
    @operation = params[:operation]
    @modal_content = partial("/projects/report_operation_modal").gsub("\n", "");
    
    project_load_builds = Build.where(:project_id => @project.id).where("is_load_testing = ?", true)
    
    if @starts_at 
      project_load_builds = project_load_builds.where("start_time > ?", @starts_at)
    end
  
    if @query_ends_at
      project_load_builds = project_load_builds.where("start_time < ?", @query_ends_at)
    end
    
    @operation_timings = []
    @vus = []
    @build_times = []
    project_load_builds.each do |a_build|
      a_build.analyse_load_results
      
      next if a_build.load_hits == 0
      
      build_load_test_results =  LoadTestResult.where(:build_id => a_build.id).order("start_timestamp ASC")
      next if build_load_test_results.empty?
      
      build_first_load_test_result = build_load_test_results.first rescue nil
      
      if build_first_load_test_result
        @build_times << build_first_load_test_result.start_timestamp
      else
        @build_times << a_build.start_time.to_i 
      end
     
      operation_load_results = LoadTestResult.where(:operation => @operation, :build_id => a_build.id).pluck("duration")
      if (operation_load_results.count > 0)
        @operation_timings <<  (operation_load_results.sum / operation_load_results.count)        
      else
        @operation_timings << 0
      end
      @vus << a_build.load_vu_count
    end    
    render "/projects/report_operation.js".to_sym, :layout => false
  end
  
  
end