module Database exposing (Database, decode, decoder, encode, fromCardData, get, getAll) import Card exposing (CardData, CardDetails, CardPerformanceData, CardType(..), ManaColor(..), Power(..), Rarity(..), parseManaCost, parsePower) import Dict exposing (Dict) import Json.Decode as Decode exposing (Decoder, decodeString) import Json.Decode.Pipeline exposing (custom, optional, required) import Json.Encode as Encode import Tuple exposing (pair) type Database = Database (Dict String CardData) fromCardData : List CardData -> Database fromCardData cards = List.map (\card -> ( card.details.name, card )) cards |> Dict.fromList |> Database get : String -> Database -> Maybe CardData get name (Database db) = Dict.get name db getAll : Database -> List CardData getAll (Database db) = Dict.values db decode : String -> Result String ( String, Maybe Database ) decode setData = decodeString decoder setData |> Result.mapError Decode.errorToString decoder : Decoder ( String, Maybe Database ) decoder = Decode.map2 pair (Decode.field "code" Decode.string) (Decode.field "data" (Decode.nullable (Decode.map2 createDatabase (Decode.field "ratings" decodePerformanceData) (Decode.field "cards" decodeSetData) ) ) ) databaseToDict : Database -> Dict String CardData databaseToDict (Database db) = db encode : ( String, Database ) -> Encode.Value encode ( setCode, Database db ) = Encode.object [ ( "code", Encode.string setCode ) , ( "data" , Encode.object [ ( "ratings", Encode.list encodeCardPerformance (Dict.values db) ) , ( "cards", Encode.list encodeCardDetails (Dict.values db) ) ] ) ] createDatabase : Dict String CardPerformanceData -> Dict String CardDetails -> Database createDatabase performanceData detailsData = Dict.merge (\_ _ cardData -> cardData) (\name performance details -> Dict.insert name (CardData details performance)) (\_ _ cardData -> cardData) performanceData detailsData Dict.empty |> Database decodePerformanceData : Decoder (Dict String CardPerformanceData) decodePerformanceData = Decode.list decodeCardPerformance |> Decode.map Dict.fromList decodeSetData : Decoder (Dict String CardDetails) decodeSetData = Decode.list decodeCardDetails |> Decode.map Dict.fromList encodeCardPerformance : CardData -> Encode.Value encodeCardPerformance d = Encode.object [ ( "Name", Encode.string d.details.name ) , ( "# Seen", encodeIntString d.performance.totalTimesSeen ) , ( "# Picked", encodeIntString d.performance.totalTimesPicked ) , ( "ATA", encodeMaybeFloatString d.performance.averagePickPosition ) , ( "ALSA", encodeMaybeFloatString d.performance.averageSeenPosition ) , ( "GIH WR", encodeMaybePercentageString d.performance.gameInHandWinRate ) , ( "IWD" , Encode.string (case d.performance.improvementWhenDrawn of Just i -> String.fromFloat i ++ "pp" Nothing -> "" ) ) ] decodeCardPerformance : Decoder ( String, CardPerformanceData ) decodeCardPerformance = Decode.map2 pair (Decode.field "Name" Decode.string) (Decode.succeed CardPerformanceData |> required "# Seen" decodeIntString |> required "# Picked" decodeIntString |> required "ATA" decodeMaybeFloatString |> required "ALSA" decodeMaybeFloatString |> required "GIH WR" decodeMaybePercentageString -- The improvement when drawn changed to improvement in hand in newer set data -- from 17lands |> custom decodeIIHOrIWD ) decodeIIHOrIWD : Decoder (Maybe Float) decodeIIHOrIWD = Decode.oneOf [ Decode.at [ "IIH" ] decodeImprovementWhenDrawn , Decode.at [ "IWD" ] decodeImprovementWhenDrawn ] decodeImprovementWhenDrawn : Decoder (Maybe Float) decodeImprovementWhenDrawn = Decode.string |> Decode.andThen (\s -> if s == "" then Decode.succeed Nothing else case String.toFloat (String.replace "pp" "" s) of Just i -> Decode.succeed (Just i) Nothing -> Decode.succeed Nothing ) encodeIntString : Int -> Encode.Value encodeIntString i = Encode.string (String.fromInt i) decodeIntString : Decoder Int decodeIntString = Decode.string |> Decode.andThen (\s -> case String.toInt s of Just i -> Decode.succeed i Nothing -> Decode.fail "Invalid integer" ) encodeMaybeFloatString : Maybe Float -> Encode.Value encodeMaybeFloatString m = case m of Just f -> Encode.string (String.fromFloat f) Nothing -> Encode.string "" decodeMaybeFloatString : Decoder (Maybe Float) decodeMaybeFloatString = Decode.string |> Decode.andThen (\s -> if s == "" then Decode.succeed Nothing else case String.toFloat s of Just i -> Decode.succeed (Just i) Nothing -> Decode.succeed Nothing ) encodeMaybePercentageString : Maybe Float -> Encode.Value encodeMaybePercentageString m = case m of Just f -> Encode.string (String.fromFloat (f * 100) ++ "%") Nothing -> Encode.string "" decodeMaybePercentageString : Decoder (Maybe Float) decodeMaybePercentageString = Decode.string |> Decode.andThen (\s -> if s == "" then Decode.succeed Nothing else case String.toFloat (String.replace "%" "" s) of Just i -> Decode.succeed (Just (i / 100)) Nothing -> Decode.succeed Nothing ) encodeCardDetails : CardData -> Encode.Value encodeCardDetails card = Encode.object [ ( "name", Encode.string card.details.name ) , ( "cmc", Encode.int card.details.cmc ) , ( "type_line", Encode.string card.details.typeLine ) , ( "oracle_text" , case card.details.oracleText of Just t -> Encode.string t Nothing -> Encode.null ) , ( "power", encodePower card.details.power ) , ( "toughness", encodePower card.details.toughness ) , ( "colors", Encode.list encodeManaColor card.details.colors ) , ( "mana_cost", Encode.string card.details.rawManaCost ) , ( "image_uris", Encode.object [ ( "large", Encode.string card.details.imageUrl ) ] ) , ( "rarity", encodeRarity card.details.rarity ) ] encodeRarity : Rarity -> Encode.Value encodeRarity r = case r of Common -> Encode.string "common" Uncommon -> Encode.string "uncommon" Rare -> Encode.string "rare" Mythic -> Encode.string "mythic" NoRarity -> Encode.string "" decodeCardDetails : Decoder ( String, CardDetails ) decodeCardDetails = Decode.map2 pair (Decode.field "name" Decode.string) (Decode.succeed CardDetails |> required "name" Decode.string |> required "cmc" Decode.int |> required "type_line" decodeCardType |> required "type_line" Decode.string |> optional "oracle_text" (Decode.nullable Decode.string) Nothing |> optional "power" (Decode.nullable decodePower) Nothing |> optional "toughness" (Decode.nullable decodePower) Nothing |> required "colors" (Decode.list decodeManaColor) |> required "mana_cost" Decode.string |> required "mana_cost" (Decode.string |> Decode.andThen (\s -> case parseManaCost s of Just m -> Decode.succeed (Just m) Nothing -> Decode.succeed Nothing ) ) |> required "image_uris" (Decode.field "large" Decode.string) |> optional "rarity" decodeRarity NoRarity ) encodePower : Maybe Power -> Encode.Value encodePower p = case p of Just (ConstantPower i) -> Encode.string (String.fromInt i) Just (ConstantPlusVariablePower i) -> Encode.string (String.fromInt i ++ "+*") Just VariablePower -> Encode.string "*" Nothing -> Encode.null decodePower : Decoder Power decodePower = Decode.string |> Decode.andThen (\s -> case parsePower s of Just p -> Decode.succeed p Nothing -> Decode.fail ("Could not parse power: " ++ s) ) encodeManaColor : ManaColor -> Encode.Value encodeManaColor c = case c of Red -> Encode.string "R" Blue -> Encode.string "U" Green -> Encode.string "G" White -> Encode.string "W" Black -> Encode.string "B" Colorless -> Encode.string "C" decodeManaColor : Decoder ManaColor decodeManaColor = Decode.string |> Decode.andThen (\c -> case c of "R" -> Decode.succeed Red "U" -> Decode.succeed Blue "G" -> Decode.succeed Green "W" -> Decode.succeed White "B" -> Decode.succeed Black "C" -> Decode.succeed Colorless _ -> Decode.fail "Invalid mana color" ) decodeCardType : Decoder CardType decodeCardType = Decode.string |> Decode.map (\c -> if String.contains "Creature" c then Creature else if String.contains "Instant" c then Instant else if String.contains "Sorcery" c then Sorcery else if String.contains "Enchantment" c then Enchantment else if String.contains "Artifact" c then Artifact else if String.contains "Planeswalker" c then Planeswalker else if String.contains "Land" c then Land else Other ) decodeRarity : Decoder Rarity decodeRarity = Decode.string |> Decode.map (\r -> if String.contains "mythic" r then Mythic else if String.contains "rare" r then Rare else if String.contains "uncommon" r then Uncommon else if String.contains "common" r then Common else NoRarity )