require "#{Rails.root}/app/utilities/users_generator"
require "#{Rails.root}/app/utilities/renaming_util"
require "#{Rails.root}/test/helpers/fake_test_helper"
include UsersGenerator
include RenamingUtil
include FakeTestHelper

namespace :db do
  NR_TEAMS = 4
  NR_USERS = 100
  NR_SAMPLE_TYPES = 20
  NR_SAMPLE_GROUPS = 20
  NR_CUSTOM_FIELDS = 20
  NR_SAMPLES = 100
  NR_PROTOCOLS = 20
  NR_PROTOCOL_KEYWORDS = 20
  NR_PROJECTS = 3
  NR_EXPERIMENTS = 4
  NR_MODULE_GROUPS = 4
  NR_MODULES = 4
  NR_STEPS = 3
  NR_RESULTS = 4
  NR_REPORTS = 4
  NR_COMMENTS = 10
  RATIO_USER_TEAMS = 0.5
  NR_MAX_USER_TEAMS = 20
  RATIO_CUSTOM_FIELDS = 0.7
  RATIO_SAMPLE_CUSTOM_FIELDS = 0.6
  RATIO_PROTOCOL_KEYWORDS = 0.3
  THRESHOLD_PROTOCOL_IN_MODULE_LINKED = 0.5
  THRESHOLD_PROTOCOL_PUBLIC = 0.6
  THRESHOLD_PROTOCOL_ARCHIVED = 0.2
  NR_MAX_USER_PROJECTS = 15
  RATIO_USER_PROJECTS = 0.5
  RATIO_COMMENTS = 0.7
  NR_MAX_USER_MODULES = 15
  RATIO_USER_MODULES = 0.5
  NR_MAX_SAMPLE_MODULES = 10
  RATIO_SAMPLE_MODULES = 0.7
  RATIO_MODULE_MODULE_GROUPS = 0.8
  RATIO_EDGES = 0.7
  RATIO_STEP_COMPLETED = 0.5
  NR_MAX_STEP_ATTACHMENTS = 2
  RATIO_STEP_ATTACHMENTS = 0.2
  NR_MAX_CHECKLIST_ITEMS = 20
  RATIO_CHECKLIST_ITEM = 0.2
  RATIO_CHECKLIST_ITEM_CHECKED = 0.5
  RATIO_RESULT_ARCHIVED = 0.2
  RATIO_REPORT_ELEMENTS = 0.75

  THRESHOLD_ARCHIVED = 0.2
  THRESHOLD_RESTORED = 0.9

  MIN_FILE_SIZE = 0.01
  MAX_FILE_SIZE = 0.1

  desc "Drops the database, sets it up and inserts fake data for " +
       "the current RAILS_ENV. WARNING: THIS WILL ERASE ALL " +
       "CURRENT DATA IN THE DATABASE."
  task :fake => :environment do
    Rake::Task["db:drop"].reenable
    Rake::Task["db:drop"].invoke
    Rake::Task["db:setup"].reenable
    Rake::Task["db:setup"].invoke
    Rake::Task["db:fake:generate"].reenable
    Rake::Task["db:fake:generate"].invoke
  end

  namespace :fake do
    desc "Generates fake data & inserts it into database for the " +
         "current RAILS_ENV."
    task :generate => :environment do
      require 'rgl/base'
      require 'rgl/adjacency'
      require 'rgl/topsort'

      puts "Verbose? (Y/n)"
      res = $stdin.gets.to_s.downcase.strip
      unless res.in?(["", "y", "n"]) then
        puts "Invalid parameter, exiting"
        return
      end
      verbose = res.in?(["", "y"])

      puts "Simple seeding? (Y/n)"
      res = $stdin.gets.to_s.downcase.strip
      unless res.in?(["", "y", "n"]) then
        puts "Invalid parameter, exiting"
        return
      end
      simple = res.in?(["", "y"])

      if simple
        puts "Choose the size of generated dataset(T - tiny, " +
             "s - small, m - medium, l - large, h -huge)"
        res = $stdin.gets.to_s.downcase.strip
        unless res.in?(["", "t", "s", "m", "l", "h"]) then
          puts "Invalid parameter, exiting"
          return
        end

        case res
          when "", "t"
            factor = 0.5
          when "s"
            factor = 1
          when "m"
            factor = 5
          when "l"
            factor = 20
          when "h"
            factor = 100
        end

        nr_team = NR_TEAMS * factor
        nr_users = NR_USERS * factor
        nr_sample_types = NR_SAMPLE_TYPES * factor
        nr_sample_groups = NR_SAMPLE_GROUPS * factor
        nr_custom_fields = NR_CUSTOM_FIELDS * factor
        nr_samples = NR_SAMPLES * factor
        nr_protocols = NR_PROTOCOLS * factor
        nr_protocol_keywords = NR_PROTOCOL_KEYWORDS * factor
        nr_projects = NR_PROJECTS * factor
        nr_experiments = NR_EXPERIMENTS * factor
        nr_module_groups = NR_MODULE_GROUPS * factor
        nr_modules = NR_MODULES * factor
        nr_steps = NR_STEPS * factor
        nr_results = NR_RESULTS * factor
        nr_reports = NR_REPORTS * factor
        nr_comments = NR_COMMENTS * factor
      else
        puts "Type in the number of seeded teams"
        nr_team = $stdin.gets.to_i
        puts "Type in the number of seeded users"
        nr_users = $stdin.gets.to_i
        puts "Type in the number of seeded sample types " +
             "for each team"
        nr_sample_types = $stdin.gets.to_i
        puts "Type in the number of seeded sample groups for " +
             "each team"
        nr_sample_groups = $stdin.gets.to_i
        puts "Type in the max. number of seeded custom fields " +
             "for each team"
        nr_custom_fields = $stdin.gets.to_i
        puts "Type in the number of seeded samples for each team"
        nr_samples = $stdin.gets.to_i
        puts "Type in the number of seeded protocols for each team"
        nr_protocols = $stdin.gets.to_i
        puts "Type in the number of seeded protocol keywords for each team"
        nr_protocol_keywords = $stdin.gets.to_i
        puts "Type in the number of seeded projects for each team"
        nr_projects = $stdin.gets.to_i
        puts "Type in the number of seeded experiments for each project"
        nr_experiments = $stdin.gets.to_i
        puts "Type in the number of seeded workflows for each experiment"
        nr_module_groups = $stdin.gets.to_i
        puts "Type in the number of seeded modules for each experiment"
        nr_modules = $stdin.gets.to_i
        puts "Type in the number of seeded steps for each module"
        nr_steps = $stdin.gets.to_i
        puts "Type in the number of seeded results for each module"
        nr_results = $stdin.gets.to_i
        puts "Type in the number of seeded reports for each project"
        nr_reports = $stdin.gets.to_i
        puts "Type in the max. number of seeded comments for each " +
             "commentable item"
        nr_comments = $stdin.gets.to_i
      end

      begin
        ActiveRecord::Base.transaction do

          puts "Generating fake teams..."
          taken_team_names = []
          for _ in 1..nr_team
            begin
              name = Faker::University.name
            end while name.in? taken_team_names
            taken_team_names << name

            Team.create(
              name: name,
              description: rand >= 0.7 ? Faker::Lorem.sentence : nil
            )
          end

          all_teams = Team.all

          puts "Generating fake users..."
          taken_emails = []
          for _ in 1..nr_users
            begin
              if rand >= 0.8
                name = generate_got_name
              else
                name = Faker::Name.name
              end
              email_name = name.downcase.remove(".").split(" ").join(".")
              password = Faker::Internet.password(10, 20)
              email = Faker::Internet.free_email(email_name)
            end while email.in? taken_emails
            taken_emails << email

            user = create_user(
              name,
              email,
              password,
              true,
              nil,
              []
            )
            user.update(
              confirmed_at: Faker::Date.backward(30),
            )
            if verbose then
              puts "  Generated user #{name} (email: #{email}, " +
                   "password: #{password})"
            end

            # Randomly assign user to teams
            taken_team_ids = []
            for _ in 1..[NR_MAX_USER_TEAMS, all_teams.count].min
              if rand <= RATIO_USER_TEAMS then
                begin
                  team = pluck_random(all_teams)
                end while team.id.in? taken_team_ids
                taken_team_ids << team.id

                UserTeam.create(
                  user: user,
                  team: team,
                  role: rand(0..2)
                )
              end
            end
          end

          puts "Generating fake sample types..."
          all_teams.find_each do |team|
            for _ in 1..nr_sample_types
              SampleType.create(
                name: Faker::Commerce.department(4),
                team: team
              )
            end
          end

          puts "Generating fake sample groups..."
          all_teams.find_each do |team|
            for _ in 1..nr_sample_groups
              SampleGroup.create(
                name: Faker::Commerce.color,
                team: team,
                color: generate_color
              )
            end
          end

          puts "Generating fake custom fields..."
          all_teams.find_each do |team|
            for _ in 1..nr_custom_fields
              if rand <= RATIO_CUSTOM_FIELDS then
                CustomField.create(
                  name: Faker::Team.state,
                  team: team,
                  user: pluck_random(team.users)
                )
              end
            end
          end

          puts "Generating fake samples..."
          all_teams.find_each do |team|
            for _ in 1..nr_samples
              sample = Sample.create(
                name: Faker::Book.title,
                team: team,
                user: pluck_random(team.users),
                sample_type: pluck_random(team.sample_types),
                sample_group: pluck_random(team.sample_groups)
              )

              # Add some custom fields to sample
              team.custom_fields.find_each do |cf|
                if rand <= RATIO_SAMPLE_CUSTOM_FIELDS then
                  SampleCustomField.create(
                    sample: sample,
                    custom_field: cf,
                    value: Faker::Team.state
                  )
                end
              end
            end
          end

          puts "Generating fake protocol keywords"
          all_teams.find_each do |team|
            taken_kw_names = []
            for _ in 1..nr_protocol_keywords
              begin
                name = Faker::Book.genre
              end while name.in? taken_kw_names
              taken_kw_names << name
              ProtocolKeyword.create(
                team: team,
                name: name
              )
            end
          end

          puts "Generating fake repository protocols..."
          all_teams.find_each do |team|
            for _ in 1..nr_protocols
              protocol = generate_fake_protocol(team,
                                                nil,
                                                nr_steps,
                                                nr_comments)

              if verbose then
                puts "    Generated protocol #{protocol.name}"
              end
            end
          end

          puts "Generating fake projects..."
          all_teams.find_each do |team|
            taken_project_names = []
            for _ in 1..nr_projects
              begin
                name = Faker::Company.name[0..29]
              end while name.in? taken_project_names
              taken_project_names << name

              author = pluck_random(team.users)
              created_at = Faker::Time.backward(500)
              last_modified_by = pluck_random(team.users)
              archived_by = pluck_random(team.users)
              archived_on = Faker::Time.between(created_at, DateTime.now)
              restored_by = pluck_random(team.users)
              restored_on = Faker::Time.between(archived_on, DateTime.now)
              status = random_status

              project = Project.create(
                visibility: rand(0..1),
                name: name,
                due_date: nil,
                team: team,
                created_by: author,
                created_at: created_at,
                last_modified_by: last_modified_by,
                archived: status == :archived,
                archived_by: status.in?([:archived, :restored]) ?
                  archived_by : nil,
                archived_on: status.in?([:archived, :restored]) ?
                  archived_on : nil,
                restored_by: status == :restored ? restored_by : nil,
                restored_on: status == :restored ? restored_on : nil
              )
              # Automatically assign project author onto project
              UserProject.create(
                user: author,
                project: project,
                role: 0,
                created_at: created_at
              )

              # Activities
              Activity.create(
                type_of: :create_project,
                user: author,
                project: project,
                message: I18n.t(
                  "activities.create_project",
                  user: author.full_name,
                  project: project.name
                ),
                created_at: created_at
              )
              if status.in?([:archived, :restored]) then
                Activity.create(
                  type_of: :archive_project,
                  user: archived_by,
                  project: project,
                  message: I18n.t(
                    "activities.archive_project",
                    user: archived_by.full_name,
                    project: project.name
                  ),
                  created_at: archived_on
                )
              end
              if status == :restored then
                Activity.create(
                  type_of: :restore_project,
                  user: restored_by,
                  project: project,
                  message: I18n.t(
                    "activities.restore_project",
                    user: restored_by.full_name,
                    project: project.name
                  ),
                  created_at: restored_on
                )
              end

              # Assign users onto the project
              taken_user_ids = []
              for _ in 2..[NR_MAX_USER_PROJECTS, team.users.count].min
                if rand <= RATIO_USER_PROJECTS
                  begin
                    user = pluck_random(team.users)
                  end while user.id.in? taken_user_ids
                  taken_user_ids << user.id

                  assigned_on = Faker::Time.backward(500)
                  assigned_by = pluck_random(project.users)
                  up = UserProject.create(
                    user: user,
                    project: project,
                    role: rand(0..3),
                    created_at: assigned_on,
                    assigned_by: assigned_by
                  )
                  Activity.create(
                    type_of: :assign_user_to_project,
                    user: assigned_by,
                    project: project,
                    message: I18n.t(
                      "activities.assign_user_to_project",
                      assigned_user: user.full_name,
                      role: up.role_str,
                      project: project.name,
                      assigned_by_user: assigned_by.full_name
                    ),
                    created_at: assigned_on
                  )
                end
              end

              # Add some comments
              for _ in 1..nr_comments
                generate_fake_project_comment(project) if rand <= RATIO_COMMENTS
              end
            end
          end

          puts "Generating fake experiments..."
          Project.find_each do |project|
            for _ in 1..nr_experiments
              status = random_status
              created_at = Faker::Time.backward(500)
              archived_by = pluck_random(project.users)
              archived_on = Faker::Time.between(created_at, DateTime.now)
              restored_by = pluck_random(project.users)
              restored_on = Faker::Time.between(archived_on, DateTime.now)

              author = pluck_random(project.users)
              Experiment.create(
                name: Faker::Hacker.noun,
                description: Faker::Hipster.sentence,
                project: project,
                created_at: created_at,
                created_by: author,
                last_modified_by: author,
                archived: status.in?([:active, :restored]),
                archived_on: status.in?([:archived, :restored]) ?
                  archived_on : nil,
                archived_by: status.in?([:archived, :restored]) ?
                  archived_by : nil,
                restored_on: status == :restored ? restored_on : nil,
                restored_by: status == :restored ? restored_by : nil
              )
            end
          end

          puts "Generating fake workflows..."
          Experiment.find_each do |experiment|
            for _ in 1..nr_module_groups
              MyModuleGroup.create(
                name: Faker::Hacker.noun,
                experiment: experiment
              )
            end
          end

          puts "Generating fake modules..."
          total_experiment = Experiment.count
          Experiment.find_each.with_index do |experiment, i|
            if verbose then
              puts "  Generating modules for experiment #{experiment.name} " +
                   "(#{i + 1} of #{total_experiment})..."
            end
            project = experiment.project
            taken_pos = []
            for _ in 1..nr_modules
              begin
                x = rand(0..nr_modules) * 32
                y = rand(0..nr_modules) * 16
              end while [x, y].in? taken_pos
              taken_pos << [x, y]

              status = random_status
              author = pluck_random(team.users)
              created_at = Faker::Time.backward(500)
              archived_by = pluck_random(team.users)
              archived_on = Faker::Time.between(created_at, DateTime.now)
              restored_by = pluck_random(team.users)
              restored_on = Faker::Time.between(archived_on, DateTime.now)

              my_module = MyModule.create(
                name: Faker::Hacker.verb,
                created_by: author,
                created_at: created_at,
                due_date: rand <= 0.5 ?
                  Faker::Time.forward(500) : nil,
                description: rand <= 0.5 ?
                  Faker::Hacker.say_something_smart : nil,
                x: x,
                y: y,
                experiment: experiment,
                my_module_group: status == :archived ?
                  nil :
                  (rand <= RATIO_MODULE_MODULE_GROUPS ?
                    pluck_random(experiment.my_module_groups) : nil
                  ),
                archived: status == :archived,
                archived_on: status.in?([:archived, :restored]) ?
                  archived_on : nil,
                archived_by: status.in?([:archived, :restored]) ?
                  archived_by : nil,
                restored_on: status == :restored ? restored_on : nil,
                restored_by: status == :restored ? restored_by : nil
              )

              # Activities
              Activity.create(
                type_of: :create_module,
                user: author,
                project: my_module.experiment.project,
                my_module: my_module,
                message: I18n.t(
                  "activities.create_module",
                  user: author.full_name,
                  module: my_module.name
                ),
                created_at: created_at
              )
              if status.in?([:archived, :restored]) then
                Activity.create(
                  type_of: :archive_module,
                  user: archived_by,
                  project: my_module.experiment.project,
                  my_module: my_module,
                  message: I18n.t(
                    "activities.archive_module",
                    user: archived_by.full_name,
                    module: my_module.name
                  ),
                  created_at: archived_on
                )
              end
              if status == :restored then
                Activity.create(
                  type_of: :restore_module,
                  user: restored_by,
                  project: my_module.experiment.project,
                  my_module: my_module,
                  message: I18n.t(
                    "activities.restore_module",
                    user: restored_by.full_name,
                    module: my_module.name
                  ),
                  created_at: restored_on
                )
              end

              if verbose then
                puts "    Generated module #{my_module.name}"
              end

              # Assign some users onto module
              taken_user_ids = []
              for _ in 1..[NR_MAX_USER_MODULES, project.users.count].min
                if rand <= RATIO_USER_MODULES then
                  begin
                    user = pluck_random(project.users)
                  end while user.id.in? taken_user_ids
                  taken_user_ids << user.id

                  assigned_on = Faker::Time.backward(500)
                  assigned_by = pluck_random(my_module.experiment.project.users)
                  UserMyModule.create(
                    user: user,
                    my_module: my_module,
                    assigned_by: pluck_random(project.users),
                    created_at: assigned_on
                  )
                  Activity.create(
                    type_of: :assign_user_to_module,
                    user: assigned_by,
                    project: my_module.experiment.project,
                    my_module: my_module,
                    message: I18n.t(
                      "activities.assign_user_to_module",
                      assigned_user: user.full_name,
                      module: my_module.name,
                        assigned_by_user: assigned_by.full_name
                    ),
                    created_at: assigned_on
                  )
                end
              end

              # Assign some samples onto module
              taken_sample_ids = []
              for _ in 1..[
                NR_MAX_SAMPLE_MODULES,
                project.team.samples.count
              ].min
                if rand <= RATIO_SAMPLE_MODULES then
                  begin
                    sample = pluck_random(project.team.samples)
                  end while sample.id.in? taken_sample_ids
                  taken_sample_ids << sample.id

                  SampleMyModule.create(
                    sample: sample,
                    my_module: my_module
                  )
                end
              end

              # Add some comments
              for _ in 1..nr_comments
                generate_fake_module_comment(my_module) if rand <= RATIO_COMMENTS
              end
            end

            # Generate some connections between modules
            experiment.my_module_groups.find_each do |my_module_group|
              if my_module_group.my_modules.empty? or
                my_module_group.my_modules.count == 1
                # If any module group doesn't contain
                # any modules (or has only 1 module), remove it
                my_module_group.destroy
              else
                # Make connections between project modules,
                # keeping in mind not to generate cycles
                n = my_module_group.my_modules.count
                max_edges = (n - 1) * n / 2

                dg = RGL::DirectedAdjacencyGraph.new
                for _ in 1..max_edges
                  if rand <= RATIO_EDGES
                    begin
                      m1 = pluck_random(my_module_group.my_modules)
                      m2 = pluck_random(my_module_group.my_modules)
                    end while (
                      m1 == m2 or
                      dg.has_edge?(m1.id, m2.id)
                    )

                    # Only add edge if it won't make graph cyclic
                    dg.add_edge(m1.id, m2.id)
                    if dg.acyclic?
                      Connection.create(
                        input_id: m1.id,
                        output_id: m2.id
                      )
                    else
                      dg.remove_edge(m1.id, m2.id)
                    end
                  end
                end

                # Set order number for each module in group
                topsort = dg.topsort_iterator.to_a
                my_module_group.my_modules.each do |mm|
                  if topsort.include? mm.id
                    mm.workflow_order = topsort.find_index(mm.id)
                    mm.save!
                  end
                end
              end
            end
          end

          puts 'Generating fake module protocols...'
          Experiment.find_each do |experiment|
            experiment.my_modules.find_each do |my_module|
              generate_fake_protocol(experiment.project.team,
                                     my_module,
                                     nr_steps,
                                     nr_comments)
            end
          end

          puts "Generating fake module results..."
          Experiment.find_each do |experiment|
            experiment.my_modules.find_each do |my_module|
              for _ in 1..nr_results
                user = pluck_random(my_module.users)
                created_at = Faker::Time.backward(500)
                archived_on = Faker::Time.between(created_at, DateTime.now)
                restored_on = Faker::Time.between(archived_on, DateTime.now)
                status = random_status

                result = Result.new(
                  name: Faker::Hacker.abbreviation[0..49],
                  my_module: my_module,
                  user: user,
                  created_at: created_at,
                  archived: status == :archived,
                  archived_by: status.in?([:archived, :restored]) ?
                    user : nil,
                  archived_on: status.in?([:archived, :restored]) ?
                    archived_on : nil,
                  restored_by: status == :restored ? user : nil,
                  restored_on: status == :restored ? restored_on : nil
                )

                type = [:text, :asset, :table][rand(0..2)]
                case type
                  when :text
                    result.result_text = ResultText.new(
                      text: Faker::Hipster.paragraph,
                    )
                    str = "activities.add_text_result"
                    str2 = "activities.archive_text_result"
                  when :asset
                    result.asset = Asset.new(
                      file: generate_file(rand(MIN_FILE_SIZE..MAX_FILE_SIZE)),
                      created_by: result.user
                    )
                    str = "activities.add_asset_result"
                    str2 = "activities.archive_asset_result"
                  when :table
                    result.table = Table.new(
                      contents: generate_table_contents(rand(30), rand(150)),
                      created_by: result.user
                    )
                    str = "activities.add_table_result"
                    str2 = "activities.archive_table_result"
                end

                result.save

                # Add activities
                Activity.create(
                  type_of: :add_result,
                  project: experiment.project,
                  my_module: my_module,
                  user: user,
                  created_at: created_at,
                  message: I18n.t(
                    str,
                    user: user.full_name,
                    result: result.name
                  )
                )
                if status.in?([:archived, :restored]) then
                  Activity.create(
                    type_of: :archive_result,
                    user: user,
                    project: experiment.project,
                    my_module: my_module,
                    message: I18n.t(
                      str2,
                      user: user.full_name,
                      result: result.name
                    ),
                    created_at: archived_on
                  )
                end
                # Currently, there is no way to restore archived results

                # Add some comments
                for _ in 1..nr_comments
                  generate_fake_result_comment(result) if rand <= RATIO_COMMENTS
                end
              end
            end
          end

          puts "Generating fake reports..."
          Experiment.find_each do |experiment|
            project = experiment.project
            for _ in 1..nr_reports
              taken_project_names = []
              begin
                name = Faker::Company.bs
                user = pluck_random(project.users)
              end while [user, name].in? taken_project_names
              taken_project_names << [user, name]

              report = Report.create(
                name: name,
                description: Faker::Hipster.sentence,
                project: project,
                user: user
              )

              # Generate the oh-so-many report elements
              ReportElement.create(
                sort_order: 0,
                position: 0,
                report: report,
                type_of: :project_header
              )
              experiment.my_modules.each do |my_module|
                if rand <= RATIO_REPORT_ELEMENTS then
                  re_my_module = ReportElement.create(
                    sort_order: rand <= 0.5 ? 0 : 1,
                    position: 0,
                    report: report,
                    type_of: :my_module,
                    my_module: my_module
                  )

                  my_module.protocol.completed_steps.each do |step|
                    if rand <= RATIO_REPORT_ELEMENTS then
                      re_step = ReportElement.create(
                        sort_order: rand <= 0.5 ? 0 : 1,
                        position: 0,
                        report: report,
                        type_of: :step,
                        step: step,
                        parent: re_my_module
                      )

                      step.checklists.each do |checklist|
                        if rand <= RATIO_REPORT_ELEMENTS then
                          ReportElement.create(
                            sort_order: rand <= 0.5 ? 0 : 1,
                            position: 0,
                            report: report,
                            type_of: :step_checklist,
                            checklist: checklist,
                            parent: re_step
                          )
                        end
                      end
                      step.assets.each do |asset|
                        if rand <= RATIO_REPORT_ELEMENTS then
                          ReportElement.create(
                            sort_order: rand <= 0.5 ? 0 : 1,
                            position: 0,
                            report: report,
                            type_of: :step_asset,
                            asset: asset,
                            parent: re_step
                          )
                        end
                      end
                      step.tables.each do |table|
                        if rand <= RATIO_REPORT_ELEMENTS then
                          ReportElement.create(
                            sort_order: rand <= 0.5 ? 0 : 1,
                            position: 0,
                            report: report,
                            type_of: :step_table,
                            table: table,
                            parent: re_step
                          )
                        end
                      end
                      if rand <= RATIO_REPORT_ELEMENTS then
                        ReportElement.create(
                          sort_order: rand <= 0.5 ? 0 : 1,
                          position: 0,
                          report: report,
                          type_of: :step_comments,
                          step: step,
                          parent: re_step
                        )
                      end
                    end
                  end

                  my_module.results.each do |result|
                    if rand <= RATIO_REPORT_ELEMENTS then
                      if result.is_asset
                        type_of = :result_asset
                      elsif result.is_table
                        type_of = :result_table
                      else
                        type_of = :result_text
                      end
                      re_result = ReportElement.create(
                        sort_order: rand <= 0.5 ? 0 : 1,
                        position: 0,
                        report: report,
                        type_of: type_of,
                        result: result,
                        parent: re_my_module
                      )

                      if rand <= RATIO_REPORT_ELEMENTS then
                        ReportElement.create(
                          sort_order: rand <= 0.5 ? 0 : 1,
                          position: 0,
                          report: report,
                          type_of: :result_comments,
                          result: result,
                          parent: re_result
                        )
                      end
                    end
                  end

                  if rand <= RATIO_REPORT_ELEMENTS then
                    ReportElement.create(
                      sort_order: rand <= 0.5 ? 0 : 1,
                      position: 0,
                      report: report,
                      type_of: :my_module_activity,
                      my_module: my_module,
                      parent: re_my_module
                    )
                  end

                  if rand <= RATIO_REPORT_ELEMENTS then
                    ReportElement.create(
                      sort_order: rand <= 0.5 ? 0 : 1,
                      position: 0,
                      report: report,
                      type_of: :my_module_samples,
                      my_module: my_module,
                      parent: re_my_module
                    )
                  end
                end
              end

              # Shuffle the report
              shuffle_report_elements(
                report.report_elements.where(parent: nil)
              )
            end
          end

        end

        # Now, at the end, add additional "private" team
        # to each user
        User.find_each do |user|
          create_private_user_team(user, Constants::DEFAULT_PRIVATE_TEAM_NAME)
        end

        # Generate thumbnails of all experiments
        Experiment.find_each do |experiment|
          experiment.generate_workflow_img
        end

        # Calculate space taken by each team; this must
        # be done in a separate transaction because the estimated
        # asset sizes are calculated in after_commit, which is done
        # after the first transaction is completed
        ActiveRecord::Base.transaction do
          puts 'Calculating team sizes...'
          Team.find_each do |team|
            team.calculate_space_taken
            team.save
          end
        end
      rescue ActiveRecord::ActiveRecordError,
        ArgumentError, ActiveRecord::RecordNotSaved => e
        puts "Error seeding fake data, transaction reverted"
        puts "Output: #{e.inspect}"
        puts e.backtrace.join("\n")
      end
    end
  end

  def generate_fake_protocol(
    team,
    my_module,
    nr_steps,
    nr_comments
  )
    protocol = nil
    if my_module.present?
      protocol = my_module.protocol
      users = my_module.experiment.project.users
      author = pluck_random(users)
      if rand <= THRESHOLD_PROTOCOL_IN_MODULE_LINKED &&
        (parent = pluck_random(
          team.protocols.where(protocol_type: [
            Protocol.protocol_types[:in_repository_private],
            Protocol.protocol_types[:in_repository_public]
          ]))
        ).present?
        protocol.protocol_type = :linked
        protocol.added_by = author
        protocol.parent = parent
        protocol.parent_updated_at = parent.updated_at
      else
        protocol.protocol_type = :unlinked
      end
      protocol.my_module = my_module
    else
      protocol = Protocol.new
      users = team.users
      author = pluck_random(users)
      val = rand
      if val > THRESHOLD_PROTOCOL_ARCHIVED
        if val > THRESHOLD_PROTOCOL_PUBLIC
          protocol.protocol_type = :in_repository_public
          protocol.published_on = Faker::Time.backward(500)
        else
          protocol.protocol_type = :in_repository_private
        end
      else
        protocol.protocol_type = :in_repository_archived
        protocol.archived_by = author
        protocol.archived_on = Faker::Time.backward(500)
      end
      protocol.added_by = author
      protocol.authors = Faker::Book.author
      protocol.description = Faker::Lorem.paragraph(2)
    end
    protocol.name = Faker::Hacker.ingverb
    protocol.team = team

    if protocol.invalid? then
      rename_record(protocol, :name)
    end

    protocol.save!

    team.protocol_keywords.find_each do |kw|
      if rand <= RATIO_PROTOCOL_KEYWORDS
        ProtocolProtocolKeyword.create(
          protocol: protocol,
          protocol_keyword: kw
        )
      end
    end

    protocol.reload
    if protocol.linked?
      # For linked protocols, simply copy their parents' contents
      protocol.load_from_repository(protocol.parent, author)
    else
      # Generate fake protocol data
      for i in 1..nr_steps
        created_at = Faker::Time.backward(500)
        completed = protocol.in_repository? ? false : (rand <= RATIO_STEP_COMPLETED)
        completed_on = completed ?
          Faker::Time.between(created_at, DateTime.now) : nil

        step = Step.create(
          created_at: created_at,
          name: Faker::Hacker.ingverb,
          description: Faker::Hacker.say_something_smart,
          position: i - 1,
          completed: completed,
          user: pluck_random(users),
          protocol: protocol,
          completed_on: completed_on
        )
        if protocol.in_module?
          Activity.create(
            type_of: :create_step,
            project: my_module.experiment.project,
            my_module: my_module,
            user: step.user,
            created_at: created_at,
            message: I18n.t(
              "activities.create_step",
              user: step.user.full_name,
              step: i,
              step_name: step.name
            )
          )
        end
        if completed then
          Activity.create(
            type_of: :complete_step,
            project: my_module.experiment.project,
            my_module: my_module,
            user: step.user,
            created_at: completed_on,
            message: I18n.t(
              "activities.complete_step",
              user: step.user.full_name,
              step: i,
              step_name: step.name,
              completed: i,
              all: i
            )
          )
        end

        # Add checklists
        for _ in 1..NR_MAX_STEP_ATTACHMENTS
          if rand <= RATIO_STEP_ATTACHMENTS then
            checklist = Checklist.create(
              name: Faker::Hacker.noun,
              step: step,
              created_by: step.user,
            )

            # Add checklist items
            for j in 1..NR_MAX_CHECKLIST_ITEMS
              if rand <= RATIO_CHECKLIST_ITEM then
                checked = protocol.in_repository? ? false : (rand <= RATIO_CHECKLIST_ITEM_CHECKED)
                checked_on = Faker::Time.backward(500)
                ci = ChecklistItem.create(
                  created_at: checked_on,
                  text: Faker::Hipster.sentence,
                  checklist: checklist,
                  checked: checked,
                  created_by: step.user
                )
                if checked then
                  Activity.create(
                    type_of: :check_step_checklist_item,
                    project: my_module.experiment.project,
                    my_module: my_module,
                    user: step.user,
                    created_at: checked_on,
                    message: I18n.t(
                      "activities.check_step_checklist_item",
                      user: step.user.full_name,
                      checkbox: ci.text,
                      completed: j,
                      all: j,
                      step: i,
                      step_name: step.name
                    )
                  )
                end
              end
            end
          end
        end

        # Add assets
        for _ in 1..NR_MAX_STEP_ATTACHMENTS
          if rand <= RATIO_STEP_ATTACHMENTS then
            asset = Asset.create(
              file: generate_file(rand(MIN_FILE_SIZE..MAX_FILE_SIZE)),
              estimated_size: 0,
              file_present: true,
              created_by: step.user
            )
            StepAsset.create(
              step: step,
              asset: asset
            )
          end
        end

        # Add tables
        for _ in 1..NR_MAX_STEP_ATTACHMENTS
          if rand <= RATIO_STEP_ATTACHMENTS then
            table = Table.create(
              contents:
                generate_table_contents(rand(30), rand(150)),
              created_by: step.user
            )
            StepTable.create(
              step: step,
              table: table
            )
          end
        end

        # Add some comments (only on protocols on module)
        if protocol.in_module? then
          for _ in 1..nr_comments
            if rand <= RATIO_COMMENTS
              user = pluck_random(users)
              created_at = Faker::Time.backward(500)
              StepComment.create(
                user: user,
                message: Faker::Hipster.sentence,
                created_at: created_at,
                step: step
              )
              Activity.create(
                type_of: :add_comment_to_step,
                project: my_module.experiment.project,
                my_module: my_module,
                user: user,
                created_at: created_at,
                message: I18n.t(
                  "activities.add_comment_to_step",
                  user: user.full_name,
                  step: i,
                  step_name: step.name
                )
              )
            end
          end
        end
      end
    end

    return protocol.reload
  end

  def shuffle_report_elements(report_elements)
    if report_elements.blank? or report_elements.count == 0
      return
    end

    header = report_elements.find_by(type_of: :project_header)
    if header.present?
      header.position = 0
      header.save
      i = 1
    else
      i = 0
    end

    ids_map = (i..(report_elements.count - 1)).to_a.shuffle
    for i in i..(report_elements.count - 1)
      re = report_elements[i]
      re.position = ids_map[i]
      re.save
    end

    # Recursively shuffle children
    report_elements.each do |re2|
      if re2.children.count > 0
        shuffle_report_elements(re2.children)
      end
    end
  end

  def generate_fake_project_comment(project)
    user = pluck_random(project.users)
    created_at = Faker::Time.backward(500)
    ProjectComment.create(
      user: user,
      message: Faker::Hipster.sentence,
      created_at: created_at,
      project: project
    )
    Activity.create(
      type_of: :add_comment_to_project,
      user: user,
      project: project,
      created_at: created_at,
      message: I18n.t('activities.add_comment_to_project',
                      user: user.full_name,
                      project: project.name)
    )
  end

  def generate_fake_module_comment(my_module)
    user = pluck_random(my_module.experiment.project.users)
    created_at = Faker::Time.backward(500)
    TaskComment.create(
      user: user,
      message: Faker::Hipster.sentence,
      created_at: created_at,
      my_module: my_module
    )
    Activity.create(
      type_of: :add_comment_to_module,
      user: user,
      project: my_module.experiment.project,
      my_module: my_module,
      created_at: created_at,
      message: I18n.t('activities.add_comment_to_module',
                      user: user.full_name,
                      module: my_module.name)
    )
  end

  def generate_fake_result_comment(result)
    user = pluck_random(result.my_module.experiment.project.users)
    created_at = Faker::Time.backward(500)
    ResultComment.create(
      user: user,
      message: Faker::Hipster.sentence,
      created_at: created_at,
      result: result
    )
    Activity.create(
      type_of: :add_comment_to_result,
      project: result.my_module.experiment.project,
      my_module: result.my_module,
      user: user,
      created_at: created_at,
      message: I18n.t(
        'activities.add_comment_to_result',
        user: user.full_name,
        result: result.name
      )
    )
  end

  def generate_fake_step_comment(step)
    user = pluck_random(step.protocol.my_module.experiment.project.users)
    created_at = Faker::Time.backward(500)
    StepComment.create(
      user: user,
      message: Faker::Hipster.sentence,
      created_at: created_at,
      step: step
    )
    Activity.create(
      type_of: :add_comment_to_step,
      project: step.protocol.my_module.experiment.project,
      my_module: step.protocol.my_module,
      user: user,
      created_at: created_at,
      message: I18n.t(
        "activities.add_comment_to_step",
        user: user.full_name,
        step: step.position + 1,
        step_name: step.name
      )
    )
  end

  # WARNING: This only works on PostgreSQL
  def pluck_random(scope)
    scope.order("RANDOM()").first
  end

  # Randomly determine whether project/module/result is active (0),
  # archived (1), or already restored (2)
  def random_status
    val = rand
    status = :active
    if val > THRESHOLD_ARCHIVED
      if val > THRESHOLD_RESTORED
        status = :archived
      end
    else
      status = :restored
    end
    status
  end
end
