module EasyXmlData
  class Exporter
    require 'zip'

    def initialize(exportables, project_ids)
      @project_ids = project_ids
      @projects = Project.preload(project_preload_list).where(:id => project_ids)
      @exportables = exportables
      @redmine_plugins = Redmine::Plugin.all(:without_disabled => true).collect(&:id)

      collect_entities
    end

    def project_preload_list
      list = [:time_entries, :trackers, :roles, {:memberships => [:roles]}, {:news => [:comments]}, :project_activity_roles, :enabled_modules, {:documents => [{:attachments => :versions}]}]
      Redmine::Hook.call_hook(:easy_xml_data_exporter_project_preload_list, {:list => list})
      return list
    end

    def issue_preload_list
      list = [{:attachments => :versions}, {:journals => [:details, :user]}, :author, :assigned_to, :status, :tracker, :custom_values, :relations_from, :relations_to]
      Redmine::Hook.call_hook(:easy_xml_data_exporter_issue_preload_list, {:list => list})
      return list
    end

    def self.exportables
      @@exportables ||= [:issues, :news, :documents, :time_entries]
    end

    def self.exportable_labels
      @@exportable_labels ||= Hash.new
    end

    def file_prefix
      "#{Attachment.storage_path}/easy_xml_data_exporter_"
    end

    def clear_files
      files = %w(data.xml archive.zip)
      files.each do |file|
        f = file_prefix << file
        File.delete(f) if File.exists?(f)
      end
    end

    def prepare_files
      File.open(file_prefix << 'data.xml', 'w+') do |df|
        builder = Builder::XmlMarkup.new(:target => df, :indent => 4)
        builder.instruct!
        build_xml(builder)
      end
    end

    def build_archive
      clear_files
      prepare_files
      Zip::File.open(file_prefix << 'archive.zip', Zip::File::CREATE) do |zip|
        zip.add('data.xml', file_prefix << 'data.xml')
        @attachment_files.each do |attachment_file|
          diskfile = Attachment.storage_path + '/' + attachment_file
          zip.add("attachments/#{attachment_file}", diskfile) if File.exists?(diskfile)
        end
      end
      file_prefix + 'archive.zip'
    end

    def build_xml(bob)
      bob.easy_xml_data do
        @users.to_xml(:builder => bob, :skip_instruct => true, :except => [:easy_user_type, :easy_zoom_user_uid], :procs => [
                        Proc.new{|options, record| options[:builder].tag!('easy-user-type-id', record.easy_user_type_id); options[:builder].tag!('mail', record.mail)}]
        ) unless @users.blank?
        @groups.to_xml(:builder => bob, :skip_instruct => true, :include => {
                        :users => {:only => [:id]}
        }) unless @groups.blank?
        @project_custom_fields.to_xml(:builder => bob, :skip_instruct => true, :except => [:settings], :procs => [
                        Proc.new{|options, record| options[:builder].tag!('settings', record.settings.to_yaml, :type => 'yaml')}]
        ) unless @project_custom_fields.blank?
        @easy_project_template_custom_fields.to_xml(:builder => bob, :skip_instruct => true, :except => [:settings], :procs => [
                        Proc.new{|options, record| options[:builder].tag!('settings', record.settings.to_yaml, :type => 'yaml')}]
        ) unless @easy_project_template_custom_fields.blank?
        @issue_custom_fields.to_xml(:builder => bob, :skip_instruct => true, :except => [:settings], :procs => [
                        Proc.new{|options, record| options[:builder].tag!('settings', record.settings.to_yaml, :type => 'yaml')}]
        ) unless @issue_custom_fields.blank?
        @trackers.to_xml(:builder => bob, :skip_instruct => true, :include => {
          :custom_fields => {:only => [:id]}
        }) unless @trackers.blank?

        @projects.to_xml(:builder => bob, :skip_instruct => true, :include => {
          :trackers => {:only => [:id]},
          :enabled_modules => {:only => [:name]},
          :project_custom_fields => {:only => [:id]},
          :issue_custom_fields => {:only => [:id]},
          :custom_values => {}
        }) unless @projects.blank?
        @easy_page_zone_modules.to_xml(:builder => bob, :skip_instruct => true, :except => [
                                                          :uuid,
                                                          :easy_pages_id,
                                                          :easy_page_available_zones_id,
                                                          :easy_page_available_modules_id,
                                                          :settings
                                                      ], :procs => [
                                                          Proc.new{|options, record| options[:builder].tag!('id', record.id)},
                                                          Proc.new{|options, record| options[:builder].tag!('easy-page', record.page_definition.page_name)},
                                                          Proc.new{|options, record| options[:builder].tag!('easy-page-zone', record.zone_definition.zone_name)},
                                                          Proc.new{|options, record| options[:builder].tag!('easy-page-module', record.module_definition.type)},
                                                          Proc.new{|options, record| options[:builder].tag!('settings', record.settings.to_yaml, :type => 'yaml')}
                                                      ]) unless @easy_page_zone_modules.blank?
        @easy_page_user_tabs.to_xml(:builder => bob, :skip_instruct => true, :except => [:page_id],
                                                     :procs => [
                                                       Proc.new{|options, record| options[:builder].tag!('page-id', record.entity_id ? nil : record.page_id)},
                                                       Proc.new{|options, record| options[:builder].tag!('easy-page', record.entity_id ? record.page_definition.page_name : nil)}
        ]) unless @easy_page_user_tabs.blank?
        @roles.to_xml(:builder => bob, :skip_instruct => true) unless @roles.blank?
        @members.to_xml(:builder => bob, :skip_instruct => true, :procs => [
                                                          Proc.new{|options, record| options[:builder].tag!('roles', :type => 'array') do
                                                            record.roles.where(member_roles: {inherited_from: nil}).pluck(:id).each do |role_id|
                                                              options[:builder].tag!('role') do
                                                                options[:builder].tag!('id', role_id, :type => 'integer')
                                                              end
                                                            end
                                                          end }]
        ) unless @members.blank?
        @versions.to_xml(:builder => bob, :skip_instruct => true) unless @versions.blank?
        @issue_priorities.to_xml(:builder => bob, :skip_instruct => true) unless @issues.blank?
        @issue_statuses.to_xml(:builder => bob, :skip_instruct => true) unless @issue_statuses.blank?
        @issues.to_xml(:builder => bob, :skip_instruct => true, :include => {
          :custom_values => {}
        }, :except => [:easy_repeat_settings], :procs => [Proc.new{|options, record| options[:builder].tag!('easy-repeat-settings', record.easy_repeat_settings.to_yaml, :type => 'yaml')}]) unless @issues.blank?
        @issue_relations.to_xml(:builder => bob, :skip_instruct => true) unless @issue_relations.blank?
        @journals.to_xml(:builder => bob, :skip_instruct => true, :include => [:details]) unless @issues.blank?

        @workflow_rules.to_xml(:builder => bob, :skip_instruct => true) unless @workflow_rules.blank?

        @news.to_xml(:builder => bob, :skip_instruct => true) unless @news.blank?
        @comments.to_xml(:builder => bob, :skip_instruct => true) unless @comments.blank?

        @document_categories.to_xml(:builder => bob, :skip_instruct => true) unless @document_categories.blank?
        @documents.to_xml(:builder => bob, :skip_instruct => true) unless @documents.blank?

        @time_entry_activities.to_xml(:builder => bob, :skip_instruct => true, :except => [:project_id, :position]) unless @time_entry_activities.blank?
        @project_activities.to_xml(:builder => bob, :skip_instruct => true) unless @project_activities.blank?
        @project_activity_roles.to_xml(:builder => bob, :skip_instruct => true) unless @project_activity_roles.blank?

        @time_entries.to_xml(:builder => bob, :skip_instruct => true) unless @time_entries.blank?

        @attachments.to_xml(:builder => bob, :skip_instruct => true) unless @attachments.blank?
        @attachment_versions.to_xml(:builder => bob, :skip_instruct => true) unless @attachment_versions.blank?

        Redmine::Hook.call_hook(:easy_xml_data_exporter_build_xml, {:builder => bob})
      end
    end

    private

    def collect_entities

      # projects and stuff
      @users,@groups, @trackers, @roles, @members, @versions, @project_custom_fields, @issue_custom_fields, @attachments = [],[],[],[],[],[],[],[],[]
      @projects.each do |project|
        @users = project.users | @users
        @groups = Group.joins(:members).where(users: { type: 'Group', status: Principal::STATUS_ACTIVE }, members: { project_id: project.id }).uniq | @groups
        @trackers = project.trackers | @trackers
        @roles = project.roles | @roles
        @members = (project.memberships.joins(:principal).where('users.type LIKE ?', '%User%').where(users: { status: Principal::STATUS_ACTIVE }).joins(:roles).where(member_roles: {inherited_from: nil}).uniq + project.memberships.joins(:principal).where('users.type LIKE ?', '%Group%')) | @members
        project.memberships.each{|r| @roles = r.roles | @roles }
        @versions = project.versions | @versions
        project_available_custom_fields = project.available_custom_fields
        @project_custom_fields = (project_available_custom_fields | @project_custom_fields).select { |cf| cf.type == 'ProjectCustomField' }
        @easy_project_template_custom_fields = project_available_custom_fields.select { |cf| cf.type == 'EasyProjectTemplateCustomField' }
        @issue_custom_fields = project.issue_custom_fields | @issue_custom_fields
      end
      @issue_custom_fields = IssueCustomField.where(:is_for_all => true) | @issue_custom_fields
      @easy_page_zone_modules = EasyPageZoneModule.includes([:page_definition, :zone_definition, :module_definition]).where(:easy_pages => {:page_scope => 'project'}).where(:entity_id => @project_ids)
      @easy_page_user_tabs = EasyPageUserTab.where(entity_id: @project_ids)

      #issues and stuff
      if @exportables.include? :issues
        @issues = []
        @issue_relations = []
        @journals = []
        @issue_statuses = []
        @projects.each do |project|
          project_issues = project.issues.preload(issue_preload_list)
          @issues.concat project_issues
          @issue_relations.concat project_issues.select{|i| i.relations_from.any?}.collect{|i| i.relations_from}
          @journals.concat project_issues.collect(&:journals).flatten
          @issue_statuses = project_issues.collect(&:status).flatten | @issue_statuses
          @issue_statuses = (@issue_statuses + project.trackers.map(&:default_status)).uniq
        end

        @issue_priorities = IssuePriority.all

        #users from journals
        @journals.each do |j|
          u = j.user
          @users << u unless @users.include?(u)
        end

        #users from issues
        @issues.each do |issue|
          @users << issue.author unless issue.author.blank? || @users.include?(issue.author)
        end
      end

      #workflows rules
      unless @roles.blank? || @trackers.blank?
        @workflow_rules = WorkflowRule.where(:role_id => @roles, :tracker_id => @trackers)
      end

      #news
      if @exportables.include? :news
        @news = @projects.collect(&:news).flatten
        @comments = @news.collect(&:comments).flatten
        @news.each do |n|
          @users << n.author unless @users.include? n.author
        end
        @comments.each do |c|
          @users << c.author unless @users.include? c.author
        end
      end

      #documents
      if @exportables.include? :documents
        @documents = @projects.collect(&:documents).flatten
        @document_categories = @documents.collect(&:category).flatten.uniq
      end

      #activities
      @time_entry_activities = @projects.collect{|p| p.activities + p.role_activities}.flatten.uniq
      @project_activity_roles = @projects.collect(&:project_activity_roles).flatten
      @project_activities = ProjectActivity.where(:project_id => @projects)

      #time_entries
      if @exportables.include? :time_entries
        @time_entries = @projects.collect(&:time_entries).flatten
        @time_entries.each do |te|
          @users << te.user unless @users.include?(te.user)
        end
      end

      #attachments
      @attachments = []
      Array(@issues).each{|i| @attachments.concat i.attachments}
      Array(@documents).each{|i| @attachments.concat i.attachments}

      @attachment_files = @attachments.collect(&:disk_filename)
      @attachment_versions = @attachments.collect(&:versions).flatten
      @attachment_versions.each do |version|
        @attachment_files << version.disk_filename unless @attachment_files.include?(version.disk_filename)
      end

      #easy_money
      Redmine::Hook.call_hook(:easy_xml_data_exporter_collect_entities, {:exporter => self})

    end

  end
end
