{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Brig.Provider.Tag
  ( Bucket (..),
    defBucket,
    foldTags,
    unfoldTags,
    unfoldTagsInto,
    diffTags,
    nonEmptyTags,
    tagToInt,
    intToTag,
  )
where

import Cassandra.CQL (Cql)
import Data.Bits
import Data.Range
import Data.Set qualified as Set
import Imports
import Wire.API.Provider.Service.Tag (ServiceTag (..))

newtype Bucket = Bucket Int32
  deriving newtype (Cql, Show)

-- | Bucketing allows us to distribute individual tag bitmasks
-- across multiple wide rows, if it should become necessary.
-- If a tag bitmask it spread across buckets, lookups and deletes
-- on that bitmask will require O(n) queries, where /n/ is the number
-- of buckets for a specific tag, whereas writes can stay at O(1)
-- by always writing to the "newest" bucket.
defBucket :: Bucket
defBucket = Bucket 201608

foldTags :: Range 1 3 (Set ServiceTag) -> Int64
foldTags = foldl' (.|.) 0 . map tagToInt . Set.toList . fromRange

unfoldTags :: Range 0 3 (Set ServiceTag) -> [Int64]
unfoldTags s = case map tagToInt (Set.toList (fromRange s)) of
  [] -> []
  [t] -> [t]
  ts@[t, u] -> (t .|. u) : ts
  ts@[t, u, v] -> (t .|. u) : (t .|. v) : (u .|. v) : (t .|. u .|. v) : ts
  _ -> error "Brig.Provider.DB.Tag: unfoldTags: Too many tags."

unfoldTagsInto :: Range 1 3 (Set ServiceTag) -> [Int64] -> [Int64]
unfoldTagsInto xs ys =
  let xs' = unfoldTags (rcast xs)
   in xs' ++ concatMap (\x -> map (.|. x) ys) xs'

diffTags ::
  Range 0 3 (Set ServiceTag) ->
  Range 0 3 (Set ServiceTag) ->
  Range 0 3 (Set ServiceTag)
diffTags a b = unsafeRange $ Set.difference (fromRange a) (fromRange b)

nonEmptyTags :: Range m 3 (Set ServiceTag) -> Maybe (Range 1 3 (Set ServiceTag))
nonEmptyTags r
  | Set.null (fromRange r) = Nothing
  | otherwise = Just (unsafeRange (fromRange r))

tagToInt :: ServiceTag -> Int64
tagToInt AudioTag = 0b1
tagToInt BooksTag = 0b10
tagToInt BusinessTag = 0b100
tagToInt DesignTag = 0b1000
tagToInt EducationTag = 0b10000
tagToInt EntertainmentTag = 0b100000
tagToInt FinanceTag = 0b1000000
tagToInt FitnessTag = 0b10000000
tagToInt FoodDrinkTag = 0b100000000
tagToInt GamesTag = 0b1000000000
tagToInt GraphicsTag = 0b10000000000
tagToInt HealthTag = 0b100000000000
tagToInt IntegrationTag = 0b1000000000000
tagToInt LifestyleTag = 0b10000000000000
tagToInt MediaTag = 0b100000000000000
tagToInt MedicalTag = 0b1000000000000000
tagToInt MoviesTag = 0b10000000000000000
tagToInt MusicTag = 0b100000000000000000
tagToInt NewsTag = 0b1000000000000000000
tagToInt PhotographyTag = 0b10000000000000000000
tagToInt PollTag = 0b100000000000000000000
tagToInt ProductivityTag = 0b1000000000000000000000
tagToInt QuizTag = 0b10000000000000000000000
tagToInt RatingTag = 0b100000000000000000000000
tagToInt ShoppingTag = 0b1000000000000000000000000
tagToInt SocialTag = 0b10000000000000000000000000
tagToInt SportsTag = 0b100000000000000000000000000
tagToInt TravelTag = 0b1000000000000000000000000000
tagToInt TutorialTag = 0b10000000000000000000000000000
tagToInt VideoTag = 0b100000000000000000000000000000
tagToInt WeatherTag = 0b1000000000000000000000000000000

intToTag :: Int64 -> Maybe ServiceTag
intToTag 0b1 = pure AudioTag
intToTag 0b10 = pure BooksTag
intToTag 0b100 = pure BusinessTag
intToTag 0b1000 = pure DesignTag
intToTag 0b10000 = pure EducationTag
intToTag 0b100000 = pure EntertainmentTag
intToTag 0b1000000 = pure FinanceTag
intToTag 0b10000000 = pure FitnessTag
intToTag 0b100000000 = pure FoodDrinkTag
intToTag 0b1000000000 = pure GamesTag
intToTag 0b10000000000 = pure GraphicsTag
intToTag 0b100000000000 = pure HealthTag
intToTag 0b1000000000000 = pure IntegrationTag
intToTag 0b10000000000000 = pure LifestyleTag
intToTag 0b100000000000000 = pure MediaTag
intToTag 0b1000000000000000 = pure MedicalTag
intToTag 0b10000000000000000 = pure MoviesTag
intToTag 0b100000000000000000 = pure MusicTag
intToTag 0b1000000000000000000 = pure NewsTag
intToTag 0b10000000000000000000 = pure PhotographyTag
intToTag 0b100000000000000000000 = pure PollTag
intToTag 0b1000000000000000000000 = pure ProductivityTag
intToTag 0b10000000000000000000000 = pure QuizTag
intToTag 0b100000000000000000000000 = pure RatingTag
intToTag 0b1000000000000000000000000 = pure ShoppingTag
intToTag 0b10000000000000000000000000 = pure SocialTag
intToTag 0b100000000000000000000000000 = pure SportsTag
intToTag 0b1000000000000000000000000000 = pure TravelTag
intToTag 0b10000000000000000000000000000 = pure TutorialTag
intToTag 0b100000000000000000000000000000 = pure VideoTag
intToTag 0b1000000000000000000000000000000 = pure WeatherTag
intToTag _ = Nothing
