# frozen_string_literal: true

require 'intranet/abstract_responder'
require 'intranet/core/haml_wrapper'
require 'intranet/core/locales'
require 'json'
require_relative 'version'

module Intranet
  module Pictures
    # The responder for the Pictures monitor module of the Intranet.
    class Responder < AbstractResponder
      include Core::HamlWrapper # 'inherits' from methods of HamlWrapper

      # Returns the name of the module.
      # @return [String] The name of the module.
      def self.module_name
        NAME
      end

      # The version of the module, according to semantic versionning.
      # @return [String] The version of the module.
      def self.module_version
        VERSION
      end

      # The homepage of the module.
      # @return [String] The homepage URL of the module.
      def self.module_homepage
        HOMEPAGE_URL
      end

      # Initializes a new Pictures responder instance.
      # @param provider [#title,#group_types,#list_groups,#group_thumbnail,#list_pictures,#picture]
      #  The pictures provider.
      # @see Intranet::Pictures::JsonDbProvider The specification of the provider, and in particular
      #  the minimal mandatory elements that must be returned by the operations.
      # @param recents [Array<Hash{group_type:String, sort_by:String, asc:Boolean, limit:Integer}>]
      #  The description of the recent pictures album to be displayed on the module home page (all
      #  keys except +group_type+ may be omitted).
      # @param home_groups [Array<Hash{group_type:String, sort_by:String, asc:Boolean,
      #  browse:String, browse_sort_by:String, browse_asc:Boolean}>]
      #  The description of the groups to be displayed on the module home page. All groups of the
      #  +group_type+ will be displayed and link to a page showing all groups of the +browse+ type.
      # @param in_menu [Boolean] Whether the module instance should be displayed in the main
      #                          navigation menu or not.
      def initialize(provider, recents = [], home_groups = [], in_menu = true)
        @provider = provider
        @recents = recents
        @in_menu = in_menu
        @home_groups = home_groups
      end

      # Specifies if the responder instance should be displayed in the main navigation menu or not.
      # @return [Boolean] True if the responder instance should be added to the main navigation
      #                   menu, False otherwise.
      def in_menu?
        @in_menu
      end

      # Specifies the absolute path to the resources directory for that module.
      # @return [String] The absolute path to the resources directory for the module.
      def resources_dir
        File.absolute_path(File.join('..', 'resources'), __dir__)
      end

      # Generates the HTML content associated to the given +path+ and +query+.
      # === REST API Description:
      # * Read-only access to pictures listings under */api/pictures* using +GET+ method, response
      #   is in JSON format with the following structure:
      #     [
      #       { "id": "...", "height": 480, "width": 640, "title": "...", "datetime": "...", ... },
      #       { ... }
      #     ]
      # * Read-only access to groups listings under */api/groups/*+group_type+ using +GET+ method,
      #   response is in JSON format with the following structure:
      #     [
      #       { "id": "...", "title": "...", ... },
      #       { ... }
      #     ]
      # @param path [String] The requested URI, relative to that module root URI.
      # @param query [Hash<String,String>] The URI variable/value pairs, if any.
      # @return [Integer, String, String] The HTTP return code, the MIME type and the answer body.
      def generate_page(path, query)
        case path
        when %r{^/index\.html$} then serve_home
        when %r{^/browse_\w+\.html$}
          serve_groups(path.gsub(%r{^/browse_(\w+)\.html$}, '\\1'), query)
        when %r{^/api/} then serve_api(path.gsub(%r{^/api}, ''), query)
        else super(path, query)
        end
      end

      # The title of the Pictures module, as displayed on the web page.
      # @return [String] The title of the Pictures module web page.
      def title
        @provider.title
      end

      # Provides the list of Cascade Style Sheets (CSS) dependencies for this module.
      # @return [Array<String>] The list of CSS dependencies.
      def css_dependencies
        super + ['design/style.css',
                 'design/photoswipe/photoswipe.css',
                 'design/photoswipe/default-skin/default-skin.css']
      end

      # Provides the list of Javascript files (JS) dependencies for this module.
      # @return [Array<String>] The list of JS dependencies.
      def js_dependencies
        super + ['design/jpictures.js',
                 'design/photoswipe/photoswipe.min.js',
                 'design/photoswipe/photoswipe-ui-default.min.js']
      end

      private

      # Extract a selector from the given +query+.
      # @return [Hash<String,String>] The picture or group selector.
      def selector(query)
        query.except('sort_by', 'sort_order')
      end

      # Extract the sort criteria from the given +query+.
      # @return [String] The key to use to sort pictures or groups, or nil if no sort order is
      #                  specified in +query+.
      def sort_by(query)
        query.fetch('sort_by')
      rescue KeyError
        nil
      end

      # Extract the sort order from the given +query+.
      # @return [Boolean] False if the pictures or groups should be sorted in descending order,
      #                   True otherwise.
      # @raise KeyError If the query requests an invalid sort order.
      def sort_order(query)
        return false if query['sort_order'] == 'desc'
        return true if query['sort_order'].nil? || query['sort_order'] == 'asc'

        raise KeyError # incorrect value for 'sort_order'
      end

      ##########################################################################
      ###            Servicing of the HTML "display-able" content            ###
      ##########################################################################

      def active_filters(query)
        selector(query).map do |k, v|
          { k => @provider.list_groups(k, { k => v }).first.fetch('title') }
        rescue KeyError
          { k => v }
        end.reduce({}, :merge)
      end

      def recent_groups
        @recents.map do |recent|
          groups = @provider.list_groups(recent[:group_type], {}, recent[:sort_by], recent[:asc])
          see_more_url = ''
          if recent[:limit].to_i.positive?
            groups = groups.first(recent[:limit])
            see_more_url = "browse_#{recent[:group_type]}.html?sort_by=#{recent[:sort_by]}"
            see_more_url += '&sort_order=desc' unless recent[:asc]
          end
          { group_type: recent[:group_type], groups: groups, see_more_url: see_more_url }
        end
      end

      def all_groups
        @home_groups.map do |section|
          groups = @provider.list_groups(section[:group_type], {}, section[:sort_by], section[:asc])
          url_prefix = "browse_#{section[:browse]}.html?sort_by=#{section[:browse_sort_by]}"
          url_prefix += '&sort_order=desc' unless section[:browse_asc]
          { group_type: section[:group_type], groups: groups, url_prefix: url_prefix }
        end
      end

      def make_nav(group_type = nil, query = {})
        h = { I18n.t('nav.home') => '/index.html', I18n.t('pictures.menu') => nil, title => nil }
        unless group_type.nil?
          h[title] = 'index.html'
          extra_key = I18n.t("pictures.nav.#{group_type}")
          filters = active_filters(query).values
          extra_key += " (#{filters.join(', ')})" unless filters.empty?
          h.store(extra_key, nil)
        end
        h
      end

      def gallery_url(group_type, group_id, filters = {})
        filters.store(group_type, group_id)
        filters.store('sort_by', 'datetime')
        filters.map { |k, v| [k, v].join('=') }.join('&')
      end

      def serve_home
        content = to_markup('pictures_home', nav: make_nav)
        [206, 'text/html', { content: content, title: title }]
      rescue KeyError
        [404, '', '']
      end

      def serve_groups(type, query)
        groups = @provider.list_groups(type, selector(query), sort_by(query), sort_order(query))
        content = to_markup('pictures_browse', nav: make_nav(type, query), group_type: type,
                                               filters: selector(query), groups: groups)
        [206, 'text/html', { content: content, title: title }]
      rescue KeyError
        [404, '', '']
      end

      ##########################################################################
      ###        Servicing of the REST API (raw JSON data & pictures)        ###
      ##########################################################################

      def api_list_groups(path, query)
        group_type = path.split('/')[2].to_s
        @provider.list_groups(group_type, selector(query), sort_by(query), sort_order(query))
      end

      def api_group_thumbnail(path, query)
        group_type = path.split('/')[2].to_s
        pic = @provider.group_thumbnail(group_type, selector(query))
        if pic.nil?
          pic = ['image/svg+xml', File.read(File.join(resources_dir, 'www', 'group_thumbnail.svg'))]
        end
        pic
      end

      def api_list_pictures(query)
        @provider.list_pictures(selector(query), sort_by(query), sort_order(query))
      end

      def serve_api(path, query)
        case path
        when %r{^/groups/} then [200, 'application/json', api_list_groups(path, query).to_json]
        when %r{^/group/} then [200, api_group_thumbnail(path, query)].flatten
        when %r{^/pictures$} then [200, 'application/json', api_list_pictures(query).to_json]
        when %r{^/picture$} then [200, @provider.picture(query)].flatten
        else [404, '', '']
        end
      rescue KeyError
        [404, '', '']
      end
    end
  end
end