# encoding: UTF-8

# Copyright 2011-2013 innoQ Deutschland GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

class SearchResultsController < ApplicationController
  include DatasetInitialization

  resource_description do
    name 'Search'
  end

  api :GET, 'search', 'Search for concepts or collections based on various criteria.'
  formats [:html, :ttl, :rdf]
  param :q, String, required: true,
      desc: 'Query term (URL-encoded, if necessary). Wild cards are not '\
               'supported, see the `qt` parameter below.'
  param :qt, ['exact', 'contains', 'begins_with', 'ends_with'],
      desc: 'Query type'
  param :t, %w(labels pref_labels notes),
      required: true,
      desc: 'Specifies the properties to be searched.'
  param :for, %w(concept collection all),
      desc: 'The result type you are searching for.'
  param 'l[]', Iqvoc.all_languages,
      required: true,
      desc: 'One or more languages of the labels or notes to be queried. '\
               'Use 2-letter language codes from ISO 639.1.'
  param :c, String,
      desc: 'Constrains results to members of the given collection ID.'
  param 'ds[]', String ,
      desc: 'Specifies one or more external data sets (connected thesauri)'\
               'to include in search.'
  param :include_expired, String,
        desc: 'Specifies if expired concepts should be included (defaults to false).'
  example <<-DOC
    GET /search.ttl
    200

    # omitted namespace definitions
    <http://try.iqvoc.net/search.ttl?l=en&q=dance&t=labels> a sdc:Query;
                                                            sdc:totalResults 1;
                                                            sdc:itemsPerPage 40;
                                                            sdc:searchTerms "dance";
                                                            sdc:first <http://try.iqvoc.net/search.ttl?l=en&page=1&q=dance&t=labels>;
                                                            sdc:last <http://try.iqvoc.net/search.ttl?l=en&page=1&q=dance&t=labels>;
                                                            sdc:result search:result1.
    <http://try.iqvoc.net/search.ttl?l=en&page=1&q=dance&t=labels> a sdc:Page;
                                                                   sdc:startIndex 1.
    search:result1 a sdc:Result;
                   sdc:link :dance;
                   skos:prefLabel "Dance"@en.
  DOC

  def index
    authorize! :read, Concept::Base
    # TODO: requires a dedicated :search permission because this covers more
    # than just concepts

    self.class.prepare_basic_variables(self)

    # Map short params to their log representation
    { t: :type,
      q: :query,
      l: :languages,
      qt: :query_type,
      c: :collection_origin,
      ds: :datasets }.each do |short, long|
      params[long] ||= params[short]
    end

    # Delete parameters which should not be included into generated urls (e.g.
    # in rdf views)
    request.query_parameters.delete('commit')
    request.query_parameters.delete('utf8')

    @datasets = init_datasets

    @remote_result_collections = []

    if params[:query]
      if params[:query].blank? and request.format.to_sym.in? [:ttl, :nt, :rdf]
        # support blank searches via web ui, but not for api requests
        respond_to do |format|
          format.any(:ttl, :rdf, :nt) do
            head :bad_request
          end
        end
      else
        # Deal with language parameter patterns
        languages = []
        # Either "l[]=de&l[]=en" as well as "l=de,en" should be possible
        if params[:languages].respond_to?(:each) && params[:languages].include?('none')
          # Special treatment for the "nil language"
          languages << nil
        elsif params[:languages].respond_to?(:split)
          languages = params[:languages].split(',')
        end

        # Ensure a valid class was selected
        unless klass = Iqvoc.searchable_class_names.detect { |key, value| value == params[:type] }.try(:first)
          raise "'#{params[:type]}' is not a searchable class! Must be one of " + Iqvoc.searchable_class_names.keys.join(', ')
        end
        klass = klass.constantize

        @results = klass.single_query(params.merge({ languages: languages.flatten }))
                        .filter { |search_result| result_allowed?(search_result) }

        if params[:limit] && Iqvoc.unlimited_search_results
          @results = @results.per(params[:limit].to_i)
        end

        if params[:datasets] && datasets = @datasets.select { |a| params[:datasets].include?(a.name) }
          @results = SearchResultCollection.new(@results)
          datasets.each do |dataset|
            results = dataset.search(params)
            if results
              @results = @results + results
            else
              flash.now[:error] ||= []
              flash.now[:error] << t('txt.controllers.search_results.remote_source_error', source: dataset)
            end
          end
          @results = @results.sort { |x, y| x.to_s <=> y.to_s }
        end

        @results = Kaminari.paginate_array(@results)
        @results = @results.page(params[:page])

        respond_to do |format|
          format.html {
            if request.headers['Accept'] == 'text/html; fragment=true'
              render template: 'search_results/_result_list', layout: false
            else
              render :index, layout: with_layout?
            end
          }
          format.any(:ttl, :rdf, :nt)
        end
      end
    else
      respond_to do |format|
        format.html do
          render :index
        end
        format.any(:ttl, :rdf, :nt) do
          head :bad_request
        end
      end
    end
  end

  def self.prepare_basic_variables(controller)
    langs = Iqvoc.all_languages.each_with_object({}) do |lang, hsh|
      lang ||= 'none'
      hsh[lang] = I18n.t("languages.#{lang}", default: lang)
    end
    controller.instance_variable_set(:@available_languages, langs)

    # AR requires mapping to existing object attributes so that value is mapped to the unused status attribute to keep the information
    collections = Iqvoc::Collection.base_class.joins(:pref_labels).order('value').select('value AS status, concepts.origin')
    controller.instance_variable_set(:@collections, collections)

    # default search params
    controller.params['t'] = Iqvoc.searchable_class_names.values.first if controller.params['t'].nil?
    controller.params['qt'] = 'contains' if controller.params['qt'].nil?
    controller.params['for'] = 'all' if controller.params['for'].nil?
    controller.params['l'] = langs.keys if controller.params['l'].nil?
    controller.params['include_expired'] = (controller.params['include_expired'] == "true")
  end

  private

  def result_allowed?(result)
    if result.result_object.is_a?(Labeling::Base)
      can?(:read, result.owner)
    else
      can?(:read, result.result_object)
    end
  end

end
