class GamesController < ApplicationController
  before_action :require_login, :check_auth
  before_action :find_game, except: [:jupyter]
  before_action :find_shixun, only: [:show, :answer, :rep_content, :choose_build, :game_build, :game_status]

  before_action :allowed, except: [:jupyter]

  #require 'iconv'

  include GamesHelper
  include ApplicationHelper

  def show
    uid_logger("--games show start")
    # 防止评测中途ajaxE被取消;3改成0是为了处理首次进入下一关的问题
    update_game_parameter(@game)

    game_challenge = Challenge.base_attrs.find(@game.challenge_id)

    # 选择题类型的实训关卡总分
    @st = game_challenge.st
    if @st == 1
      game_challenge.score = game_challenge.choose_score.to_i
    end

    game_count = Game.where(myshixun_id: @game.myshixun_id).count

    discusses = @shixun.discusses
    discusses = discusses.where('hidden = false OR user_id = :user_id', user_id: current_user.id) unless current_user.admin_or_business?
    discusses_count = discusses.count

    @user = @game.owner
    is_teacher = @user.is_teacher?

    # 实训超时设置
    time_limit = game_challenge.exec_time

    # 上一关、下一关
    prev_game = @game.prev_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position)
    #next_game =  @game.next_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position)
    next_game = user_next_game(@shixun, game_challenge, @game, @identity)

    # 关卡点赞数, praise_or_tread = 1则表示赞过
    praise_count = game_challenge.praises_count
    user_praise = game_challenge.praise_treads.exists?(user_id:current_user.id, praise_or_tread: 1)

    # 实训的最大评测次数，这个值是为了优化查询，每次只取最新的最新一次评测的结果集
    max_query_index = @game.query_index.to_i

    # 统计评测时间
    record_onsume_time = EvaluateRecord.where(game_id: @game.id).first.try(:pod_execute)

    # myshixun_manager判断用户是否有权限查看隐藏测试集(TPM管理员；平台认证的老师；花费金币查看者)
    myshixun_manager = @identity < User::EDU_GAME_MANAGER

    # 选择题和编程题公共部分
    @base_date = {st: @st, discusses_count: discusses_count, game_count: game_count, myshixun: @myshixun,
                  challenge: game_challenge.attributes.except("answer"), game: @game.try(:attributes), shixun: @shixun.attributes.except("vnc", "vnc_evaluate"),
                  record_onsume_time: record_onsume_time, prev_game: prev_game, next_game: next_game,
                  praise_count: praise_count, user_praise: user_praise, time_limit: time_limit,
                  tomcat_url: edu_setting('cloud_tomcat_php'), is_teacher: is_teacher,
                  myshixun_manager: myshixun_manager, git_url: (@shixun.vnc ? repo_url(@myshixun.repo_path) : "")}
    if @shixun.vnc
      get_vnc_link(@game)
    end

    # 区分选择题和编程题，st：0编程题；
    if @st == 0
      has_answer = game_challenge.challenge_answers.size != 0
      game_challenge.answer = nil
      mirror_name = @shixun.mirror_name
      # 判断tpm是否修改了
      begin
        tpm_modified = @myshixun.repository_is_modified(@shixun.repo_path) # 判断TPM和TPI的版本库是否被改了
      rescue
        uid_logger("实训平台繁忙，繁忙等级（81）")
      end
      tpm_cases_modified = (game_challenge.modify_time != @game.modify_time)  # modify_time 决定TPM测试集是否有更新

      @task_result = {tpm_modified: tpm_modified, tpm_cases_modified: tpm_cases_modified, mirror_name: mirror_name, has_answer: has_answer}

      testset_detail max_query_index, game_challenge
    else # 选择题类型的
      # 该方法多个地方调用，比如show、评测
      # 最后一个字段true表示只显示数据，false表示可能有添加数据
      choose_container(game_challenge, @game, max_query_index)
    end
  end

  def jupyter
    # Jupyter没有challenge
    @myshixun = Myshixun.find_by_identifier params[:identifier]
    unless current_user.id == @myshixun.user_id || current_user.admin_or_business?
      raise Educoder::TipException.new(403, "..")
    end
    @shixun = @myshixun.shixun
    # 判断tpm是否修改了
    begin
      @tpm_modified = @myshixun.repository_is_modified(@shixun.repo_path) # 判断TPM和TPI的版本库是否被改了
    rescue
      uid_logger("服务器出现问题，请重置刷新页面")
    end
  end

  def reset_vnc_link
    begin
      # 删除vnc的pod
      delete_vnc(@game)
      # 重新连接
      get_vnc_link(@game)

      render :json => {status: 1, message: "重置VNC成功", data: {vnc_url: @vnc_url, vnc_evaluate: @vnc_evaluate}}
    rescue Exception => e
      logger.error("############'#{e.message}'")
      tip_exception("实训云平台繁忙")
    end

  end

  # 查看效果
  # todo : 这块代码有很大的改进空间
  # todo : 中文排序问题
  def picture_display
    myshixun = Myshixun.find(@game.myshixun_id)
    if myshixun.main_mirror.try(:type_name) == "Android"
      @type = "qrcode"
      workspace = @game.try(:picture_path)
      game_challenge = @game.challenge
      qr = RQRCode::QRCode.new("#{edu_setting('host_name')}/api/shixuns/download_file?file_name=#{workspace}/#{game_challenge.picture_path}/manual-ok.apk", :size => 12, :level => :h)
      @qrcode_str = Base64.encode64( qr.to_img.resize(400,400).to_s )

    else
      #conv = Iconv.new("GBK", "utf-8")
      @game_challenge = @game.challenge
      type = @game_challenge.show_type
      @type = shixun_show_type type

      workspace_path = @game.try(:picture_path)
      @answer_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.expect_picture_path}"
      @user_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.picture_path}"
      @original_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.original_picture_path}"

      @answer_picture = @game_challenge.expect_picture_path.nil? ? [] : get_dir_filename(@answer_path, type, @game.id)
      #@answer_picture = @answer_picture.sort {|x, y| conv.iconv(x) <=> conv.iconv(y)} if @answer_picture.present?

      @user_picture = @game_challenge.picture_path.nil? ? [] : get_dir_filename(@user_path, type, @game.id)
      #@user_picture = @user_picture.sort {|x, y| conv.iconv(x) <=> conv.iconv(y)}
      if @game_challenge.original_picture_path.blank?
        @orignal_picture = nil
      else
        @orignal_picture = @game_challenge.original_picture_path.nil? ? [] : get_dir_filename(@original_path, type, @game.id)
        #@orignal_picture = @orignal_picture.sort {|x, y| conv.iconv(x) <=> conv.iconv(y)}
      end
    end
  end

  # 同步更新最新代码
  # 同步完成后，需要更新myshixun的commit_id为实训的commit最新值
  # --------------------------
  # 新思路，有冲突则则重置，没有冲突直接pull
  # identifier为password---
  # --------------------------
  # todo: TPI弹框的立即更新
  def sync_codes
    shixun_tomcat = edu_setting('cloud_bridge')
    begin
      git_myshixun_url = repo_ip_url @myshixun.repo_path
      git_shixun_url = repo_ip_url @myshixun.shixun.try(:repo_path)
      git_myshixun_url = Base64.urlsafe_encode64(git_myshixun_url)
      git_shixun_url = Base64.urlsafe_encode64(git_shixun_url)
      # todo: identifier 是以前的密码,用来验证的,新版如果不需要,和中间层协调更改.
      params = {tpiID: "#{@myshixun.try(:id)}", tpiGitURL: "#{git_myshixun_url}", tpmGitURL: "#{git_shixun_url}",
                identifier: "xinhu1ji2qu3"}
      uri = "#{shixun_tomcat}/bridge/game/resetTpmRepository"
      res = uri_post uri, params
      if (res && res['code'] != 0)
        tip_exception("实训云平台繁忙（繁忙等级：95）")
      end
      shixun_new_commit = GitService.commits(repo_path: @myshixun.shixun.repo_path).first["id"]
      @myshixun.update_attributes!(commit_id: shixun_new_commit, reset_time: @myshixun.shixun.try(:reset_time))
      if @game.challenge.st == 0 && @game.challenge.path.present?
        paths = @game.challenge.path.split("；")
        paths.each do |path|
          game_code_init @game.id, path.try(:strip)
        end
      end
      @path = @game.challenge.path
      # 更新完成后，弹框则隐藏不再提示
      @myshixun.update_column(:system_tip, false)
    rescue Exception => e
      tip_exception("立即更新代码失败!#{e.message}")
    end
  end

  ## 给关卡打星星
  def star
    shixun = Shixun.select([:id, :averge_star, :status]).where(id: params[:shixun_id]).first
    grades = Grade.where(user_id: current_user.id, container_id: @game.id, container_type: 'Star')
    if grades.exists?
      tip_exception("您已经评价过该实训")
    else
      @game.update_column(:star, params[:star].to_i)
      # 更新实训平均星星数值
      averge_star = Game.find_by_sql("select ifnull(sum(g.star),0)/ifnull(count(*),1) as averge_star from (games g left join
                                     (myshixuns m join shixuns s on s.id = m.shixun_id) on m.id = g.myshixun_id)
                                     where star != 0 and s.id = #{shixun.id}").first.try(:averge_star)
      averge_star = averge_star.to_f || 5
      shixun.update_column(:averge_star, averge_star.round(1))
      # 随机生成10-100金币作为奖励
      @gold = 0
      # 加积分只针对已发布的实训
      if shixun.status >= 2
        @gold = rand(10..100)

        RewardGradeService.call(current_user, container_id: @game.id, container_type: 'Star', score: @gold)
      end
    end
  end

  ## 代码文件目录结构
  def git_entries
    gpid = params[:gpid]
    @path = params[:path].try(:strip)
    rev = params[:rev] ? params[:rev] : "master"
    @trees = @g.trees(gpid, path: @path, rev: rev)
    unless @trees.count
      tip_exception("版本库异常")
    end
  end

  ## 是否可以查看答案，如果是管理员或者系统认证的老师则直接查看，不需要弹框
  def answer
    challenge = Challenge.select([:answer, :id, :score, :st]).find(@game.challenge_id)
    # 这几种情况可以直接查看答案的：实训未发布；当前用户为实训管理员；已经查看过答案；平台认证的老师；
    @allowed = @shixun.status < 2 || @game.answer_open == 1 || current_user.shixun_identity(@shixun) <= User::EDU_CERTIFICATION_TEACHER
    uid_logger("-- is manager #{current_user.manager_of_shixun?(@shixun)}")

    @result = challenge.st == 0 ? challenge.try(:answer) : challenge.choose_answer
  end

  # 获取实践题答案
  # GET: /tasks/:identifier/get_answer_info
  # 0 直接查看答案, 1 查看答案弹框, 2 答案详情弹框
  def get_answer_info
    @challenge = @game.challenge
    @challenge_answers = @challenge.challenge_answers
    # 平台已认证的老师需要控制
    @power = (@identity < User::EDU_GAME_MANAGER)

    # 只要跟这个实训相关的实训作业中有设置实训不公开答案，那么该实训下的该挑战就不能查看答案
    homework_common_answer_public = @game.myshixun.shixun.homework_commons.
        where(course_id: User.current.as_student_courses.map(&:id)).
        map(&:answer_public).include?(false)
    if !@power
      if @challenge_answers.size == 0
        tip_exception("无答案可以查看")
      elsif @challenge_answers.size == 1
        if homework_common_answer_public
          tip_exception("该课程禁止查看答案")
        end
        # 未看答案,提示弹框
        if @game.answer_open == 0
          tip_exception(1, {answer_id: @challenge_answers.first.id, answer_score:@challenge_answers.first.score})
        end
      else
        if @game.answer_open == 0
          tip_exception(2, @challenge_answers.map{|a| {answer_id: a.id, answer_name: a.name, answer_score:a.score}})
        end
      end
    end
  end

  # 获取选择题答案
  def get_choose_answer
    @challenge = @game.challenge
    tip_exception("本接口只能获取选择题答案") if @challenge.st != 1
    @power = (@identity < User::EDU_GAME_MANAGER)
    # 如果没权限，也没看过答案，则需要解锁
    if @game.answer_open == 0 && !@power
      tip_exception(1, @challenge.choose_score)
    else
      @challenge_chooses = @challenge.challenge_chooses
    end
  end

  # 解锁实践题答案
  # GET: /tasks/:identifier/get_answer_info?answer_id=?
  def unlock_answer
    @challenge = @game.challenge
    @answer = ChallengeAnswer.find(params[:answer_id])
    challenge = @answer.challenge
    # 解锁需要本层级的答案是否需要扣分
    points = challenge.challenge_answers.where(level: @game.answer_open + 1..@answer.level).sum(:score)
    deduct_score = ((points / 100.0) * challenge.score).to_i
    uid_logger("############金币数目: #{current_user.grade}")
    unless current_user.grade.to_i - deduct_score > 0
      tip_exception("您没有足够的金币")
    end

    ActiveRecord::Base.transaction do
      begin
        # 积分消耗情况记录
        score = challenge.st.zero? ? -deduct_score : -challenge.choose_score.to_i
        RewardGradeService.call(current_user, container_id: @game.id, container_type: 'Answer', score: score)

        # 通关查看答案 不扣 得分
        answer_open = @challenge.st == 1 ? 1 : @answer.level
        if @game.status == 2
          @game.update_attributes!(:answer_open =>answer_open)
        else
          # 扣除总分计算
          answer_deduction = challenge.challenge_answers.where("level <= #{@answer.level}").sum(:score)
          @game.update_attributes!(:answer_open => answer_open, :answer_deduction => answer_deduction)
        end
        GameAnswer.create!(challenge_answer_id: @answer.id, user_id: current_user.id, game_id: @game.id, view_time: Time.now)

      rescue Exception => e
        uid_logger_error("#######金币扣除异常: #{e.message}")
        raise ActiveRecord::Rollback
      end
    end
  end

  # 解锁选择题答案
  def unlock_choose_answer
    @challenge = @game.challenge
    score = @challenge.choose_score
    unless current_user.grade.to_i - score > 0
      tip_exception("您没有足够的金币")
    end
    ActiveRecord::Base.transaction do
      begin
        # 积分消耗情况记录
        RewardGradeService.call(current_user, container_id: @game.id, container_type: 'Answer', score: -score)
        # 通关查看答案 不扣 得分
        if @game.status == 2
          @game.update_attributes!(:answer_open => 1)
        else
          # 扣除总分计算
          @game.update_attributes!(:answer_open => 1, :answer_deduction => 100)
        end
        @challenge_chooses = @challenge.challenge_chooses
        GameAnswer.create!(user_id: current_user.id, game_id: @game.id, view_time: Time.now)
      rescue Exception => e
        uid_logger_error("#######金币扣除异常: #{e.message}")
        raise ActiveRecord::Rollback
      end
    end
  end

  # 查看答案需要扣取金币
  # 必须保证用户的金币数大于关卡的金币数
  def answer_grade
    challenge = Challenge.select([:answer, :id, :score, :st]).find(@game.challenge_id)
    challenge_score = challenge.try(:score)
    final_score = @game.final_score
    @allowed_viewed = current_user.grade.to_i - challenge_score > 0
    unless @allowed_viewed
      tip_exception("您没有足够的金币")
    end
    ActiveRecord::Base.transaction do
      begin
        if @game.answer_open == 0 # 如果这是第一次查看答案
          if challenge.st == 0
            @final_score = final_score - challenge_score
            # 积分消耗情况记录
            RewardGradeService.call(
              current_user,
              container_id: @game.id,
              container_type: 'Answer',
              score: -challenge_score
            )
          else
            @final_score = final_score - challenge.choose_score.to_i
            # 之所以不用final_score是因为过关后查看答案的final_score为0，但是记录需要记录扣除的分数
            RewardGradeService.call(
              current_user,
              container_id: @game.id,
              container_type: 'Answer',
              score: -challenge.choose_score.to_i)
          end
          @game.update_attributes!(:answer_open => true, :final_score => final_score)
        end
        if challenge.st == 0
          @answer = challenge.try(:answer)
        else
          @answer = challenge.choose_answer
        end
        @answer =
        # 更新当前用户的总金币数
        @grade = User.where(:id => @game.user_id).pluck(:grade).first
      rescue Exception => e
        uid_logger_error("#######奖励金币异常: #{e.message}")
        raise ActiveRecord::Rollback
      end
    end

  end

  # 查看隐藏测试集
  # REDO:有漏洞，通过game详情可以看到隐藏的测试集
  def check_test_sets
    challenge = Challenge.select([:id, :score]).find(@game.challenge_id)
    user_grade = current_user.grade
    @minus_grade = challenge.score * 5
    @allowed_viewed = user_grade >= @minus_grade
    if @allowed_viewed
      current_user.update_attribute(:grade, user_grade - @minus_grade)
      @game.update_attribute(:test_sets_view, true)
      # 扣分记录
      Grade.create(:user_id => current_user.id, :container_id => @game.id, :score => -@minus_grade, :container_type => "testSet")
      max_query_index = @game.query_index.to_i
      testset_detail max_query_index, challenge
    else
      tip_exception(-1, "本操作需要扣除#{ @minus_grade }金币，您的金币不够了")
    end
  end

  # 恢复初始代码
  # 注意path为当前打开文件的path
  def reset_original_code
    path = params[:path]
    # 恢复初始代码应该找tpm的版本库
    repo_path = @myshixun.shixun.repo_path

    @content = git_fle_content(repo_path, path)
    tip_exception("初始代码为空，代码重置失败") if @content.nil?

    @language = judge_language_by_suffix(path)
    # 将tpm的代码内容同步更新到tpi
    update_file_content(@content, @myshixun.repo_path, path, current_user.git_mail, current_user.real_name, "reset_original_code")
  rescue Exception => e
    uid_logger_error("#{e.message}")
    tip_exception("初始化代码失败")
  end

  # 加载上次通过的代码
  def reset_passed_code
    path = params[:path]
    game_code = GameCode.where(:game_id => @game.try(:id), :path => path).first
    if game_code.present?
      @content = game_code.try(:new_code)
      # @content =  if @myshixun.mirror_name.select{|a| a.include?("MachineLearning") || a.include?("Python")}.present? && content.present?
      #               content.gsub(/\t/, '    ')
      #             else
      #               content
      #             end
      update_file_content(@content, @myshixun.repo_path, path, current_user.git_mail, current_user.real_name, "game passed reset")
      @language = judge_language_by_suffix(path)
    else
      tip_exception("代码重置失败，代码为空")
    end
  end

  # 获取版本库文件内容
  # 注：如果本身path传错，内容肯定也为空；fork成功后，可能短时间内也获取不到版本库内容
  # params[:status] 1: 目录树点击的请求 0：正常自动加载
  # 返回参数status ： -1 系统统一报错提示；-3 需要轮训重试，带retry参数；-4 立即重试
  def rep_content
    challenge_path = @game.challenge.try(:path)
    if challenge_path.blank?
      tip_exception("代码获取异常，请检查实训模板的评测设置是否正确")
    end
    path = @game.challenge.try(:path).split("；")[0].strip()
    path = params[:path] || path
    status = params[:status].to_i
    path = path.try(:strip)
    @language = judge_language_by_suffix(path)
    uid_logger("--rep_content: path is #{path}")
    begin
      @content = git_fle_content(@myshixun.repo_path, path) || ""
    rescue Exception => e
      # 思路： 异常首先应该考虑去恢复
      # retry为1表示已经轮训完成后还没有解决问题，这个时候需要检测异常
      begin
        # 如果模板没有问题，则通过中间层检测实训仓库是否异常
        # 监测版本库HEAD是否存在，不存在则取最新的HEAD
        gitUrl = repo_url @myshixun.repo_path
        gitUrl = Base64.urlsafe_encode64(gitUrl)
        shixun_tomcat = edu_setting('cloud_bridge')
        rep_params = {:tpiID => "#{@myshixun.id}", :tpiGitURL  => "#{gitUrl}"}
        # 监测版本库HEAD是否存在，不存在则取最新的HEAD
        uri = "#{shixun_tomcat}/bridge/game/check"
        res = uri_post uri, rep_params
        uid_logger("repo_content to bridge: res is #{res}")
        # res值：0 表示正常；-1表示有错误；-2表示代码版本库没了
        #
        if status == 0 && res
          # 版本库报错，修复不了
          if res['code'] == -1 || res['code'] == -2
            begin
              # GitService.delete_repository(repo_path: @myshixun.repo_path) if res['code'] == -1
              project_fork(@myshixun, @shixun.repo_path, current_user.login)
            rescue Exception => e
              uid_logger_error("#{e.message}")
              tip_exception("#{e.message}")
            end
          end
        end
      rescue Exception => e
        uid_logger_error(e.message)

        if @myshixun.shixun.try(:status) < 2
          tip_exception("代码获取异常，请检查实训模板的评测设置是否正确")
        else
          tip_exception(-3, "#{e.message}")
        end
      end
      # 如果报错了，并且retry 为1的时候，则fork一个新的仓库
      if params[:retry].to_i == 1
        project_fork(@myshixun, @shixun.repo_path, current_user.login)
      end
      tip_exception(0, e.message)
    end
  end

  # 编程题评测
  def game_build
    sec_key = params[:sec_key]
    game_challenge = Challenge.select([:id, :position, :picture_path, :exec_time]).find(@game.challenge_id)

    # 更新评测次数
    @game.update_column(:evaluate_count, (@game.evaluate_count.to_i + 1))
    # 清空代码评测信息
    msg = @game.run_code_message
    msg.update_attributes(:status => 0, :message => nil) if msg.present?

    # 更新时间是为了TPM端显示的更新，退出实训及访问实训的时候会更新，如果版本库地址不存在，重新去版本库中找
    myshixuns_update =
        if @myshixun.repo_name.nil?
          g = Gitlab.client
          repo_name = g.project(@myshixun.gpid).path_with_namespace
          {repo_name: repo_name}
        else
          {updated_at: Time.now}
        end
    #logger.info("#############myshixuns_update: ##{myshixuns_update}")
    @myshixun.update_attributes!(myshixuns_update)
    gitUrl = repo_ip_url @myshixun.repo_path

    #logger.info("#############giturl: ##{gitUrl}")
    gitUrl = Base64.urlsafe_encode64(gitUrl)

    shixun_tomcat = edu_setting('cloud_bridge')
    step = game_challenge.try(:position)
    mirror_repository_limit = @shixun.mirror_repositories.where(main_type: 1).select(:resource_limit).try(:first).try(:resource_limit)
    # mirror表中很很大的脚本字段，所以单独查询一个字段效果更好
    resource_limit = "echo 'ulimit -f #{mirror_repository_limit}' >> /root/.bashrc  ; source /root/.bashrc\n"
    tpmScript = @shixun.evaluate_script.nil? ? "" : Base64.urlsafe_encode64((resource_limit + @shixun.evaluate_script).gsub("\r\n", "\n"))

    # status为2已经通过关，是重新评测
    if @game.status == 2
      resubmit = params[:resubmit]
    else
      # 重新评测不影响已通关的实训状态；first为第一次评测，通过前端JS轮询获取
      @game.update_attributes!(status: 1) if params[:first].to_i == 1
    end

    testSet = []
    game_challenge.test_sets.each do |test_set|
      input = test_set.input.nil? ? "" : test_set.input.gsub("\r\n", "\n")
      output = test_set.output.nil? ? "" : test_set.output.gsub("\r\n", "\n")
      test_cases = {:input => input, :output => output, :matchRule => test_set.match_rule}
      testSet << test_cases
    end

    #logger.info("##############testSet: #{testSet}")

    testCases = Base64.urlsafe_encode64(testSet.to_json) unless testSet.blank?
    # 评测类型： 0，1，2 用于webssh的评测， 3用于vnc
    podType = @shixun.vnc_evaluate ? 3 : @shixun.webssh

    # 注意：这个地方的参数写的时候不能换行
    content_modified = params[:content_modified] # 决定文件内容是否有修改，有修改如果中间层pull没有更新，则轮询等待更新
    br_params = {:tpiID => "#{@myshixun.id}", :tpiGitURL  => "#{gitUrl}", :buildID => "#{@game.id}",
                 :instanceChallenge  => "#{step}", :testCases => "#{testCases}", :resubmit => "#{resubmit}",
                 :times => params[:first].to_i, :podType => podType, :content_modified => content_modified,
                 :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(@shixun))}",
                 :persistenceName => @shixun.identifier, :tpmScript => "#{tpmScript}", :sec_key => sec_key,
                 :timeLimit => game_challenge.exec_time, :isPublished => (@shixun.status < 2 ? 0 : 1), :trimBlank => @game.challenge&.ignore_space }

    # 评测有文件输出的需要特殊传字段 path：表示文件存储的位置
    br_params['file'] = Base64.urlsafe_encode64({path: "#{game_challenge.picture_path}"}.to_json) if game_challenge.picture_path.present?

    # needPortMapping： web类型需要pod端口映射
    br_params[:needPortMapping] = 8080 if @myshixun.mirror_name.include?("Web")

    # 私密仓库的设置
    secret_rep = @shixun.shixun_secret_repository
    logger.info("############secret_rep: #{secret_rep}")
    if secret_rep&.repo_name
      secretGitUrl = repo_ip_url secret_rep.repo_path
      br_params.merge!({secretGitUrl:  Base64.urlsafe_encode64(secretGitUrl), secretDir: secret_rep.secret_dir_path})
      #logger.info("#######br_params:#{br_params}")
    end

    # 中间层交互
    uri = "#{shixun_tomcat}/bridge/game/gameEvaluate"
    res = interface_post uri, br_params, 502, "gameEvaluate failed"

    @result = {status: 1, resubmit: resubmit, position: game_challenge.position, port: res['port'], had_done: @game.had_done}
  rescue Exception => e
    uid_logger("评测出错，详情：" + e.message)
    @result = {status: -1, message: "实训云平台繁忙（繁忙等级：502），请稍后刷新并重试", position: game_challenge.position,
               had_done: @game.had_done}
  end

  # 选择题评测
  def choose_build
    Rails.logger.error("#################{params}")
    # 选择题如果通关了，则不让再评测
    if @game.status == 2
      raise Educoder::TipException.new("您已通过该关卡")
    end
    # 更新评测次数
    @game.update_column(:evaluate_count, (@game.evaluate_count.to_i + 1))

    game_challenge = Challenge.select([:id, :position]).find(@game.challenge_id)
    user_answer = params[:answer]
    challenge_chooses_count = user_answer.length

    choose_correct_num = 0
    score = 0
    had_passed = true
    test_sets = []
    str = ""
    game_challenge.challenge_chooses.includes(:challenge_tags).each_with_index do |choose, index|
      # user_answer虽然是传的数组,但是可能存在多选择提的情况.
      user_answer_tran = user_answer[index].size > 1 ? user_answer[index].split("").sort.join("") :  user_answer[index]
      standard_answer_tran = choose.standard_answer.size > 1 ? choose.standard_answer.split("").sort.join("") :  choose.standard_answer
      correct = (user_answer_tran == standard_answer_tran)
      if str.present?
        str += ","
      end
      str += "('#{@game.id}', '#{choose.position}', '#{user_answer_tran}', '#{correct ? 1 : 0}', '#{@game.next_query_index}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
      # 只要有一题错误就不能通关
      had_passed = false if !correct
      choose_correct_num += 1 if correct

      # 全部通关的时候，需要对所得的总分记录
      # 总分的记录应该是根据每一题累加，如果该题正确，则加分
      score += choose.score if correct

      standard_answer = correct ? standard_answer_tran : -1
      sin_test_set = {:result => correct,
                      :actual_output => user_answer_tran,
                      :standard_answer => standard_answer,
                      :position => choose.position}
      test_sets << sin_test_set
    end

    # 批量插入评测结果
    uid_logger("#------------chooice score: #{score}")
    sql = "INSERT INTO outputs (game_id, test_set_position, actual_output, result, query_index, created_at, updated_at) VALUES" + str
    ActiveRecord::Base.connection.execute sql

    # 没通关或者查看了答案通关的时候经验为0
    # 通关但是查看了答案，评测的时候金币显示0（避免用户以为重复扣除），但是在关卡列表中金币显示负数
    experience = 0
    final_score = 0
    # 如果本次答题全部正确，并且之前没有通关，则进行相应的奖励，每关只要有错题，则不进行任何奖励
    # 注：扣除分数是在查看答案的时候扣除的
    if had_passed && !@game.had_passed?
      @game.update_attributes(:status => 2, :end_time => Time.now)
      # TPM实训已发布并且没有查看答案
      if @shixun.is_published? && @game.answer_open == 0
        uid_logger("@@@@@@@@@@@@@@@@@chooice score: #{score}")
        # 查看答案的时候处理final_scor和扣分记录
        experience = score
        reward_attrs = { container_id: @game.id, container_type: 'Game', score: score }
        RewardGradeService.call(@myshixun.owner, reward_attrs)
        @game.update_attribute(:final_score, score)
        final_score = score
        RewardExperienceService.call(@myshixun.owner, reward_attrs)
      end
      had_done = @game.had_done
      @myshixun.update_attribute(:status, 1) if had_done == 1
    end

    grade = @myshixun.owner.try(:grade)

    # 更新实训关联的作品分数 TODO: 更新作业需要等作业模块开了再打开
    # update_myshixun_work_score myshixun

    # 高性能取上一关、下一关
    prev_game = @game.prev_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position)
    next_game = @game.next_game(@shixun.id, @game.myshixun_id, game_challenge.position) if had_passed
    next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3

    # 高性能取上一关、下一关
    #prev_game = Game.prev_identifier(@shixun.id, @game.myshixun_id, game_challenge.position)
    #next_game = Game.next_game(@shixun.id, @game.myshixun_id, game_challenge.position)

    @result = {grade: grade,
               gold: final_score,
               experience: experience,
               challenge_chooses_count: challenge_chooses_count,
               choose_correct_num: choose_correct_num,
               test_sets: test_sets,
               prev_game: prev_game,
               next_game: next_game&.identifier}
  rescue Exception => e
    uid_logger("choose build failed #{e.message}")
    tip_exception(-1, e.message)
  end

  # 轮询获取状态
  # resubmit是在file_update中生成的，从game_build中传入的
  def game_status
    resubmit_identifier = @game.resubmit_identifier
    # 如果没有超时并且正在评测中
    # 判断评测中的状态有两种：1、如果之前没有通关的，只需判断status为1即可；如果通过关，则判断game的resubmit_identifier是否更新
    #uid_logger("################game_status: #{@game.status}")
    #uid_logger("################params[:resubmit]: #{params[:resubmit]}")
    #uid_logger("################resubmit_identifier: #{resubmit_identifier}")
    #uid_logger("################time_out: #{params[:time_out]}")
    sec_key = params[:sec_key]
    if (params[:time_out] == "false") && ((params[:resubmit].blank? && @game.status == 1) || (params[:resubmit].present? &&
        (params[:resubmit] != resubmit_identifier)))
      # 代码评测的信息
      running_code_status = @game.run_code_message.try(:status)
      running_code_message = @game.run_code_message.try(:message)
      render :json => { running_code_status: running_code_status, running_code_message: running_code_message }
    end

    uid_logger("##### resubmit_identifier is #{resubmit_identifier}")
    port = params[:port]
    score = 0
    experience = 0
    game_status = @game.status
    had_done = @game.had_done
    game_challenge = Challenge.select([:id, :score, :position, :shixun_id, :web_route, :show_type]).find(@game.challenge_id)

    if params[:resubmit].blank?   # 非重新评测
      if game_status == 2  # 通关
        if @shixun.status > 1
          score = @game.final_score  # 查看答案的时候有对最终获得金币进行处理
          experience = @game.final_score
        else
          score = 0
          experience = 0
        end
      end
    else   # 重新评测
      # 如果满足前面的条件，进入此处只可能是结果已返回并存入了数据库
      if params[:resubmit] == resubmit_identifier       # 本次重新评测结果已经返回并存入数据库
        game_status = (@game.retry_status == 2 ? 2 : 0) # retry_status是判断重新评测的通关情况。2表示通关
      end
    end

    # 实训的最大评测次数，这个值是为了优化查询，每次只取最新的最新一次评测的结果集
    max_query_index = @game.query_index
    # max_query_index = @game.outputs.first.try(:query_index)
    # 区分评测过未评测过，未评测过按需求取数据
    testset_detail max_query_index.to_i, game_challenge

    # 处理生成图片类型文件
    picture = (game_challenge.show_type.to_i == -1 || @game.picture_path.nil?) ? 0 : @game.id
    # 针对web类型的实训
    web_route = game_challenge.try(:web_route)

    server_url = @game.get_server_url if web_route.present?

    mirror_name = @shixun.mirror_name

    e_record = EvaluateRecord.where(:identifier => sec_key).first
    # 轮询结束，更新评测统计耗时
    if game_status == 0 || game_status == 2

      if e_record
        front_js = format("%.3f",  (Time.now.to_f - e_record.try(:updated_at).to_f)).to_f
        consume_time = format("%.3f", (Time.now - e_record.created_at)).to_f
        e_record.update_attributes(:consume_time => consume_time, :front_js => front_js)
      end
    end

    #uid_logger("game is #{@game.id}, record id is #{e_record.try(:id)}, time is**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
    # 记录前端总耗时
    record_consume_time = e_record.try(:pod_execute)
    max_mem = e_record.try(:max_mem)
    # 实训制作者当前拥有的金币
    grade = User.where(:id => @game.user_id).pluck(:grade).first

    # 高性能取上一关、下一关
    # 上一关、下一关
    prev_game = @game.prev_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position)
    next_game = @game.next_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position) if game_status == 2
    @base_date = {grade: grade, gold: score, experience: experience, status: game_status, had_done: had_done,
                  position: game_challenge.position, port: port, record_consume_time: record_consume_time,
                  mirror_name: mirror_name, picture: picture, web_route: web_route, star: @game.star,
                  next_game: next_game, prev_game: prev_game, max_mem: max_mem, server_url: server_url}
  end

  # 记录实训花费的时间
  # REDO:需要添加详细的说明
  def cost_time
    #return if @game.status >= 2
    max_cost_time = (Time.now.to_i - @game.open_time.to_i)
    time = params[:time].to_i < @game.cost_time.to_i ? (@game.cost_time.to_i + params[:time].to_i) : params[:time].to_i
    cost_time = time > max_cost_time ? max_cost_time : time
    @game.update_attribute(:cost_time, cost_time)
  end

  # 同步challenge的更新时间
  def sync_modify_time
    modify_time = Challenge.where(:id => @game.challenge_id).pluck(:modify_time).first
    @game.update_column(:modify_time, modify_time)
    sucess_status
  end

  # tpi弹框状态更新；true则不再显示；false每次刷新显示
  def system_update
    myshixun = Myshixun.find(params[:myshixun_id])
    myshixun.update_attribute(:system_tip, true)
    sucess_status
  end

  # 关闭webssh
  def close_webssh
    myshixun_id = @game.myshixun_id
    digest = @game.identifier + edu_setting('bridge_secret_key')
    digest_key = Digest::SHA1.hexdigest("#{digest}")
    begin
      shixun_tomcat = edu_setting('cloud_bridge')
      uri = "#{shixun_tomcat}/bridge/webssh/delete"
      Rails.logger.info("#{current_user} => cloese_webssh digest is #{digest}")
      params = {:tpiID => myshixun_id, :digestKey => digest_key, :identifier => @game.identifier}
      res = uri_post uri, params
      if res && res['code'].to_i != 0
        raise("实训云平台繁忙（繁忙等级：110）")
      end
    rescue Exception => e
      Rails.logger.error(e)
      tip_exception("实训云平台繁忙, 关闭失败!")
    end
  end

  # tpi对于实训关卡的点赞或取消点赞
  def plus_or_cancel_praise
    challenge = @game.challenge
    pt = PraiseTread.where(:praise_tread_object_id => challenge.id, :praise_tread_object_type => 'Challenge',
                           :user_id => current_user, :praise_or_tread => 1).first
    # 如果当前用户已赞过，则不能重复赞
    if pt.blank?
      PraiseTread.create!(:praise_tread_object_id => challenge.id, :praise_tread_object_type => 'Challenge',
                          :user_id => current_user.id, :praise_or_tread => 1)  if pt.blank?
      @praise = true
    else
      pt.destroy if pt.present? # 如果已赞过，则删掉这条赞（取消）；如果没赞过，则为非法请求不处理
      @praise = false
    end

    @praise_count = PraiseTread.where(:praise_tread_object_id => challenge.id, :praise_tread_object_type => 'Challenge',
                                      :praise_or_tread => 1).count
  end

  private

  # 评测测试机封装
  def testset_detail max_query_index, challenge
    # 是否允许查看隐藏的测试集，以前的power
    @allowed_hidden_testset = @identity < User::EDU_GAME_MANAGER || @game.test_sets_view #解锁的用户

    if max_query_index > 0
      #uid_logger("max_query_index is #{max_query_index} game id is #{@game.id}, challenge_id is #{challenge.id}")
      @qurey_test_sets = TestSet.find_by_sql("SELECT o.code, o.actual_output, o.out_put, o.result, o.test_set_position, o.ts_time, o.ts_mem,
                              o.query_index, t.is_public, t.input, t.output, o.compile_success FROM outputs o, games g, challenges c,
                              test_sets t where g.id=#{@game.id} and c.id=#{challenge.id} and o.query_index=#{max_query_index}
                              and g.id = o.game_id and c.id= g.challenge_id and t.challenge_id = c.id and
                              t.position =o.test_set_position order by o.query_index
                         ")
      @test_sets_count = @qurey_test_sets.count
      # 错误的测试集总数
      @sets_error_count = 0
      @qurey_test_sets.each do |set|
        @sets_error_count += 1 unless set.result
      end
      @last_compile_output = @qurey_test_sets.first['out_put'].gsub(/\n/, '<br/>').gsub(/\t/, "&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;&nbsp\;") if @qurey_test_sets.first['out_put'].present?
    else
      # 没有评测过，第一次进来后的呈现方式
      @qurey_test_sets = TestSet.find_by_sql("SELECT t.is_public, t.input, t.output, t.position
                                              FROM test_sets t where t.challenge_id = #{challenge.id}")
    end
  end

  # 实训选择题需要局部刷新或者显示的部分
  def choose_container game_challenge, game, max_query_index
    # category 1: 单选题，其它的多选题（目前只有两种）
    challenge_chooses = game_challenge.challenge_chooses.includes(:challenge_questions)
    test_sets = []
    @chooses = []

    # 选择题测试集统计
    challenge_chooses_count = challenge_chooses.count
    choose_correct_num = game.choose_correct_num(max_query_index)
    game_outputs = game.outputs.where(:query_index => max_query_index)

    # 判断用户是否有提交
    had_submmit = game_outputs.present?

    # 判断选择题是否写了标准答案
    has_answer = []
    challenge_chooses.each do |choose|
      challenge_question = []
      output = game_outputs.select{|game_output| game_output.test_set_position == choose.position}[0] unless game_outputs.blank?

      category = choose.category
      subject = choose.subject
      choose.challenge_questions.each do |question|
        position = question.position
        option_name = question.option_name
        challenge_question <<{:positon =>  position, :option_name => option_name}
      end
      # actual_output为空表示暂时没有评测答题，不允许查看
      actual_output = output.try(:actual_output).try(:strip)
      #has_answer << choose.answer  if choose.answer.present?
      # 标准答案处理，错误的不让用户查看，用-1替代
      standard_answer = (actual_output.blank? || !output.try(:result)) ? -1 : choose.standard_answer
      result = output.try(:result)
      sin_test_set = {:result => result, :actual_output => actual_output, :standard_answer => standard_answer,
                      :position => choose.position}

      sin_choose = {:category => category, :subject => subject, :challenge_question => challenge_question}
      @chooses << sin_choose
      test_sets << sin_test_set
    end
    @has_answer = true # 选择题永远都有答案
    @choose_test_cases = {:had_submmit => had_submmit, :challenge_chooses_count => challenge_chooses_count,
                          :choose_correct_num => choose_correct_num, :test_sets => test_sets}
  end


  def find_game
    @game = Game.find_by_identifier(params[:identifier])
    if @game.blank?
      normal_status(404, "...")
      return
    end

    @myshixun = @game.myshixun
  end

  def find_shixun
    @shixun = Shixun.find(@myshixun.shixun_id)
  end

  # http://localhost:3000/tasks/hcie39pw2bjn
  # 可以访问条件：学员本身；管理员；TPM制作者
  def allowed
    @identity = current_user.game_identity(@game)
    raise Educoder::TipException.new(403, "..") if @identity > User::EDU_GAME_MANAGER
  end
  # identity用户身份
  def user_next_game(shixun, challenge, game, identity)
    next_game = game.next_of_current_game(shixun.id, game.myshixun_id, challenge.position)
    # 实训允许跳关 、 当前关卡已经通关、 用户是已认证的老师以上权限的人，允许跳关
    if shixun.task_pass || game.status == 2 || identity <= User::EDU_CERTIFICATION_TEACHER
      next_game
    else
      nil
    end
  end

  # 更新关卡状态和一些学习进度
  def update_game_parameter game
    game.update_attribute(:status, 0) if game.status == 1
    # 第一次进入关卡更新时间
    game.update_attributes(status: 0, open_time: Time.now) if game.open_time.blank? || game.status == 3
    # 开启实训更新myshixuns的时间，方便跟踪用于的学习进度。
    game.myshixun.update_column(:updated_at, Time.now)
  end

  # vnc连接
  def get_vnc_link game
    begin
      shixun = game.myshixun.shixun
      shixun_tomcat = edu_setting('cloud_bridge')
      service_host = edu_setting('vnc_url')
      tpiGitURL = "#{edu_setting('git_address_domain')}/#{game.myshixun.repo_path}"

      uri = "#{shixun_tomcat}/bridge/vnc/getvnc"
      params = {tpiID: game.myshixun.id, :containers => "#{Base64.urlsafe_encode64(shixun_container_limit(shixun))}",
                tpiGitURL: tpiGitURL}
      res = uri_post uri, params
      if res && res['code'].to_i != 0
        raise("实训云平台繁忙（繁忙等级：99）")
      end
      @vnc_url = res['showServer']
      # @vnc_url =
      #     if request.subdomain == "pre-newweb" || request.subdomain == "test-newweb"
      #       # 无域名版本
      #       "http://#{service_host}:#{res['port']}/vnc_lite.html?password=headless"
      #     else
      #       # 有域名版本
      #       "https://#{res['port']}.#{service_host}/vnc_lite.html?password=headless"
      #     end
      @vnc_evaluate = shixun.vnc_evaluate
    rescue Exception => e
      Rails.logger.error(e.message)
    end
  end

  # 删除pod
  def delete_vnc game
    myshixun_id = game.myshixun_id
    digest = game.identifier + edu_setting('bridge_secret_key')
    digest_key = Digest::SHA1.hexdigest("#{digest}")
    begin
      shixun_tomcat = edu_setting('cloud_bridge')
      uri = "#{shixun_tomcat}/bridge/vnc/delete"
      Rails.logger.info("#{current_user} => cloese_vnc digest is #{digest}")
      params = {:tpiID => myshixun_id, :digestKey => digest_key, :identifier => game.identifier}
      res = uri_post uri, params
      if res && res['code'].to_i != 0
        raise("实训云平台繁忙（繁忙等级：110）")
      end
    end
  end

  # 根据文件后缀判断语言,默认使用shell渲染
  def judge_language_by_suffix filename
    return "shell" if filename.blank?
    suffix = filename.split(".").last
    return "shell" if suffix.blank?
    case suffix.downcase
    when "py"  then "python"
    when "h", "c" then "c"
    when "cpp", "java", "php", "html", "css", "scss", "go", "r", "graphql", "swift", "xml", "yaml", "json", "lua",
        "scheme", "less", "ini" then suffix.downcase
    when "coffee", "litcoffee" then "coffeescript"
    when "js" then "javascript"
    when "cs" then "csharp"
    when "kt" then "kotlin"
    when "md" then "markdown"
    when "sql" then "mysql"
    when "m", "mm" then "objective-c"
    when "pas" then "pascal"
    when "perl", "pl" then "perl"
    when "rb" then "ruby"
    when "rs", "rust" then "rust"
    when "sh" then "shell"
    else
      "shell"
    end
  end

end
