--[[----------------------------------------------------------------------------

WFCatSearch.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2015 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remains the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'

	-- WF.oz_client.managed_catalog.search
local WFCatSearchQuery = require 'WFCatSearchQuery'

--==============================================================================

local WFCatSearch = {}

--------------------------------------------------------------------------------

local KSTableUtils_derefOpt = require 'KSTableUtils'.derefOpt

--------------------------------------------------------------------------------

function WFCatSearch.search( wfCatalog )

	local indexStore = wfCatalog:indexStore()
	return indexStore:query {
		sql = "SELECT docId FROM assets",
		singleColumnQuery = true
	}
	:map( function( assetIds )
		return {
			content = assetIds,
		}
	end )

end

--------------------------------------------------------------------------------

local function searchSupportsStacks( searchDescription )

	-- 2018.09.07 bkruse
	-- 
	-- Only support stacks if show all photos or a single album, stacks
	-- currently don't display correctly otherwise

 	local predicateTerms = KSTableUtils.derefOpt( searchDescription, 'predicates', 'terms' ) 
			or { KSTableUtils.derefOpt( searchDescription, 'predicates' ) }

	local albumsSeen = 0
	
	for _, term in ipairs( predicateTerms ) do

		if not term.album then

			-- contains a non-album search filter, don't show stacks
			return false

		elseif albumsSeen > 0 then

			-- multiple albums, don't show stacks
			return false

		end

	end

	-- stacks are supported
	return true

end

--------------------------------------------------------------------------------

local function makeSearchORDERBY( orderingDirection )

	return string.format( 
				[[ORDER BY captureDate %s, 
							filenameLC COLLATE i18n_collate %s, 
							docId %s]],
				orderingDirection,
				orderingDirection,
				orderingDirection )

end

--------------------------------------------------------------------------------

local kDescendingORDERBY = makeSearchORDERBY( "DESC" )

--------------------------------------------------------------------------------

local kAscendingORDERBY = makeSearchORDERBY( "ASC" )

--------------------------------------------------------------------------------

local function getORDERBYSQL( currentRequestedSortOrder )

	if type( currentRequestedSortOrder ) ~= "string" then

		return kDescendingORDERBY

	end

	return string.find( string.upper( currentRequestedSortOrder ), "DESC" ) and
				kDescendingORDERBY or kAscendingORDERBY

end

--------------------------------------------------------------------------------

local searchResultColumns =  require 'WFCatAssetInfoListUtils'.getRequiredFields()

--------------------------------------------------------------------------------

function WFCatSearch.executeSearch( wfCatalog, searchDescription, currentRequestedSortOrder )

	local orderBYSQL = getORDERBYSQL( currentRequestedSortOrder )
	
	if wfCatalog == nil then
		return Observable.never()
	end
	local indexStore = wfCatalog:indexStore()
	local searchSchema = wfCatalog:searchSchema()
	
	-- Rani [TODO] : https://jira.corp.adobe.com/browse/NMB-718
	-- Separate Asset Table Data and other filtering data
	
	local queryParamsStream = 
		WFCatSearchQuery.createQueryParamsStream( 
			searchSchema, searchDescription )

 	local supportsStacks = searchSupportsStacks( searchDescription )
	
	local maybeWhereClause = ""
	
	-- SPAM [jandrew 2019-05-24]: The following logic prevents Group Album assets from
	-- showing up anywhere but in the actual album view.
	
	local hasAlbumPredicate = false
	
	local predicateTerms = KSTableUtils_derefOpt( searchDescription, 'predicates', 'terms' )
	
	if predicateTerms then
	
		for _, term in ipairs( predicateTerms ) do
		
			if term.type == 'album' then
			
				hasAlbumPredicate = true
				break
			
			end
			
		end
	
	else
	
		local singlePredicateType = KSTableUtils_derefOpt( searchDescription, 'predicates', 'type' )
	
		if singlePredicateType == 'album' then
			hasAlbumPredicate = true
		end
	
	end
	
	if not hasAlbumPredicate then
	
		if not supportsStacks then
			maybeWhereClause = "WHERE spaceId IS NULL AND ( subtype == 'image' OR subtype == 'video' )"
		else
			maybeWhereClause = "WHERE spaceId IS NULL"
		end
	
	elseif not supportsStacks then
	
		maybeWhereClause = "WHERE subtype == 'image' OR subtype == 'video'"
		
	end

	if queryParamsStream then

		local sqlWithJOINPlaceholder = 
			string.format( 
				[[SELECT %s 
					FROM assets 
					@JOIN
					%s
					%s]],
				searchResultColumns,
				maybeWhereClause,
				orderBYSQL )

		return WFCatSearchQuery.resultSetQuery( 
				indexStore, sqlWithJOINPlaceholder, 
				queryParamsStream, 'assets.docId' )

	end

	local sql = string.format( 
			[[SELECT %s 
				FROM assets 
				%s
				%s]],
			searchResultColumns,
			maybeWhereClause,
			orderBYSQL )

	return indexStore:query( {

			sql = sql,

			calm = WFCatSearchQuery.defaultQueryCalmRate

		} )

end

--------------------------------------------------------------------------------

WFCatSearch.searchSupportsStacks = searchSupportsStacks

--------------------------------------------------------------------------------

function WFCatSearch.makeCountModel( wfCatalogStream, searchDescription )
		
	local search = 
	wfCatalogStream
	:map( function( wfCatalog )
		if wfCatalog == nil then
			return Observable.just( 0 )
		end
		if searchDescription and ( searchDescription.operator or searchDescription.type ) then
			-- it wasn't wrapped as a searchDescription, this is just an expression
			searchDescription = {
				predicates = searchDescription
			}
		end

		local indexStore = wfCatalog:indexStore()
		local searchSchema = wfCatalog:searchSchema()

		local queryParamsStream = 
				WFCatSearchQuery.createQueryParamsStream( 
					searchSchema, searchDescription )

		return WFCatSearchQuery.count( indexStore, queryParamsStream )

	end )

	:switch()

	return {
		value = search,
	}
end

--------------------------------------------------------------------------------

function WFCatSearch.makeQueryTermCategoriesModel()
	return {
		value = {
			{
				term = 'general',
				ztitle = "$$$/WFCatSearch/TermCategories/General=General",
			},
			{
				term = 'picks',
				ztitle = "$$$/WFCatSearch/TermCategories/Flags=Flags",
			},
			{
				term = 'ratings',
				ztitle = "$$$/WFCatSearch/TermCategories/Ratings=Ratings",
			},
--			{
--				term = 'albums',
--				ztitle = "$$$/WFCatSearch/TermCategories/Albums=Collections",
--			},
--			{
--				term = 'dates',
--				ztitle = "$$$/WFCatSearch/TermCategories/Date=Dates",
--			},
--			{
--				term = 'keywords',
--				ztitle = "$$$/WFCatSearch/TermCategories/Keywords=Keywords",
--			},
--			{
--				term = 'locations',
--				ztitle = "$$$/WFCatSearch/TermCategories/Places=Places",
--			},
		}
	}
end

--------------------------------------------------------------------------------

local function _makeQueryTermsValue( wfCatalog, termCategory, searchDescription )
	local KSJsonEncoder = require 'KSJsonEncoder'

	local json = KSJsonEncoder.encodeWebSafeJson( searchDescription )
	local len = string.len( json ) + 1

	-- use a hyperbolic function based on the length of the search query
	-- to do build a fake count
	local count = math.floor( ( 999999 + len ) / len )

	local abortCode
		-- magic number used to figure out how many fake entries to include

	local terms = {}

	if termCategory == 'general' then
		terms[ #terms + 1 ] = {
			type = 'all',
			ztitle = "$$$/WFCatSearch/TermCategories/General/SubItem/All=All",
			expression = {
				type = 'all',
			}
		}
--		terms[ #terms + 1 ] = {
--			type = 'all',
--			ztitle = "$$$/WFCatSearch/TermCategories/General/SubItem/Photos=Photos",
--			expression = {
--				type = 'subtype',
--				subtype = 'jpeg',
--			}
--		}
--		terms[ #terms + 1 ] = {
--			type = 'all',
--			ztitle = "$$$/WFCatSearch/TermCategories/General/SubItem/Videos=Videos",
--			expression = {
--				type = 'subtype',
--				subtype = 'video',
--			}
--		}
--		terms[ #terms + 1 ] = {
--			type = 'all',
--			ztitle = "$$$/WFCatSearch/TermCategories/General/SubItem/LastImport=Last Import",
--			expression = {
--				type = 'importIndex',
--				importIndex = -1,
--			}
--		}
	end

	if termCategory == 'albums' then
		terms[ #terms + 1 ] = {
			type = 'album',
			label = 'My Album',
			expression = {
				type = 'album',
				album = 'abcd1234abcd1234abcd1234abcd1234',
			}
		}
		terms[ #terms + 1 ] = {
			type = 'album',
			label = 'My OtherAlbum',
			expression = {
				type = 'album',
				album = '1234abcd1234abcd1234abcd1234abcd',
			}
		}
	end

	if termCategory == 'ratings' and wfCatalog then
		local userId = wfCatalog:ozUserId()

		terms[ #terms + 1 ] = {
			type = 'rating',
			rating = 0,
			ztitle = "$$$/WFCatSearch/TermCategories/Ratings/Rating/0=No Stars",
			expression = {
				type = 'all',
			}
		}
		terms[ #terms + 1 ] = {
			type = 'rating',
			rating = 1,
			ztitle = "$$$/WFCatSearch/TermCategories/Ratings/Rating/1=One Star",
			expression = {
				type = 'rating',
				rating = 1,
				operator = '>=',
				user = userId,
			}
		}
		terms[ #terms + 1 ] = {
			type = 'rating',
			rating = 2,
			ztitle = "$$$/WFCatSearch/TermCategories/Ratings/Rating/2=Two Stars",
			expression = {
				type = 'rating',
				rating = 2,
				operator = '>=',
				user = userId,
			}
		}
		terms[ #terms + 1 ] = {
			type = 'rating',
			rating = 3,
			ztitle = "$$$/WFCatSearch/TermCategories/Ratings/Rating/3=Three Stars",
			expression = {
				type = 'rating',
				rating = 3,
				operator = '>=',
				user = userId,
			}
		}
		terms[ #terms + 1 ] = {
			type = 'rating',
			rating = 4,
			ztitle = "$$$/WFCatSearch/TermCategories/Ratings/Rating/4=Four Stars",
			expression = {
				type = 'rating',
				rating = 4,
				operator = '>=',
				user = userId,
			}
		}
		terms[ #terms + 1 ] = {
			type = 'rating',
			rating = 5,
			ztitle = "$$$/WFCatSearch/TermCategories/Ratings/Rating/5=Five Stars",
			expression = {
				type = 'rating',
				rating = 5,
				operator = '>=',
				user = userId,
			}
		}

	end

	if termCategory == 'keyword' then
		local keywords = {
			"Beach", "Bird", "Black", "Blue", "Cat", "Dog", "House", "Red",
			"Sunset", "Tree", "Treehouse", "White", "Yellow",
		}

		abortCode = 60000
		for _, keyword in ipairs( keywords ) do
			if count > abortCode then
				terms[ #terms + 1 ] = {
					type = 'keyword',
					label = keyword,
					expression = {
						type = 'keyword',
						keyword = keyword,
					}
				}
			end
			abortCode = abortCode / 2
		end
	end

	if termCategory == 'locations' then
		local WFZString = require 'WFZString'

		local places = {
			"St. Paul, MN", "San Fransisco, CA", "Seattle, WA"
		}

		abortCode = 20000
		for _, place in ipairs( places ) do
			if count > abortCode then
				terms[ #terms + 1 ] = {
					type = 'location',
					label = place,
					expression = {
						type = 'location',
						geotag = place,
					}
				}
			end
			abortCode = abortCode / 2
		end

		if count > 8000 then
			terms[ #terms + 1 ] = {
				type = 'location',
				label = WFZString.fromZString( "$$$/private/Search/Location=^0^d^1 ^2^d^3",
					45, 'N', '93', 'W' ),
				expression = {
					type = 'location',
					geohash = 'cbjb',
				}
			}
		end
	end

	if termCategory == 'picks' and wfCatalog then
		local searchFlags = {
			Flagged = 'pick',
			Unflagged = 'unflagged',
			Rejected = 'rejected'
		}
		local userId = wfCatalog:ozUserId()
		for _, flag in ipairs { "Flagged", "Unflagged", "Rejected" } do
			terms[ #terms + 1 ] = {
				type = 'picks',
				label = flag,
				expression = {
					type = 'review',
					flag = searchFlags[ flag ],
					operator = '==',
					user = userId,
				}
			}
		end
	end

	if termCategory == 'dates' then
		if count > 60000 then
			terms[ #terms + 1 ] = {
				type = 'dates',
				subtype = 'years',
				years = {
					2006, 2007, 2008, 2010, 2012, 2014, 2015
				}
			}
		elseif count > 2000 then
			terms[ #terms + 1 ] = {
				type = 'dates',
				subtype = 'months',
				year = 2015,
				months = {
					1, 2, 3, 5,
				}
			}
		elseif count > 100 then
			terms[ #terms + 1 ] = {
				type = 'dates',
				subtype = 'days',
				year = 2015,
				month = 3,
				days = {
					8, 9, 20, 21, 22
				}
			}
		end
	end

	local WFCrypto = require 'WFCrypto'

	for _, term in ipairs( terms ) do

		local termJson = KSJsonEncoder.encodeWebSafeJson( term )
		local digest = WFCrypto.SHA1.digest( termJson )
		term.clientId = digest

	end

	return terms
end

function WFCatSearch.makeQueryTermsModel( wfCatalogStream, termCategory, searchDescription )

	return {
		value = wfCatalogStream:map( function( wfCatalog )
			return _makeQueryTermsValue( wfCatalog, termCategory, searchDescription )
		end ),
	}

end

--==============================================================================

return WFCatSearch
