class CanvasController < ApplicationController
  before_action :load_vars

  before_action :check_view_canvas, only: [:edit, :full_zoom, :medium_zoom, :small_zoom]
  before_action :check_edit_canvas, only: [:edit, :update]

  def edit
    render partial: 'canvas/edit',
      locals: { experiment: @experiment, my_modules: @my_modules },
      :content_type => 'text/html'
  end

  def full_zoom
    render partial: 'canvas/full_zoom',
      locals: { experiment: @experiment, my_modules: @my_modules },
      :content_type => 'text/html'
  end

  def medium_zoom
    render partial: 'canvas/medium_zoom',
      locals: { experiment: @experiment, my_modules: @my_modules },
      :content_type => 'text/html'
  end

  def small_zoom
    render partial: 'canvas/small_zoom',
      locals: { experiment: @experiment, my_modules: @my_modules },
      :content_type => 'text/html'
  end

  def update
    # Make sure that remove parameter is valid
    to_archive = []
    if update_params[:remove].present?
      to_archive = update_params[:remove].split(',')
      if to_archive.all? { |id| can_archive_module?(MyModule.find_by(id: id)) }
        to_archive.collect!(&:to_i)
      else
        return render_403
      end
    end

    # Make sure connections parameter is valid
    connections = []
    if update_params[:connections].present?
      conns = update_params[:connections].split(',')
      if conns.length.even? && conns.all? { |c| c.is_a? String }
        conns.each_slice(2).each do |c|
          connections << [c[0], c[1]]
        end
      else
        return render_403
      end
    end

    # Make sure positions parameter is valid
    positions = {}
    if update_params[:positions].present?
      poss = update_params[:positions].split(';')
      (poss.collect { |pos| pos.split(',') }).each_with_index do |pos, _|
        unless pos.length == 3 && pos[0].is_a?(String) && float?(pos[1]) && float?(pos[2])
          return render_403
        end
        x = pos[1].to_i
        y = pos[2].to_i

        positions[pos[0]] = { x: x, y: y }
      end
    end

    # Make sure that to_add is an array of strings,
    # as well as that positions for newly added modules exist
    to_add = []
    if update_params[:add].present? &&
       update_params['add-names'].present?
      ids = update_params[:add].split(',')
      names = update_params['add-names'].split('|')
      if ids.length == names.length &&
         ids.all? { |id| id.is_a?(String) && positions.include?(id) } &&
         names.all? { |name| name.is_a? String }
        ids.each_with_index do |id, i|
          to_add << { id: id, name: names[i],
                      x: positions[id][:x], y: positions[id][:y] }
        end
      else
        return render_403
      end
    end

    # Make sure rename parameter is valid
    to_rename = {}
    if update_params[:rename].present?
      begin
        to_rename = JSON.parse(update_params[:rename])
        # Okay, JSON parsed!
        unless to_rename.is_a?(Hash) &&
               to_rename.keys.all? do |id|
                 id.is_a?(String) &&
                 can_manage_module?(MyModule.find_by_id(id))
               end &&
               to_rename.values.all? { |new_name| new_name.is_a? String }
          return render_403
        end
      rescue
        return render_403
      end
    end

    # Make sure move parameter is valid
    to_move = {}
    if update_params[:move].present?
      begin
        to_move = JSON.parse(update_params[:move])
        # Okay, JSON parsed!
        unless to_move.is_a?(Hash) &&
               to_move.keys.all? do |id|
                 !is_int?(id) || can_move_module?(MyModule.find_by(id: id))
               end &&
               to_move.values.all? do |exp_id|
                 can_manage_experiment?(Experiment.find_by(id: exp_id))
               end
          return render_403
        end
      rescue StandardError
        return render_403
      end
    end

    # Distinguish between moving modules/module_groups
    to_move_groups = {}
    to_move.each do |key, value|
      if key =~ /.*,.*/
        to_move_groups[key.split(',')] = value
        to_move.delete(key)
      end
    end

    # Make sure that to_clone is an array of pairs,
    # as well as that all IDs exist
    to_clone = {}
    if update_params[:cloned].present?
      clones = update_params[:cloned].split(';')
      (clones.collect { |v| v.split(',') }).each do |val|
        if val.length == 2 && is_int?(val[0]) && val[1].is_a?(String) &&
           to_add.any? { |m| m[:id] == val[1] }
          to_clone[val[1]] = val[0]
        else
          return render_403
        end
      end
    end

    # Call the "master" function to do all the updating for us
    unless @experiment.update_canvas(
      to_archive,
      to_add,
      to_rename,
      to_move,
      to_move_groups,
      to_clone,
      connections,
      positions,
      current_user
    )
      return render_403
    end

    # Save activities that modules were archived
    to_archive.each do |module_id|
      my_module = MyModule.find_by_id(module_id)
      next if my_module.blank?
    end

    flash[:success] = t('experiments.canvas.update.success_flash')
    redirect_to canvas_experiment_path(@experiment)
  end

  private

  def update_params
    params.permit(
      :id,
      :connections,
      :positions,
      :add,
      "add-names",
      :rename,
      :move,
      :cloned,
      :remove,
      "module-groups"
    )
  end

  def load_vars
    @experiment = Experiment.find_by_id(params[:id])
    unless @experiment
      respond_to do |format|
        format.html { render_404 and return }
        format.any(:xml, :json, :js) { render(json: { redirect_url: not_found_url }, status: :not_found) and return }
      end
    end

    @my_modules = @experiment.my_modules.active
  end

  def check_edit_canvas
    render_403 and return unless can_manage_experiment?(@experiment)
  end

  def check_view_canvas
    render_403 unless can_read_experiment?(@experiment)
  end

  # Check if given value is "integer" string (e.g. "15")
  def is_int?(val)
    /\A[-+]?\d+\z/ === val
  end

  def float?(val)
    true if Float(val)
  rescue ArgumentError
    false
  end
end
