module JamRuby
  class BandSearch < BaseSearch

    cattr_accessor :jschema, :search_meta
    attr_accessor :user_counters

    #serialize :data_blob, JSON

    KEY_BAND_SEARCH_TYPE = 'band_search_type'
    KEY_BAND_TYPE = 'band_type'
    KEY_BAND_STATUS = 'band_status'
    KEY_PLAY_COMMIT = 'play_commitment'
    KEY_TOUR_OPTION = 'touring_option'
    KEY_PERF_SAMPLES = 'performance_samples'
    KEY_HIRE_MAX_COST = 'max_cost'
    KEY_HIRE_FREE = 'free_gigs'

    TO_JOIN = 'to_join'
    TO_HIRE = 'to_hire'

    BAND_SEARCH_TYPE_VALS = [TO_JOIN, TO_HIRE]
    BAND_SEARCH_TYPES = {
      TO_JOIN => 'search bands',
      TO_HIRE => 'search bands to hire',
    }

    BAND_TYPE_VAL_STRS = [ANY_VAL_STR, 'amateur', 'professional']
    BAND_TYPES = {
      BAND_TYPE_VAL_STRS[0] => BAND_TYPE_VAL_STRS[0].camelcase,
      BAND_TYPE_VAL_STRS[1] => BAND_TYPE_VAL_STRS[1].camelcase,
      BAND_TYPE_VAL_STRS[2] => BAND_TYPE_VAL_STRS[2].camelcase,
    }

    SORT_VALS = %W{ distance }
    SORT_ORDERS = { 
      SORT_VALS[0] => 'Distance to Me'
    }
    # SORT_VALS = %W{ distance latency }
    # SORT_ORDERS = { 
    #   SORT_VALS[0] => 'Distance to Me'
    #   SORT_VALS[1] => 'Latency to Me', 
    # }

    HIRE_SORT_VALS = %W{ distance price_asc price_desc }
    HIRE_SORT_ORDERS = { 
      HIRE_SORT_VALS[0] => 'Distance to Me',
      HIRE_SORT_VALS[1] => 'Gig Minimum Price (Low to High)', 
      HIRE_SORT_VALS[2] => 'Gig Minimum Price (High to Low)', 
    }

    BAND_STATUS_VALS = [ANY_VAL_STR,
                     GenrePlayer::VIRTUAL_BAND,
                     GenrePlayer::TRADITIONAL_BAND,
                    ]
    BAND_STATUS = { 
      BAND_STATUS_VALS[0] => 'Any', 
      BAND_STATUS_VALS[1] => 'Virtual Band',
      BAND_STATUS_VALS[2] => 'Traditional Band',
    }

    PLAY_COMMIT_VALS = [ANY_VAL_STR,
                        '1',
                        '2',
                        '3',
                        '4',
                       ]
    PLAY_COMMITS = { 
      PLAY_COMMIT_VALS[0] => 'Any', 
      PLAY_COMMIT_VALS[1] => 'Infrequent',
      PLAY_COMMIT_VALS[2] => 'Once a Week',
      PLAY_COMMIT_VALS[3] => '2-3 Times Per Week',
      PLAY_COMMIT_VALS[4] => '4+ Times Per Week',
    }

    TOUR_OPTION_VALS = [ANY_VAL_STR,
                        VAL_YES,
                        VAL_NO,
                       ]
    TOUR_OPTIONS = { 
      TOUR_OPTION_VALS[0] => 'Any', 
      TOUR_OPTION_VALS[1] => VAL_YES,
      TOUR_OPTION_VALS[2] => VAL_NO,
    }

    PERF_SAMPLES_VALS = TOUR_OPTION_VALS.clone
    PERF_SAMPLES = TOUR_OPTIONS.clone 

    COUNT_FOLLOW = :count_follow
    COUNT_RECORD = :count_record
    COUNT_SESSION = :count_session

    def self.json_schema
      return @@jschema if @@jschema
      @@jschema = {
        TO_JOIN => BaseSearch.json_schema.merge({
          KEY_SORT_ORDER => self::SORT_VALS[0],
          KEY_BAND_TYPE => self::BAND_TYPE_VAL_STRS[0].to_s,
          KEY_BAND_STATUS => BAND_STATUS_VALS[0],
          KEY_PLAY_COMMIT => PLAY_COMMIT_VALS[0],
          KEY_TOUR_OPTION => TOUR_OPTION_VALS[0],
        }),
        TO_HIRE => {
          KEY_SORT_ORDER => self::HIRE_SORT_VALS[0],
          KEY_GENRES => [],
          KEY_GIGS => self::GIG_COUNTS[0].to_s,
          KEY_BAND_STATUS => BAND_STATUS_VALS[0],
          KEY_PERF_SAMPLES => self::PERF_SAMPLES_VALS[0],
          KEY_HIRE_MAX_COST => 0,
          KEY_HIRE_FREE => 0,
        },
      }
    end

    def self.search_filter_meta
      return @@search_meta if @@search_meta
      toJoinMeta = super(self.json_schema[TO_JOIN])
      toJoinMeta.merge!({ 
                          KEY_BAND_TYPE => { keys: BAND_TYPE_VAL_STRS, map: BAND_TYPES },
                          KEY_BAND_STATUS => { keys: BAND_STATUS_VALS, map: BAND_STATUS },
                          KEY_PLAY_COMMIT => { keys: PLAY_COMMIT_VALS, map: PLAY_COMMITS },
                          KEY_TOUR_OPTION => { keys: TOUR_OPTION_VALS, map: TOUR_OPTIONS } 
                        })
      toHireMeta = super(self.json_schema[TO_HIRE],
                         { keys: HIRE_SORT_VALS, map: HIRE_SORT_ORDERS })
      toHireMeta.merge!({
                          KEY_BAND_STATUS => { keys: BAND_STATUS_VALS, map: BAND_STATUS },
                          KEY_PERF_SAMPLES => { keys: PERF_SAMPLES_VALS, map: PERF_SAMPLES },
                        })
      @@search_meta = {
        TO_JOIN => toJoinMeta,
        TO_HIRE => toHireMeta,
      }
    end

    def self.search_target_class
      Band
    end

    def _genres(rel, filter)
      super(rel, filter)
    end

    def _concert_gigs(rel, filter)
      gg = filter[KEY_GIGS].to_i
      rel = rel.where(concert_count: gg) if 0 <= gg
      rel
    end

    def _band_status(rel, filter)
      case filter[KEY_BAND_STATUS]
        when GenrePlayer::VIRTUAL_BAND 
          rel.where(band_status: GenrePlayer::VIRTUAL_BAND.sub('_band',''))
        when GenrePlayer::TRADITIONAL_BAND
          rel.where(band_status: GenrePlayer::TRADITIONAL_BAND.sub('_band',''))
        else
          rel
      end
    end

    def _play_commitment(rel, filter)
      unless ANY_VAL_STR == filter[KEY_PLAY_COMMIT]
        rel = rel.where(play_commitment: filter[KEY_PLAY_COMMIT].to_i)
      end
      rel
    end

    def _touring_option(rel, filter)
      case filter[KEY_TOUR_OPTION]
        when VAL_YES
          rel.where(touring_option: true)
        when VAL_NO
          rel.where(touring_option: false)
        else
          rel
      end
    end

    def _performance_samples(rel, filter)
      case filter[KEY_PERF_SAMPLES]
        when VAL_YES
          rel.joins("LEFT OUTER JOIN performance_samples AS ps ON ps.player_id = bands.id AND player_type = '#{Band.name}'").where(["ps.id IS NOT NULL"])
        when VAL_NO
          rel.joins("LEFT OUTER JOIN performance_samples AS ps ON ps.player_id = bands.id AND player_type = '#{Band.name}'").where(["ps.id IS NULL"])
        else
          rel
      end
    end

    def _max_cost(rel, filter)
      if 0 < (max_cost = filter[KEY_HIRE_MAX_COST].to_i)
        col = Band.arel_table[:gig_minimum]
        rel = rel.where(col.lteq(max_cost)).where(col.gt(0))
      end
      rel
    end

    def _free_gigs(rel, filter)
      case filter[KEY_HIRE_FREE]
        when VAL_YES
          rel.where(free_gigs: true)
        when VAL_NO
          rel.where(free_gigs: false)
        else
          rel
      end
    end

    def _band_type(rel, filter)
      case filter[KEY_BAND_TYPE]
        when BAND_TYPE_VAL_STRS[1]
          rel.where(band_type: BAND_TYPE_VAL_STRS[1])
        when BAND_TYPE_VAL_STRS[2]
          rel.where(band_type: BAND_TYPE_VAL_STRS[2])
        else
          rel
      end
    end

    def _sort_order(rel, filter)
      val = filter[KEY_SORT_ORDER]
      if 'distance' == val || val.blank?
        # TODO: bring back search by distance
        #locidispid = self.user.last_jam_locidispid || 0
        #my_locid = locidispid / 1000000
        #rel = rel.joins("LEFT JOIN geoiplocations AS my_geo ON my_geo.locid = #{my_locid}")
        #rel = rel.joins("LEFT JOIN geoiplocations AS other_geo ON other_geo.latitude = bands.lat AND other_geo.longitude = bands.lng")
        #rel = rel.group("bands.id")
        #rel = rel.order('st_distance(my_geo.geog, other_geo.geog)')

      elsif 'price_asc' == val
        rel = rel.order('gig_minimum ASC')

      elsif 'price_desc' == val
        rel = rel.order('gig_minimum DESC')
      end
      rel
    end

    def do_search(filter)
      rel = Band.unscoped
      filter.keys.each do |fkey| 
        mname = "_#{fkey}"
        if self.respond_to?(mname)
          rel = self.send(mname.to_sym, rel, filter) 
        end
      end
      rel
    end

    def search_includes(rel, subtype=TO_JOIN)
      TO_JOIN == subtype ? rel.includes([:instruments]) : rel
    end

    def _process_results_page(_results)

      @results = _results
      if user
        @user_counters = @results.inject({}) { |hh,val| hh[val.id] = {}; hh }

        # this gets counts for each search result 
        @results.each do |bb|
          counters = { 
            COUNT_FOLLOW => Follow.where(:followable_id => bb.id).count,
            COUNT_RECORD => Recording.where(:band_id => bb.id).count,
            COUNT_SESSION => MusicSession.where(:band_id => bb.id).count
          }
          @user_counters[bb.id] = counters
        end
      else
        @user_counters = {}
      end
      self
    end

    private 

    def _count(band, key)
      if mm = @user_counters[band.id] 
        return mm[key]
      end if @user_counters
      0
    end

    public

    def follow_count(band)
      _count(band, COUNT_FOLLOW)
    end

    def record_count(band)
      _count(band, COUNT_RECORD)
    end

    def session_count(band)
      _count(band, COUNT_SESSION)
    end

    def is_follower?(band)
      if mm = @user_counters[band.id] 
        return mm.include?(RESULT_FOLLOW)
      end if @user_counters
      false
    end

    def search_type
      self.class.to_s
    end

    def is_blank?(subtype=TO_JOIN)
      self.search_filter_for_subtype(subtype) == self.class.json_schema[subtype]
    end

    def reset_filter(subtype, data=nil)
      data ||= self.class.json_schema[subtype]
      dblob = self.data_blob
      dblob[subtype] = data
      self.data_blob = dblob
      self.save
    end

    def reset_search_results(subtype=TO_JOIN)
      reset_filter(subtype)
      search_results_page(subtype)
    end

    def self.search_filter_json(user, subtype=TO_JOIN)
      self.user_search_filter(user).json[subtype]
    end

    def search_filter_for_subtype(subtype)
      self.data_blob[subtype]
    end

    def search_results_page(subtype=TO_JOIN, filter=nil, page=1)
      if filter
        reset_filter(subtype, filter)
      else
        filter = self.search_filter_for_subtype(subtype)
      end
      rel = do_search(filter)

      @page_number = [page.to_i, 1].max
      rel = rel.paginate(:page => @page_number, :per_page => self.class::PER_PAGE)

      rel = self.search_includes(rel, subtype)
      @page_count = rel.total_pages

      _process_results_page(rel.all)
    end

    def _add_description(descrip, add)
      descrip += "; " if 0 < descrip.length
      descrip + add
    end
    
    def description(subtype=TO_JOIN)
      return '' if self.is_blank?(subtype)

      filter = search_filter_for_subtype(subtype)
      str = ''

      if filter.has_key?(KEY_SORT_ORDER)
        str += 'Sort = '
        case sort = filter[KEY_SORT_ORDER]
        when 'distance'
          str += SORT_ORDERS[sort]
        when 'latency'
          str += SORT_ORDERS[sort]
        when 'price_asc'
          str += HIRE_SORT_ORDERS[sort]
        when 'price_desc'
          str += HIRE_SORT_ORDERS[sort]
        end 
      end
      if (val = filter[KEY_BAND_TYPE]) != ANY_VAL_STR
        str = _add_description(str, "Band type = #{BAND_TYPES[val]}")
      end if filter.has_key?(KEY_BAND_TYPE)

      if (val = filter[KEY_BAND_STATUS]) != ANY_VAL_STR
        str = _add_description(str, "Band status = #{BAND_STATUS[val]}")
      end if filter.has_key?(KEY_BAND_STATUS)

      if (val = filter[KEY_PLAY_COMMIT]) != ANY_VAL_STR
        str = _add_description(str, "Play commitment = #{PLAY_COMMITS[val]}")
      end if filter.has_key?(KEY_PLAY_COMMIT)

      if (val = filter[KEY_TOUR_OPTION]) != ANY_VAL_STR
        str = _add_description(str, "Touring options = #{TOUR_OPTIONS[val]}")
      end if filter.has_key?(KEY_TOUR_OPTION)

      if (val = filter[KEY_PERF_SAMPLES]) != ANY_VAL_STR
        str = _add_description(str, "Performance samples = #{PERF_SAMPLES[val]}")
      end if filter.has_key?(KEY_PERF_SAMPLES)

      if (val = filter[KEY_HIRE_MAX_COST].to_i) > 0
        str = _add_description(str, "Maximum gig cost = $#{val}")
      end if filter.has_key?(KEY_HIRE_MAX_COST)

      if 0 < filter[KEY_HIRE_FREE]
        str = _add_description(str, "Bands playing free gigs")
      end if filter.has_key?(KEY_HIRE_FREE)

      if (val = filter[KEY_GIGS].to_i) != GIG_COUNTS[0]
        str = _add_description(str, "Concert gigs = #{GIG_LABELS[val]}")
      end if filter.has_key?(KEY_GIGS)

      if 0 < (val = filter[KEY_GENRES]).length
        gstr = "Genres = "
        genres = Genre.where(["id IN (?)", val]).order('description').pluck(:description)
        gstr += genres.join(', ')
        str = _add_description(str, gstr)
      end if filter.has_key?(KEY_GENRES)

      if 0 < ((val = filter[KEY_INSTRUMENTS]) || '').length
        istr = "Instruments = "
        instr_ids = val.collect { |vv| vv['instrument_id'] }
        instrs = Instrument.where(["id IN (?)", instr_ids]).order(:description)
        instrs.each_with_index do |ii, idx| 
          proficiency = val.detect { |vv| vv['instrument_id'] == ii.id }['proficiency_level']
          istr += "#{ii.description} (#{INSTRUMENT_PROFICIENCY[proficiency.to_i]})"
          istr += ', ' unless idx==(instrs.length-1)
        end
        str = _add_description(str, istr)
      end if filter.has_key?(KEY_INSTRUMENTS)
      str = "Current Search: #{str}"
      str
    end

  end
end
