package com.adlerosn.brasilfurfest.schedule.managers import androidx.annotation.DrawableRes import com.adlerosn.brasilfurfest.R import com.adlerosn.brasilfurfest.helper.* import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.GregorianCalendarRange import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.* import org.json.JSONArray import org.json.JSONObject import java.io.File import java.io.InputStream import java.util.* object ConventionJsonReader { internal enum class Icons(@DrawableRes val drawableId: Int) { ic_telegram(R.drawable.ic_telegram), ic_twitter(R.drawable.ic_twitter), ic_facebook_official(R.drawable.ic_facebook_official), ic_youtube_play(R.drawable.ic_youtube_play), ic_instagram(R.drawable.ic_instagram), ic_web_black_24dp(R.drawable.ic_web_black_24dp) } internal class UuidHashResolver{ val conventionSeries = mutableMapOf() val conventions = mutableMapOf() val events = mutableMapOf() val places = mutableMapOf() val registrationTiers = mutableMapOf() val socialMedias = mutableMapOf() val tags = mutableMapOf() val languageStrings = mutableMapOf>() val additionalRules = mutableMapOf() val banners = mutableMapOf() val maps = mutableMapOf() val registrationLinks = mutableMapOf() val announcements = mutableMapOf() } fun readFromFile(file: String) = readFromFile(File(file)) fun readFromFile(file: File) = readFromJson(file.readText(Charsets.UTF_8)) fun readFromInputStream(inputStream: InputStream): ConventionSeries = readFromJson(inputStream.bufferedReader(Charsets.UTF_8).readText()) fun readFromByteArray(byteArray: ByteArray): ConventionSeries = readFromInputStream(byteArray.inputStream()) fun readFromJson(json: String) = readFromJson(JSONObject(json)) fun readFromJson(json: JSONObject) = getConventionSeries(json, UuidHashResolver()) private fun getConventionSeries(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): ConventionSeries { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.conventionSeries[uuid] if (previouslyKnown != null) return previouslyKnown val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver) val language = parseLanguage(jsonObject.getString("language")) val timezone = parseTimeZone(jsonObject.getJSONObject("timezone")) val statute = parseLanguageString(jsonObject.getJSONObject("statute"), uuidHashResolver) val socialMedias = parseSocialMedias(jsonObject.getJSONArray("social_medias"), uuidHashResolver) val editions = parseConventions(jsonObject.getJSONArray("editions"), uuidHashResolver, statute, socialMedias, name) val featured = uuidHashResolver.conventions[jsonObject.getString("featured")] ?: uuidHashResolver.conventions.values.first() val defaultBanner = parseLanguageImage(jsonObject.getJSONObject("default_banner"), uuidHashResolver)!! val banners = parseBanners(jsonObject.getJSONArray("banners"), uuidHashResolver) val announcements = parseAnnouncements(jsonObject.getJSONArray("announcements"), uuidHashResolver) return ConventionSeries( name, featured, editions, timezone, socialMedias, language, statute, defaultBanner, banners, 1.0/6, announcements ).apply { uuidHashResolver.conventionSeries[uuid] = this } } private fun parseAnnouncements(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parseAnnouncement(it, uuidHashResolver) }.toList() private fun parseAnnouncement(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): Announcement { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.announcements[uuid] if (previouslyKnown != null) return previouslyKnown val title = parseLanguageString(jsonObject.getJSONObject("title"), uuidHashResolver) val body = parseLanguageString(jsonObject.getJSONObject("body"), uuidHashResolver) val link = parseLanguageString(jsonObject.getJSONObject("link"), uuidHashResolver) val created = parseGregCal(jsonObject.getJSONObject("created")) val modified = parseGregCal(jsonObject.getJSONObject("modified")) return Announcement( uuid, title, body, link, created, modified ).apply { uuidHashResolver.announcements[uuid] = this } } private fun parseBanners(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver) = jsonArray.asSequence().map { parseBanner(it, uuidHashResolver) }.toList() private fun parseBanner(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): Banner { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.banners[uuid] if (previouslyKnown != null) return previouslyKnown val alias = jsonObject.getString("alias") val image = parseLanguageImage(jsonObject.getJSONObject("banner"), uuidHashResolver)!! val lifetime = GregorianCalendarRange( parseGregCal(jsonObject.getJSONObject("show_up_after")), parseGregCal(jsonObject.getJSONObject("hide_after")) ) return Banner( alias, lifetime, image ).apply { uuidHashResolver.banners[uuid] = this } } private fun parseConventions(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver, statute: Map, socialMedias: List, name: Map): List = jsonArray.asSequence().map { parseConvention(it, uuidHashResolver, statute, socialMedias, name) }.toList() private fun parseConvention(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver, rules: Map, socialMedias: List, genericName: Map): Convention { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.conventions[uuid] if (previouslyKnown != null) return previouslyKnown val notificationFireMinutesBefore = jsonObject.getInt("fire_notifications_n_minutes_before") val paddingStartOfDayMillis = parseGregCal(jsonObject.getJSONObject("convention_day_start_time")).let { val hr = it.get(GregorianCalendar.HOUR_OF_DAY) val mn = it.get(GregorianCalendar.MINUTE) val sc = it.get(GregorianCalendar.SECOND) ((hr*3600L)+(mn*60L)+sc)*1000L } val paddingEndOfDayMillis = parseGregCal(jsonObject.getJSONObject("convention_day_end_time")).let { val hr = it.get(GregorianCalendar.HOUR_OF_DAY) val mn = it.get(GregorianCalendar.MINUTE) val sc = it.get(GregorianCalendar.SECOND) ((hr*3600L)+(mn*60L)+sc)*1000L }.let { if (it == 0L) 24*3600*1000L else it } val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver) val places = parsePlaces(jsonObject.getJSONArray("places"), uuidHashResolver) val tags = parseTags(jsonObject.getJSONArray("tags"), uuidHashResolver) val hashtagReminder = jsonObject.getString("hashtag_reminder") val theme = parseLanguageString(jsonObject.getJSONObject("theme"), uuidHashResolver) val registrationTiers = parseRegistrationTiers(jsonObject.getJSONArray("registration_tiers"), uuidHashResolver) val lowestRegistrationTier = uuidHashResolver.registrationTiers[jsonObject.getString("lowest_registration_tier")]!! val language = parseLanguage(jsonObject.getString("language")) val r621checker = jsonObject.getBoolean("rule_621_checker") val maps = parseMaps(jsonObject.getJSONArray("maps"), uuidHashResolver) val imageDefaultEvent = parseLanguageImage(jsonObject.getJSONObject("image_default_event"), uuidHashResolver) val imageFavorites = parseLanguageImage(jsonObject.getJSONObject("image_favorites"), uuidHashResolver) val splitDayIn = jsonObject.getInt("split_day_into_n_parts") val timeZone = parseTimeZone(jsonObject.getJSONObject("timezone")) val nextEdition = parseGregCal(jsonObject.getJSONObject("next_edition")) val officialLifespan = GregorianCalendarRange( parseGregCal(jsonObject.getJSONObject("ceremony_opening_time")), parseGregCal(jsonObject.getJSONObject("ceremony_closing_time")) ) val registrationLinks = parseRegistrationLinks(jsonObject.getJSONArray("registration_links"), uuidHashResolver) val additionalRules = parseAdditionalRulesPlural(jsonObject.getJSONArray("additional_rules"), uuidHashResolver) val events = parseEvents(jsonObject.getJSONArray("events"), uuidHashResolver, language) val eventDates = events.flatMap { Pair(it.timeRange.start, it.timeRange.finish).toList() }.distinctBy { it.timeInMillis }.sortedBy { it.timeInMillis } val startCalendarOn = eventDates.first() val endCalendarOn = eventDates.last() val days = events.map { it.timeRange.start.dayRange.start }.distinctBy { it.timeInMillis }.sortedBy{ it.timeInMillis } return Convention( events, places, registrationTiers, lowestRegistrationTier, tags, uuid, name[language]!!, genericName[language]!!, theme, rules, additionalRules, officialLifespan, startCalendarOn, endCalendarOn, paddingStartOfDayMillis, paddingEndOfDayMillis, days, splitDayIn, timeZone, socialMedias, notificationFireMinutesBefore, r621checker, maps, registrationLinks, imageDefaultEvent, imageFavorites, hashtagReminder, nextEdition ).apply { uuidHashResolver.conventions[uuid] = this } } private fun parseRegistrationLinks(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parseRegistrationLink(it, uuidHashResolver) }.toList() private fun parseRegistrationLink(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): RegistrationLink { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.registrationLinks[uuid] if (previouslyKnown != null) return previouslyKnown val ordering = jsonObject.getInt("ordering") val alias = jsonObject.getString("alias") val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver) val url = parseLanguageString(jsonObject.getJSONObject("url"), uuidHashResolver) val color = parseColorNullable(jsonObject.getJSONObject("color")) val lifespan = GregorianCalendarRange( parseGregCal(jsonObject.getJSONObject("appears")), parseGregCal(jsonObject.getJSONObject("vanishes")) ) return RegistrationLink( alias, ordering, name, url, color, lifespan ).apply { uuidHashResolver.registrationLinks[uuid] = this } } private fun parseColorNullable(jsonObject: JSONObject?): Int? = try { parseColor(jsonObject!!) } catch (t: Throwable) { null } private fun parseMaps(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parseMap(it, uuidHashResolver) }.toList() private fun parseMap(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): MapImage { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.maps[uuid] if (previouslyKnown != null) return previouslyKnown val alias = jsonObject.getString("alias") val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver) val image = parseLanguageImage(jsonObject.getJSONObject("image"), uuidHashResolver)!! return MapImage( alias, name, image ).apply { uuidHashResolver.maps[uuid] = this } } private fun parseAdditionalRulesPlural(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver): List = jsonArray.asSequence().map { parseAdditionalRules(it, uuidHashResolver) }.toList() private fun parseAdditionalRules(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): AddtionalRules { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.additionalRules[uuid] if (previouslyKnown != null) return previouslyKnown val names = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver) val rules = parseLanguageString(jsonObject.getJSONObject("rules"), uuidHashResolver) return AddtionalRules( names, rules ).apply { uuidHashResolver.additionalRules[uuid] = this } } private fun parseEvents(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver, conventionLanguage: Language): List = jsonArray.asSequence().map { parseEvent(it, uuidHashResolver, conventionLanguage) }.toList() private fun parseEvent(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver, conventionLanguage: Language): Event { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.events[uuid] if (previouslyKnown != null) return previouslyKnown val conbookId = jsonObject.getString("conbook_key") val conbookPages = jsonObject.getJSONArray("conbook_pages").toList() val titles = parseLanguageString(jsonObject.getJSONObject("title"), uuidHashResolver) val subtitles = parseLanguageString(jsonObject.getJSONObject("subtitle"), uuidHashResolver) val descriptions = parseLanguageString(jsonObject.getJSONObject("description"), uuidHashResolver) val timeRange = GregorianCalendarRange( parseGregCal(jsonObject.getJSONObject("time_start")), parseGregCal(jsonObject.getJSONObject("time_end")) ) val places = jsonObject.getJSONArray("places").toList().map { uuidHashResolver.places[it]!! } val tags = jsonObject.getJSONArray("categories").toList().map { uuidHashResolver.tags[it]!! } val attendableBy = jsonObject.getJSONArray("attendable_by").toList().map { uuidHashResolver.registrationTiers[it]!! } val language = parseLanguage(jsonObject.getString("language")).let { if(it==conventionLanguage) null else it } val hiddenFromTimeTable = jsonObject.getBoolean("hidden_from_time_table") val assetImage = parseLanguageImage(jsonObject.getJSONObject("picture"), uuidHashResolver) return Event( conbookId, conbookPages, titles, subtitles, descriptions, timeRange, places, attendableBy, tags, language, hiddenFromTimeTable, assetImage ).apply { uuidHashResolver.events[uuid] = this } } private fun parseGregCal(jsonObject: JSONObject): GregorianCalendar = GregorianCalendar( jsonObject.getInt("yr"), jsonObject.getInt("mo").minus(1), jsonObject.getInt("dy"), jsonObject.getInt("hr"), jsonObject.getInt("mn"), jsonObject.getInt("sc") ) private fun parseRegistrationTiers(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parseRegistrationTier(it, uuidHashResolver) }.toList() private fun parseRegistrationTier(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): RegistrationTier { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.registrationTiers[uuid] if (previouslyKnown != null) return previouslyKnown val level = jsonObject.getInt("level") val name = parseLanguageString(jsonObject.getJSONObject("tier"), uuidHashResolver) val benefits = jsonObject.getJSONArray("benefits").asSequence().map { parseLanguageString(it, uuidHashResolver) }.toList() return RegistrationTier( level, name, benefits ).apply { uuidHashResolver.registrationTiers[uuid] = this } } private fun parseTags(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parseTag(it, uuidHashResolver) }.toList() private fun parseTag(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): Tag { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.tags[uuid] if (previouslyKnown != null) return previouslyKnown val label = parseLanguageString(jsonObject.getJSONObject("label"), uuidHashResolver) val color = parseColor(jsonObject.getJSONObject("color")) return Tag(label, color).apply { uuidHashResolver.tags[uuid] = this } } private fun parsePlaces(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parsePlace(it, uuidHashResolver) }.toList() private fun parsePlace(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): Place { val uuid = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.places[uuid] if (previouslyKnown != null) return previouslyKnown val label = parseLanguageString(jsonObject.getJSONObject("label"), uuidHashResolver) val color = parseColor(jsonObject.getJSONObject("color")) return Place(label, color).apply { uuidHashResolver.places[uuid] = this } } private fun parseSocialMedias(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List = jsonArray.asSequence().map { parseSocialMedia(it, uuidHashResolver) }.toList() private fun parseSocialMedia(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): SocialMedia { val uuid = jsonObject.getString("alias") val previouslyKnown = uuidHashResolver.socialMedias[uuid] if (previouslyKnown != null) return previouslyKnown val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver) val url = parseLanguageString(jsonObject.getJSONObject("url"), uuidHashResolver) val color = parseColor(jsonObject.getJSONObject("color")) val icon = Icons.valueOf(jsonObject.getString("icon")).drawableId val sm = SocialMedia( name, url, icon, color ).apply { uuidHashResolver.socialMedias[uuid] = this } return sm } private fun parseColor(jsonObject: JSONObject): Int = jsonObject .getString("color") .toColor() private fun parseTimeZone(jsonObject: JSONObject): TimeZone { val name = jsonObject.getString("name") val hr = jsonObject.getInt("hr") val mn = jsonObject.getInt("mn") val offset = hr * 3600 * 1000 + ( mn * 60 * 1000 * ( if ( hr >= 0 ) 1 else -1 )) return SimpleTimeZone(offset, name) } private fun parseLanguage(string: String): Language { return Language.values().firstOrNull { it.locale.language == string } ?: Language.EN } private fun parseLanguageImage(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): Map? = parseLanguageString(jsonObject, uuidHashResolver, true) .filterNot { it.value.isBlank() }.let { if (it.isEmpty()) null else it } private fun parseLanguageString(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver, isPath: Boolean = false): Map { val uuid: String? = jsonObject.getString("uuid") val previouslyKnown = uuidHashResolver.languageStrings[uuid] if (previouslyKnown != null) return previouslyKnown val langString = Language.values().map { it to (if(jsonObject.has(it.locale.language)) jsonObject.getString(it.locale.language) else "").let { if(isPath) it.trimStart('/').trim() else it } }.toMap() uuid?.let { uuidHashResolver.languageStrings[it] = langString } return langString } }