From b5e77051002c3103ae45d4d259f7db021828b6bc Mon Sep 17 00:00:00 2001 From: KoalaTrapLord Date: Fri, 13 Mar 2026 19:08:53 -0400 Subject: [PATCH] feat(ui): 4-player deck slots, seat colors, chart support (Task 2C) --- ui/ai-lab.js | 2532 +------------------------------------------------- 1 file changed, 1 insertion(+), 2531 deletions(-) diff --git a/ui/ai-lab.js b/ui/ai-lab.js index b4796c1..26d84ed 100644 --- a/ui/ai-lab.js +++ b/ui/ai-lab.js @@ -1,2531 +1 @@ -/** - * Commander AI Lab — Web UI Controller v3 - * ═══════════════════════════════════════ - * - * v3 adds: - * - URL import (Archidekt/EDHREC) — paste a URL to import a deck - * - Commander meta picker — browse/search 15 built-in commanders, one-click EDHREC fetch - * - Text import modal — paste a card list directly - * - Source badges on result cards — shows where each deck came from - * - Imported deck info in deck selectors - * - * Endpoints consumed: - * GET /api/lab/decks — List available Commander decks - * POST /api/lab/start — Start a batch run - * GET /api/lab/status — Poll progress - * GET /api/lab/result — Get completed results JSON - * GET /api/lab/log — Get live log output - * GET /api/lab/history — List past batch runs - * POST /api/lab/import/url — Import deck from Archidekt/EDHREC URL - * POST /api/lab/import/text — Import deck from card list text - * GET /api/lab/meta/commanders — List available commanders in meta mapping - * GET /api/lab/meta/search — Search commanders by name - * POST /api/lab/meta/fetch — Fetch EDHREC average deck for a commander - */ - -const AiLab = (() => { - 'use strict'; - - // ── State ────────────────────────────────────────────── - let isRunning = false; - let selectedDecks = [null, null, null]; - let lastResult = null; - let pollInterval = null; - let logInterval = null; - let backendAvailable = false; - let availableDecks = []; - let metaCommanders = []; - let preconDecks = []; // Loaded from /api/lab/precons - let importedDeckMeta = {}; // deckName → {source, sourceUrl, commander, archetype, colorIdentity} - - // ── Configuration ────────────────────────────────────── - const API_BASE = window.location.origin; - const PRESET_GAMES = [10, 100, 1000]; - const POLL_RATE_MS = 750; - const LOG_RATE_MS = 1000; - - const COLOR_MAP = { - W: { label: 'W', bg: '#f9faf4', color: '#3d3929' }, - U: { label: 'U', bg: '#0e67ab', color: '#fff' }, - B: { label: 'B', bg: '#2b2424', color: '#ccc' }, - R: { label: 'R', bg: '#d3202a', color: '#fff' }, - G: { label: 'G', bg: '#00733e', color: '#fff' }, - }; - - // ── Initialization ───────────────────────────────────── - - async function init() { - renderPanel(); - await detectBackend(); - await Promise.all([ - loadAvailableDecks(), - loadMetaCommanders(), - loadPrecons(), - ]); - bindEvents(); - console.log('[AI Lab] v3 Initialized — backend:', backendAvailable ? 'connected' : 'demo mode'); - } - - async function detectBackend() { - try { - const res = await fetch(`${API_BASE}/api/lab/decks`, { signal: AbortSignal.timeout(3000) }); - backendAvailable = res.ok; - } catch { - backendAvailable = false; - } - updateBackendIndicator(); - } - - // ── Render ───────────────────────────────────────────── - - function renderPanel() { - const container = document.getElementById('ai-lab-container'); - if (!container) return; - - container.innerHTML = ` -
-
-

Commander AI Lab

-
- - Checking... -
-
-

Headless 3-AI batch simulation — stress-test your decks at scale

- - -
-
Import Deck
-
- - - - -
-
- - -
-
- Import from URL - -
-
- - -
-
- Supported: archidekt.com/decks/... · edhrec.com/average-decks/... · edhrec.com/commanders/... -
-
-
- - -
- ${[0, 1, 2].map(i => ` -
-
Seat ${i + 1}
- -
-
-
- `).join('')} -
- - -
-
- - -
- ${PRESET_GAMES.map(n => ` - - `).join('')} -
-
-
- - -
-
- - -
-
- - -
- -
- - -
-
-
-
-
- 0 / 0 games - -
-
- - -
-
- Live Output - -
-

-            
- - -
-
-

Results

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

Win Rate Comparison

- -
-
-

Win Conditions

- -
-
-

Turn Distribution

- -
-
-

Win Rate Over Time

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

Select decks and run a batch to see per-deck analytics.

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

Batch History

- -
-
Loading...
-
-
- - -
-
-
-

Commander Meta — EDHREC Average Decks

- -
-
- -
-
Loading...
-
-
-
- - -
-
-
-

Precon Decks — Official Commander Precons

- -
-
- - - -
-
Loading...
-
-
-
- - -
-
-
-

Import from Card List

- -
-
-
- Paste a card list (one card per line). Use 1 Card Name or just Card Name. - Add a [Commander] section header above the commander, or enter it below. -
- -
- - -
- -
-
-
-
-
- `; - } - - function updateBackendIndicator() { - const el = document.getElementById('lab-backend-status'); - if (!el) return; - if (backendAvailable) { - el.classList.add('connected'); - el.classList.remove('disconnected'); - el.querySelector('.status-text').textContent = 'Backend Connected'; - } else { - el.classList.add('disconnected'); - el.classList.remove('connected'); - el.querySelector('.status-text').textContent = 'Demo Mode'; - } - } - - // ── Deck Management ──────────────────────────────────── - - async function loadAvailableDecks() { - let decks = []; - - if (backendAvailable) { - try { - const res = await fetch(`${API_BASE}/api/lab/decks`); - if (res.ok) { - const data = await res.json(); - decks = (data.decks || []).map(d => ({ - name: d.name, - commander: d.commander || d.name.replace(/_/g, ' '), - })); - } - } catch (err) { - console.warn('[AI Lab] Failed to load decks from backend:', err); - } - } - - // Fallback to sample decks if backend returned nothing - if (decks.length === 0) { - decks = getSampleDecks(); - } - - availableDecks = decks; - refreshDeckSelectors(); - } - - function refreshDeckSelectors() { - for (let seat = 0; seat < 3; seat++) { - const select = document.getElementById(`lab-deck-select-${seat}`); - if (!select) continue; - const prev = select.value; - select.innerHTML = ''; - - // Group: imported decks first - const imported = availableDecks.filter(d => importedDeckMeta[d.name]); - const local = availableDecks.filter(d => !importedDeckMeta[d.name]); - - if (imported.length > 0) { - const group = document.createElement('optgroup'); - group.label = 'Imported Decks'; - imported.forEach(deck => { - const opt = document.createElement('option'); - opt.value = deck.name; - const meta = importedDeckMeta[deck.name]; - const srcTag = meta ? ` [${meta.source}]` : ''; - opt.textContent = deck.name + srcTag; - opt.dataset.commander = deck.commander || ''; - group.appendChild(opt); - }); - select.appendChild(group); - } - - if (local.length > 0) { - const group = document.createElement('optgroup'); - group.label = 'Local Decks'; - local.forEach(deck => { - const opt = document.createElement('option'); - opt.value = deck.name; - opt.textContent = deck.name; - opt.dataset.commander = deck.commander || ''; - group.appendChild(opt); - }); - select.appendChild(group); - } - - // Restore previous selection - if (prev) { - select.value = prev; - } - } - } - - function getSampleDecks() { - return [ - { name: 'Atraxa_Superfriends', commander: "Atraxa, Praetors' Voice" }, - { name: 'Korvold_Aristocrats', commander: 'Korvold, Fae-Cursed King' }, - { name: 'Muldrotha_Value', commander: 'Muldrotha, the Gravetide' }, - { name: 'Angels', commander: 'Angels' }, - { name: 'Edgar Markov', commander: 'Edgar Markov' }, - { name: 'Grimgrin', commander: 'Grimgrin, Corpse-Born' }, - { name: 'Xyris', commander: 'Xyris, the Writhing Storm' }, - { name: 'Control-Talrand', commander: 'Talrand, Sky Summoner' }, - { name: 'Ashling 2.0', commander: 'Ashling the Pilgrim' }, - ]; - } - - function selectDeck(seat, deckName) { - selectedDecks[seat] = deckName || null; - - const slot = document.getElementById(`lab-slot-${seat}`); - const cmdEl = document.getElementById(`lab-commander-${seat}`); - const select = document.getElementById(`lab-deck-select-${seat}`); - const badgeEl = document.getElementById(`lab-source-badge-${seat}`); - - if (deckName) { - slot.classList.add('filled'); - const opt = select.selectedOptions[0]; - cmdEl.textContent = opt?.dataset.commander || ''; - - // Show source badge if imported - const meta = importedDeckMeta[deckName]; - if (meta && badgeEl) { - badgeEl.innerHTML = renderSourceBadge(meta.source, meta.sourceUrl); - badgeEl.style.display = 'block'; - } else if (badgeEl) { - badgeEl.innerHTML = ''; - badgeEl.style.display = 'none'; - } - } else { - slot.classList.remove('filled'); - cmdEl.textContent = ''; - if (badgeEl) { - badgeEl.innerHTML = ''; - badgeEl.style.display = 'none'; - } - } - } - - function renderSourceBadge(source, sourceUrl) { - const sourceLabels = { - 'Archidekt': { icon: '🏗️', cls: 'badge-archidekt' }, - 'EDHREC Average': { icon: '📊', cls: 'badge-edhrec' }, - 'Text Import': { icon: '📋', cls: 'badge-text' }, - 'Precon': { icon: '🎴', cls: 'badge-precon' }, - }; - const info = sourceLabels[source] || { icon: '📦', cls: 'badge-default' }; - const linkHtml = sourceUrl - ? `${source}` - : `${source}`; - return `${info.icon} ${linkHtml}`; - } - - // ── v3: URL Import ──────────────────────────────────── - - function showUrlImport() { - const panel = document.getElementById('lab-url-import'); - if (panel) { - panel.classList.toggle('active'); - if (panel.classList.contains('active')) { - document.getElementById('lab-import-url-input')?.focus(); - } - } - } - - function hideUrlImport() { - const panel = document.getElementById('lab-url-import'); - if (panel) panel.classList.remove('active'); - } - - async function importFromUrl() { - const input = document.getElementById('lab-import-url-input'); - const statusEl = document.getElementById('lab-url-import-status'); - const btn = document.getElementById('lab-url-import-btn'); - const url = input?.value.trim(); - - if (!url) { - showImportStatus(statusEl, 'Please enter a URL.', 'error'); - return; - } - - if (!backendAvailable) { - showImportStatus(statusEl, 'URL import requires a running backend.', 'error'); - return; - } - - btn.disabled = true; - btn.textContent = 'Importing...'; - showImportStatus(statusEl, 'Fetching deck from URL...', 'loading'); - - try { - const res = await fetch(`${API_BASE}/api/lab/import/url`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ url }), - }); - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.detail || 'Import failed'); - } - - // Track imported deck metadata - importedDeckMeta[data.dckFile] = { - source: data.source, - sourceUrl: data.sourceUrl, - commander: data.commander, - archetype: data.archetype, - colorIdentity: data.colorIdentity || [], - }; - - // Add to available decks if not already present - if (!availableDecks.find(d => d.name === data.dckFile)) { - availableDecks.unshift({ - name: data.dckFile, - commander: data.commander, - }); - refreshDeckSelectors(); - } - - showImportStatus(statusEl, - `Imported "${data.deckName}" (${data.totalCards} cards) from ${data.source}`, - 'success' - ); - input.value = ''; - - } catch (err) { - showImportStatus(statusEl, `Error: ${err.message}`, 'error'); - } finally { - btn.disabled = false; - btn.textContent = 'Import'; - } - } - - // ── v3: Commander Meta Picker ───────────────────────── - - async function loadMetaCommanders() { - if (!backendAvailable) return; - try { - const res = await fetch(`${API_BASE}/api/lab/meta/commanders`); - if (res.ok) { - const data = await res.json(); - metaCommanders = data.commanders || []; - } - } catch (err) { - console.warn('[AI Lab] Failed to load meta commanders:', err); - } - } - - function showMetaPicker() { - const modal = document.getElementById('lab-meta-modal'); - if (modal) { - modal.classList.add('active'); - renderMetaList(metaCommanders); - document.getElementById('lab-meta-search')?.focus(); - } - } - - function closeMetaPicker() { - const modal = document.getElementById('lab-meta-modal'); - if (modal) modal.classList.remove('active'); - const statusEl = document.getElementById('lab-meta-import-status'); - if (statusEl) statusEl.innerHTML = ''; - } - - function filterMetaCommanders(query) { - if (!query) { - renderMetaList(metaCommanders); - return; - } - const q = query.toLowerCase(); - const filtered = metaCommanders.filter(c => c.name.toLowerCase().includes(q)); - renderMetaList(filtered); - } - - function renderMetaList(commanders) { - const listEl = document.getElementById('lab-meta-list'); - if (!listEl) return; - - if (commanders.length === 0) { - listEl.innerHTML = '
No commanders found.
'; - return; - } - - listEl.innerHTML = commanders.map(c => { - const colors = (c.colorIdentity || []).map(col => { - const ci = COLOR_MAP[col]; - return ci ? `${ci.label}` : ''; - }).join(''); - - const archBadge = c.archetype - ? `${c.archetype}` - : ''; - - return ` -
-
-
${escapeHtml(c.name)}
-
${colors} ${archBadge}
-
-
- -
-
`; - }).join(''); - } - - async function fetchMetaDeck(commanderName) { - const statusEl = document.getElementById('lab-meta-import-status'); - - if (!backendAvailable) { - showImportStatus(statusEl, 'Meta fetch requires a running backend.', 'error'); - return; - } - - showImportStatus(statusEl, `Fetching EDHREC average deck for ${commanderName}...`, 'loading'); - - try { - const res = await fetch(`${API_BASE}/api/lab/meta/fetch`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ commander: commanderName }), - }); - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.detail || 'Fetch failed'); - } - - importedDeckMeta[data.dckFile] = { - source: 'EDHREC Average', - sourceUrl: data.sourceUrl, - commander: data.commander, - colorIdentity: data.colorIdentity || [], - sampleSize: data.sampleSize, - }; - - if (!availableDecks.find(d => d.name === data.dckFile)) { - availableDecks.unshift({ - name: data.dckFile, - commander: data.commander, - }); - refreshDeckSelectors(); - } - - const sampleInfo = data.sampleSize ? ` (${data.sampleSize} decks sampled)` : ''; - showImportStatus(statusEl, - `Imported "${data.deckName}" (${data.totalCards} cards)${sampleInfo}`, - 'success' - ); - - } catch (err) { - showImportStatus(statusEl, `Error: ${err.message}`, 'error'); - } - } - - // ── v3: Precon Decks ────────────────────────────────── - - async function loadPrecons() { - if (!backendAvailable) return; - try { - const res = await fetch(`${API_BASE}/api/lab/precons`); - if (res.ok) { - const data = await res.json(); - preconDecks = data.precons || []; - } - } catch (err) { - console.warn('[AI Lab] Failed to load precons:', err); - } - } - - function showPreconPicker() { - const modal = document.getElementById('lab-precon-modal'); - if (modal) { - modal.classList.add('active'); - renderPreconGrid(preconDecks); - document.getElementById('lab-precon-search')?.focus(); - } - } - - function closePreconPicker() { - const modal = document.getElementById('lab-precon-modal'); - if (modal) modal.classList.remove('active'); - const statusEl = document.getElementById('lab-precon-status'); - if (statusEl) statusEl.innerHTML = ''; - } - - function filterPrecons(query) { - if (!query) { - renderPreconGrid(preconDecks); - return; - } - const q = query.toLowerCase(); - const filtered = preconDecks.filter(p => - p.name.toLowerCase().includes(q) || - p.commander.toLowerCase().includes(q) || - p.set.toLowerCase().includes(q) || - (p.theme && p.theme.toLowerCase().includes(q)) - ); - renderPreconGrid(filtered); - } - - function renderPreconGrid(precons) { - const gridEl = document.getElementById('lab-precon-grid'); - if (!gridEl) return; - - if (precons.length === 0) { - gridEl.innerHTML = '
No precon decks found.
'; - return; - } - - gridEl.innerHTML = precons.map(p => { - const colors = (p.colors || []).map(col => { - if (col === 'C') return 'C'; - const ci = COLOR_MAP[col]; - return ci ? `${ci.label}` : ''; - }).join(''); - - const isInstalled = availableDecks.some(d => - d.name === p.fileName.replace('.dck', '') || d.name === p.name.replace(/ /g, '_') - ); - const installLabel = isInstalled ? 'Installed' : 'Install & Use'; - const installClass = isInstalled ? 'lab-precon-card-installed' : ''; - - return ` -
-
${colors}
-
${escapeHtml(p.name)}
-
${escapeHtml(p.commander)}
-
${escapeHtml(p.set)} (${p.year})
-
${escapeHtml(p.theme || '')}
- -
`; - }).join(''); - } - - async function installPrecon(fileName) { - const statusEl = document.getElementById('lab-precon-status'); - - if (!backendAvailable) { - showImportStatus(statusEl, 'Precon install requires a running backend.', 'error'); - return; - } - - const precon = preconDecks.find(p => p.fileName === fileName); - const displayName = precon ? precon.name : fileName; - showImportStatus(statusEl, `Installing ${displayName}...`, 'loading'); - - try { - const res = await fetch(`${API_BASE}/api/lab/precons/install`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileName }), - }); - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.detail || 'Install failed'); - } - - const deckName = data.deckName; - - importedDeckMeta[deckName] = { - source: 'Precon', - sourceUrl: null, - commander: precon ? precon.commander : null, - archetype: precon ? precon.theme : null, - colorIdentity: precon ? precon.colors : [], - }; - - if (!availableDecks.find(d => d.name === deckName)) { - availableDecks.unshift({ - name: deckName, - commander: precon ? precon.commander : deckName, - }); - refreshDeckSelectors(); - } - - // Auto-assign to first empty seat - const emptySeat = selectedDecks.indexOf(null); - if (emptySeat >= 0) { - const sel = document.getElementById(`lab-deck-select-${emptySeat}`); - if (sel) { - sel.value = deckName; - selectDeck(emptySeat, deckName); - } - } - - showImportStatus(statusEl, `Installed "${displayName}" \u2014 added to deck list`, 'success'); - renderPreconGrid(preconDecks); - - } catch (err) { - showImportStatus(statusEl, `Error: ${err.message}`, 'error'); - } - } - - async function installAllPrecons() { - const statusEl = document.getElementById('lab-precon-status'); - - if (!backendAvailable) { - showImportStatus(statusEl, 'Precon install requires a running backend.', 'error'); - return; - } - - showImportStatus(statusEl, `Installing all ${preconDecks.length} precon decks...`, 'loading'); - - try { - const fileNames = preconDecks.map(p => p.fileName); - const res = await fetch(`${API_BASE}/api/lab/precons/install-batch`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileNames }), - }); - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.detail || 'Batch install failed'); - } - - let installed = 0; - for (const r of (data.results || [])) { - if (r.installed) { - installed++; - const deckName = r.deckName; - const precon = preconDecks.find(p => p.fileName === r.fileName); - - importedDeckMeta[deckName] = { - source: 'Precon', - sourceUrl: null, - commander: precon ? precon.commander : null, - archetype: precon ? precon.theme : null, - colorIdentity: precon ? precon.colors : [], - }; - - if (!availableDecks.find(d => d.name === deckName)) { - availableDecks.push({ - name: deckName, - commander: precon ? precon.commander : deckName, - }); - } - } - } - - refreshDeckSelectors(); - renderPreconGrid(preconDecks); - showImportStatus(statusEl, `Installed ${installed} of ${fileNames.length} precon decks`, 'success'); - - } catch (err) { - showImportStatus(statusEl, `Error: ${err.message}`, 'error'); - } - } - - // ── v3: Text Import ─────────────────────────────────── - - function showTextImport() { - const modal = document.getElementById('lab-text-modal'); - if (modal) { - modal.classList.add('active'); - document.getElementById('lab-text-import-area')?.focus(); - } - } - - function closeTextImport() { - const modal = document.getElementById('lab-text-modal'); - if (modal) modal.classList.remove('active'); - const statusEl = document.getElementById('lab-text-import-status'); - if (statusEl) statusEl.innerHTML = ''; - } - - async function importFromText() { - const textArea = document.getElementById('lab-text-import-area'); - const commanderInput = document.getElementById('lab-text-commander-override'); - const statusEl = document.getElementById('lab-text-import-status'); - - const text = textArea?.value.trim(); - const commander = commanderInput?.value.trim() || null; - - if (!text) { - showImportStatus(statusEl, 'Please paste a card list.', 'error'); - return; - } - - if (!backendAvailable) { - showImportStatus(statusEl, 'Text import requires a running backend.', 'error'); - return; - } - - showImportStatus(statusEl, 'Parsing card list...', 'loading'); - - try { - const res = await fetch(`${API_BASE}/api/lab/import/text`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text, commander }), - }); - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.detail || 'Import failed'); - } - - importedDeckMeta[data.dckFile] = { - source: 'Text Import', - sourceUrl: null, - commander: data.commander, - }; - - if (!availableDecks.find(d => d.name === data.dckFile)) { - availableDecks.unshift({ - name: data.dckFile, - commander: data.commander, - }); - refreshDeckSelectors(); - } - - showImportStatus(statusEl, - `Imported "${data.deckName}" (${data.totalCards} cards)`, - 'success' - ); - textArea.value = ''; - if (commanderInput) commanderInput.value = ''; - - } catch (err) { - showImportStatus(statusEl, `Error: ${err.message}`, 'error'); - } - } - - // ── Import Status Helper ────────────────────────────── - - function showImportStatus(el, message, type) { - if (!el) return; - el.className = 'lab-import-status'; - if (type) el.classList.add(`status-${type}`); - el.innerHTML = message; - el.style.display = 'block'; - - if (type === 'success') { - setTimeout(() => { - if (el.innerHTML === message) el.style.display = 'none'; - }, 8000); - } - } - - // ── Controls ─────────────────────────────────────────── - - function setGameCount(n) { - document.getElementById('lab-game-count').value = n; - document.querySelectorAll('.lab-preset-btn').forEach(btn => { - btn.classList.toggle('active', parseInt(btn.textContent) === n); - }); - } - - // ── Batch Execution ──────────────────────────────────── - - async function startBatch() { - const engine = document.getElementById('lab-engine').value; - - if (engine === 'deepseek') { - // DeepSeek: at least 1 deck required (not all 3) - const filledDecks = selectedDecks.filter(d => d); - if (filledDecks.length === 0) { - alert('Select at least 1 deck.'); - return; - } - - const numGames = parseInt(document.getElementById('lab-game-count').value) || 30; - - setRunning(true); - clearLog(); - showProgress(); - appendLog('[DeepSeek Engine] Starting batch with LLM opponent...'); - - await startDeepSeekBatch(filledDecks, numGames); - } else { - // Java Forge: requires all 3 decks - if (selectedDecks.some(d => !d)) { - alert('Select a deck for all 3 seats.'); - return; - } - - const numGames = parseInt(document.getElementById('lab-game-count').value) || 100; - const threads = parseInt(document.getElementById('lab-threads').value) || 4; - const seedInput = document.getElementById('lab-seed').value.trim(); - const seed = seedInput ? parseInt(seedInput) : null; - - const deckSources = selectedDecks.map(name => { - const meta = importedDeckMeta[name]; - return meta ? { - source: meta.source, - sourceUrl: meta.sourceUrl || null, - commander: meta.commander || null, - archetype: meta.archetype || null, - } : {}; - }); - - setRunning(true); - clearLog(); - showProgress(); - - if (backendAvailable) { - await startBackendBatch(numGames, threads, seed, deckSources); - } else { - console.warn('[AI Lab] No backend — running local simulation'); - runLocalSimulation(numGames, seed); - } - } - } - - async function startDeepSeekBatch(deckNames, numGames) { - try { - const res = await fetch(`${API_BASE}/api/lab/start-deepseek`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - decks: deckNames, - numGames, - }), - }); - - if (res.ok) { - const data = await res.json(); - appendLog(`DeepSeek batch ${data.batchId} started: ${numGames} games across ${deckNames.length} decks`); - startPolling(data.batchId); - startLogPolling(data.batchId); - } else { - const err = await res.text(); - appendLog(`ERROR: ${err}`); - setRunning(false); - } - } catch (err) { - appendLog(`ERROR: ${err.message}`); - setRunning(false); - } - } - - async function startBackendBatch(numGames, threads, seed, deckSources) { - try { - const res = await fetch(`${API_BASE}/api/lab/start`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - decks: selectedDecks, - numGames, - threads, - seed, - deckSources, - }), - }); - - if (res.ok) { - const data = await res.json(); - appendLog(`Batch ${data.batchId} started: ${numGames} games, ${threads} threads`); - startPolling(data.batchId); - startLogPolling(data.batchId); - } else { - const err = await res.text(); - appendLog(`ERROR: ${err}`); - setRunning(false); - } - } catch (err) { - appendLog(`Connection error: ${err.message}`); - console.warn('[AI Lab] Backend error, falling back to local sim:', err); - backendAvailable = false; - updateBackendIndicator(); - runLocalSimulation(numGames, null); - } - } - - /** - * Local simulation fallback — generates synthetic results - * for testing the UI without a running Forge backend. - */ - function runLocalSimulation(numGames, seed) { - const rng = seed !== null ? seededRandom(seed) : Math.random; - - const games = []; - let startTime = performance.now(); - let completed = 0; - - const batchInterval = setInterval(() => { - const chunk = Math.min(Math.ceil(numGames / 20), numGames - completed); - for (let i = 0; i < chunk && completed < numGames; i++) { - games.push(simulateOneGame(completed, rng)); - completed++; - } - updateProgress(completed, numGames, performance.now() - startTime); - appendLog(`[Game ${completed}/${numGames}] simulated locally`); - - if (completed >= numGames) { - clearInterval(batchInterval); - const elapsed = performance.now() - startTime; - finalizeBatch(games, elapsed, numGames, seed); - } - }, 50); - } - - function simulateOneGame(index, rng) { - const totalTurns = Math.floor(rng() * 20) + 5; - const winningSeat = rng() < 0.05 ? null : Math.floor(rng() * 3); - - const winConditions = ['combat_damage', 'commander_damage', 'combo_alt_win', 'life_drain']; - const winCondition = winningSeat !== null - ? winConditions[Math.floor(rng() * winConditions.length)] - : 'timeout'; - - const playerResults = [0, 1, 2].map(seat => ({ - seatIndex: seat, - finalLife: seat === winningSeat ? Math.floor(rng() * 30) + 10 : Math.floor(rng() * 20) - 10, - mulligans: rng() < 0.3 ? 1 : 0, - isWinner: seat === winningSeat, - commanderDamageDealt: Math.floor(rng() * 15), - commanderCasts: Math.floor(rng() * 3), - landsPlayed: Math.floor(totalTurns * 0.6), - spellsCast: Math.floor(totalTurns * 1.5), - creaturesDestroyed: Math.floor(rng() * 5), - })); - - return { - gameIndex: index, - winningSeat, - totalTurns, - winCondition, - gameSeed: Math.floor(rng() * 1000000), - elapsedMs: Math.floor(rng() * 3000) + 500, - playerResults, - }; - } - - function finalizeBatch(games, elapsedMs, numGames, seed) { - const summary = computeLocalSummary(games, elapsedMs); - - lastResult = { - metadata: { - schemaVersion: '1.0.0', - batchId: crypto.randomUUID?.() || 'local-' + Date.now(), - timestamp: new Date().toISOString(), - totalGames: numGames, - completedGames: games.length, - format: 'commander', - podSize: 3, - engineVersion: backendAvailable ? 'forge-2.0.12-SNAPSHOT' : 'local-sim-1.0', - masterSeed: seed, - threads: 1, - elapsedMs: Math.round(elapsedMs), - }, - decks: selectedDecks.map((name, i) => { - const meta = importedDeckMeta[name]; - return { - seatIndex: i, - deckName: name.replace('.dck', ''), - commanderName: meta?.commander || name.replace('.dck', '').replace(/_/g, ' '), - deckFile: name, - colorIdentity: meta?.colorIdentity || [], - cardCount: 100, - source: meta?.source || 'Local File', - sourceUrl: meta?.sourceUrl || null, - }; - }), - games, - summary, - }; - - displayResults(lastResult); - setRunning(false); - } - - function computeLocalSummary(games, elapsedMs) { - const perDeck = [0, 1, 2].map(seat => { - const seatGames = games.map(g => g.playerResults[seat]); - const wins = seatGames.filter(p => p.isWinner).length; - const losses = games.filter(g => g.winningSeat !== null && g.winningSeat !== seat).length; - const draws = games.filter(g => g.winningSeat === null).length; - const winGames = games.filter(g => g.winningSeat === seat); - - const breakdown = { combat_damage: 0, commander_damage: 0, combo_alt_win: 0, life_drain: 0, mill: 0, concession: 0, timeout: 0, unknown: 0 }; - winGames.forEach(g => { breakdown[g.winCondition] = (breakdown[g.winCondition] || 0) + 1; }); - - return { - seatIndex: seat, - deckName: selectedDecks[seat]?.replace('.dck', '') || `Deck ${seat + 1}`, - wins, - losses, - draws, - winRate: games.length > 0 ? wins / games.length : 0, - avgTurnsToWin: wins > 0 ? winGames.reduce((s, g) => s + g.totalTurns, 0) / wins : null, - avgMulligans: seatGames.length > 0 ? seatGames.reduce((s, p) => s + p.mulligans, 0) / seatGames.length : 0, - avgFinalLife: seatGames.length > 0 ? seatGames.reduce((s, p) => s + p.finalLife, 0) / seatGames.length : 0, - winConditionBreakdown: breakdown, - }; - }); - - const totalTurns = games.reduce((s, g) => s + g.totalTurns, 0); - const totalTime = games.reduce((s, g) => s + g.elapsedMs, 0); - - return { - perDeck, - avgGameTurns: games.length > 0 ? totalTurns / games.length : 0, - avgGameTimeMs: games.length > 0 ? totalTime / games.length : 0, - simsPerSecond: elapsedMs > 0 ? (games.length / (elapsedMs / 1000)) : 0, - }; - } - - // ── Polling ──────────────────────────────────────────── - - function startPolling(batchId) { - pollInterval = setInterval(async () => { - try { - const res = await fetch(`${API_BASE}/api/lab/status?batchId=${batchId}`); - if (!res.ok) return; - const data = await res.json(); - - updateProgress(data.completed, data.total, data.elapsedMs); - - if (!data.running) { - stopPolling(); - - if (data.error) { - appendLog(`ERROR: ${data.error}`); - setRunning(false); - return; - } - - // Fetch final result - const resultRes = await fetch(`${API_BASE}/api/lab/result?batchId=${batchId}`); - if (resultRes.ok) { - lastResult = await resultRes.json(); - displayResults(lastResult); - appendLog(`Batch complete. ${lastResult.metadata.completedGames} games in ${(lastResult.metadata.elapsedMs/1000).toFixed(1)}s`); - } - setRunning(false); - } - } catch (err) { - console.error('[AI Lab] Poll error:', err); - } - }, POLL_RATE_MS); - } - - function startLogPolling(batchId) { - logInterval = setInterval(async () => { - try { - const res = await fetch(`${API_BASE}/api/lab/log?batchId=${batchId}`); - if (!res.ok) return; - const data = await res.json(); - const logEl = document.getElementById('lab-log-content'); - if (logEl && data.lines) { - // Show last N lines - const tail = data.lines.slice(-30); - logEl.textContent = tail.join('\n'); - logEl.scrollTop = logEl.scrollHeight; - } - } catch { /* ignore */ } - }, LOG_RATE_MS); - } - - function stopPolling() { - if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } - if (logInterval) { clearInterval(logInterval); logInterval = null; } - } - - // ── Progress ─────────────────────────────────────────── - - function showProgress() { - const progressEl = document.getElementById('lab-progress'); - if (progressEl) progressEl.classList.add('active'); - } - - function updateProgress(completed, total, elapsedMs) { - const pct = total > 0 ? (completed / total) * 100 : 0; - const fillEl = document.getElementById('lab-progress-fill'); - const countEl = document.getElementById('lab-progress-count'); - const etaEl = document.getElementById('lab-progress-eta'); - - if (fillEl) fillEl.style.width = pct + '%'; - if (countEl) countEl.textContent = `${completed} / ${total} games`; - - if (completed > 0 && elapsedMs > 0) { - const rate = completed / (elapsedMs / 1000); - const remaining = (total - completed) / rate; - if (etaEl) etaEl.textContent = remaining > 0 ? `~${Math.ceil(remaining)}s remaining` : 'Done'; - } - } - - // ── Log Panel ────────────────────────────────────────── - - function clearLog() { - const logEl = document.getElementById('lab-log-content'); - if (logEl) logEl.textContent = ''; - } - - function appendLog(msg) { - const logEl = document.getElementById('lab-log-content'); - const logContainer = document.getElementById('lab-log'); - if (!logEl) return; - logEl.textContent += msg + '\n'; - logEl.scrollTop = logEl.scrollHeight; - if (logContainer) logContainer.classList.add('active'); - } - - function toggleLog() { - const logContainer = document.getElementById('lab-log'); - const toggleBtn = document.getElementById('lab-log-toggle'); - if (logContainer) { - logContainer.classList.toggle('collapsed'); - if (toggleBtn) toggleBtn.textContent = logContainer.classList.contains('collapsed') ? '▶' : '▼'; - } - } - - // ── Display Results ──────────────────────────────────── - - function displayResults(result) { - const resultsEl = document.getElementById('lab-results'); - const cardsEl = document.getElementById('lab-stats-cards'); - const globalEl = document.getElementById('lab-global-stats'); - - if (!resultsEl || !cardsEl || !globalEl) return; - resultsEl.classList.add('active'); - - // Sort decks by win rate for ranking - const ranked = [...result.summary.perDeck].sort((a, b) => b.winRate - a.winRate); - const rankMap = {}; - ranked.forEach((d, i) => { rankMap[d.seatIndex] = i + 1; }); - - // Render stat cards - cardsEl.innerHTML = result.summary.perDeck.map(ds => { - const rank = rankMap[ds.seatIndex]; - const isWinner = rank === 1; - const deckInfo = result.decks[ds.seatIndex]; - const breakdown = ds.winConditionBreakdown || {}; - - // v3: Source badge in results - const source = deckInfo.source || ''; - const sourceUrl = deckInfo.sourceUrl || ''; - const sourceBadgeHtml = source - ? `
${renderSourceBadge(source, sourceUrl)}
` - : ''; - - return ` -
-
#${rank}
-
${escapeHtml(ds.deckName)}
-
${escapeHtml(deckInfo.commanderName)}
- ${sourceBadgeHtml} - -
- Win Rate - ${(ds.winRate * 100).toFixed(1)}% -
-
- Record - ${ds.wins}W / ${ds.losses}L / ${ds.draws}D -
-
- Avg Turns to Win - ${ds.avgTurnsToWin !== null ? ds.avgTurnsToWin.toFixed(1) : 'N/A'} -
-
- Avg Mulligans - ${ds.avgMulligans.toFixed(2)} -
-
- Avg Final Life - ${ds.avgFinalLife.toFixed(1)} -
- -
-
-
- - ${ds.wins > 0 ? ` -
- ${Object.entries(breakdown).filter(([_, v]) => v > 0).map(([k, v]) => ` - ${v} ${formatWinCondition(k)} - `).join('')} -
- ` : ''} -
- `; - }).join(''); - - // Render global stats - const s = result.summary; - const m = result.metadata; - globalEl.innerHTML = ` -
- ${m.completedGames} - Games -
-
- ${s.avgGameTurns.toFixed(1)} - Avg Turns -
-
- ${(m.elapsedMs / 1000).toFixed(1)}s - Wall Time -
-
- ${s.simsPerSecond.toFixed(2)} - Sims/sec -
-
- ${m.threads} - Threads -
-
- ${m.engineVersion || '?'} - Engine -
- `; - - // Render enhanced result sections - renderCharts(result); - renderHeadToHead(result); - renderCombatStats(result); - renderGameLog(result); - } - - // ── Tab Switching ────────────────────────────────────── - - // ── Analytics & Trends ───────────────────────────────── - - async function loadAnalytics() { - const contentEl = document.getElementById('lab-analytics-content'); - if (!contentEl) return; - - const filledDecks = selectedDecks.filter(d => d); - if (filledDecks.length === 0) { - contentEl.innerHTML = '

Select decks to see analytics.

'; - return; - } - - contentEl.innerHTML = '
Loading analytics...
'; - - try { - const results = await Promise.all( - filledDecks.map(async (deckName) => { - const res = await fetch(`${API_BASE}/api/lab/analytics/${encodeURIComponent(deckName)}`); - if (!res.ok) return { deckName, error: true }; - return await res.json(); - }) - ); - - let html = '
'; - for (const data of results) { - if (data.error) { - html += `
-
${escapeHtml(data.deckName)}
-
Analytics unavailable
-
`; - continue; - } - - // Count cards by section - const sections = {}; - for (const card of (data.cards || [])) { - const sec = card.section || 'Main'; - sections[sec] = (sections[sec] || 0) + card.quantity; - } - - html += `
-
${escapeHtml(data.deckName)}
-
${escapeHtml(data.commanderName || 'Unknown')}
-
- Total Cards - ${data.totalCards || 0} -
-
- Unique Cards - ${data.cardCount || 0} -
-
- ${Object.entries(sections).map(([sec, count]) => - `${escapeHtml(sec)}: ${count}` - ).join('')} -
-
`; - } - html += '
'; - contentEl.innerHTML = html; - - } catch (err) { - contentEl.innerHTML = '
Error loading analytics: ' + escapeHtml(err.message) + '
'; - } - } - - async function loadTrends() { - const contentEl = document.getElementById('lab-trends-content'); - const canvas = document.getElementById('lab-chart-trends'); - if (!contentEl) return; - - const filledDecks = selectedDecks.filter(d => d); - if (filledDecks.length === 0) { - contentEl.innerHTML = '

Select decks to see trends.

'; - if (canvas) canvas.style.display = 'none'; - return; - } - - contentEl.innerHTML = '
Loading trends...
'; - - try { - const results = await Promise.all( - filledDecks.map(async (deckName) => { - const res = await fetch(`${API_BASE}/api/lab/trends/${encodeURIComponent(deckName)}`); - if (!res.ok) return { deckName, history: [] }; - return await res.json(); - }) - ); - - // Check if any deck has history - const anyHistory = results.some(r => (r.history || []).length > 0); - if (!anyHistory) { - contentEl.innerHTML = '

No batch history found. Run multiple batches to track trends.

'; - if (canvas) canvas.style.display = 'none'; - return; - } - - // Build summary cards - let html = ''; - contentEl.innerHTML = html; - - // Draw trends chart if canvas available - if (canvas && typeof Chart !== 'undefined') { - canvas.style.display = 'block'; - drawTrendsChart(results, canvas); - } - - } catch (err) { - contentEl.innerHTML = '
Error loading trends: ' + escapeHtml(err.message) + '
'; - } - } - - function drawTrendsChart(results, canvas) { - const ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const colors = ['#58a6ff', '#f85149', '#3fb950', '#a78bfa', '#fbbf24']; - const padding = { top: 30, right: 20, bottom: 40, left: 50 }; - const w = canvas.width - padding.left - padding.right; - const h = canvas.height - padding.top - padding.bottom; - - // Find max data points - let maxPoints = 0; - for (const r of results) { - maxPoints = Math.max(maxPoints, (r.history || []).length); - } - if (maxPoints < 2) return; - - // Draw axes - ctx.strokeStyle = '#2a2d3a'; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(padding.left, padding.top); - ctx.lineTo(padding.left, canvas.height - padding.bottom); - ctx.lineTo(canvas.width - padding.right, canvas.height - padding.bottom); - ctx.stroke(); - - // Y-axis labels (0-100%) - ctx.fillStyle = '#8b90a0'; - ctx.font = '11px Inter, sans-serif'; - ctx.textAlign = 'right'; - for (let pct = 0; pct <= 100; pct += 25) { - const y = canvas.height - padding.bottom - (pct / 100) * h; - ctx.fillText(pct + '%', padding.left - 8, y + 4); - // Grid line - ctx.strokeStyle = '#1e2130'; - ctx.beginPath(); - ctx.moveTo(padding.left, y); - ctx.lineTo(canvas.width - padding.right, y); - ctx.stroke(); - } - - // Draw lines for each deck - results.forEach((data, di) => { - const history = data.history || []; - if (history.length < 2) return; - - ctx.strokeStyle = colors[di % colors.length]; - ctx.lineWidth = 2; - ctx.beginPath(); - - history.forEach((point, i) => { - const x = padding.left + (i / (maxPoints - 1)) * w; - const y = canvas.height - padding.bottom - (point.winRate / 100) * h; - if (i === 0) ctx.moveTo(x, y); - else ctx.lineTo(x, y); - }); - ctx.stroke(); - - // Draw points - ctx.fillStyle = colors[di % colors.length]; - history.forEach((point, i) => { - const x = padding.left + (i / (maxPoints - 1)) * w; - const y = canvas.height - padding.bottom - (point.winRate / 100) * h; - ctx.beginPath(); - ctx.arc(x, y, 3, 0, Math.PI * 2); - ctx.fill(); - }); - - // Label - const lastPt = history[history.length - 1]; - const lx = padding.left + ((history.length - 1) / (maxPoints - 1)) * w; - const ly = canvas.height - padding.bottom - (lastPt.winRate / 100) * h; - ctx.fillStyle = colors[di % colors.length]; - ctx.textAlign = 'left'; - ctx.font = '11px Inter, sans-serif'; - ctx.fillText(data.deckName, lx + 6, ly + 4); - }); - } - - // ── Precon Refresh ────────────────────────────────────── - - async function refreshPrecons() { - const statusEl = document.getElementById('lab-precon-status'); - - if (!backendAvailable) { - showImportStatus(statusEl, 'Precon refresh requires a running backend.', 'error'); - return; - } - - showImportStatus(statusEl, 'Refreshing precon database from GitHub...', 'loading'); - - try { - const res = await fetch(`${API_BASE}/api/lab/precons/refresh`, { - method: 'POST', - }); - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.detail || 'Refresh failed'); - } - - showImportStatus(statusEl, - data.message || ('Downloaded ' + data.total + ' precon decks'), - 'success' - ); - - // Reload precon list - await loadPrecons(); - renderPreconGrid(preconDecks); - - } catch (err) { - showImportStatus(statusEl, 'Error: ' + err.message, 'error'); - } - } - - function switchTab(tabId) { - document.querySelectorAll('.lab-tab').forEach(btn => { - btn.classList.toggle('active', btn.dataset.tab === tabId); - }); - document.querySelectorAll('.lab-tab-panel').forEach(panel => { - panel.classList.toggle('active', panel.id === tabId); - }); - - // Load data for analytics/trends tabs on demand - if (tabId === 'tab-analytics') loadAnalytics(); - if (tabId === 'tab-trends') loadTrends(); - } - - // ── Charts & Visualizations ──────────────────────────── - - const SEAT_COLORS = ['#58a6ff', '#f85149', '#3fb950']; - const SEAT_COLORS_DIM = ['rgba(88,166,255,0.3)', 'rgba(248,81,73,0.3)', 'rgba(63,185,80,0.3)']; - - function renderCharts(result) { - const games = result.games || []; - const perDeck = result.summary.perDeck || []; - const decks = result.decks || []; - - drawWinRateChart(perDeck, decks); - drawWinConditionChart(perDeck, decks); - drawTurnDistribution(games, decks); - drawWinRateTrend(games, decks); - } - - function drawWinRateChart(perDeck, decks) { - const canvas = document.getElementById('lab-chart-winrate'); - if (!canvas) return; - const ctx = canvas.getContext('2d'); - const w = canvas.width, h = canvas.height; - ctx.clearRect(0, 0, w, h); - - const pad = { top: 20, right: 20, bottom: 50, left: 50 }; - const chartW = w - pad.left - pad.right; - const chartH = h - pad.top - pad.bottom; - const barW = Math.min(60, chartW / perDeck.length - 20); - - // Axes - ctx.strokeStyle = '#30363d'; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(pad.left, pad.top); - ctx.lineTo(pad.left, h - pad.bottom); - ctx.lineTo(w - pad.right, h - pad.bottom); - ctx.stroke(); - - // Y-axis labels (0%, 25%, 50%, 75%, 100%) - ctx.fillStyle = '#8b949e'; - ctx.font = '11px -apple-system, sans-serif'; - ctx.textAlign = 'right'; - for (let pct = 0; pct <= 100; pct += 25) { - const y = h - pad.bottom - (pct / 100) * chartH; - ctx.fillText(pct + '%', pad.left - 8, y + 4); - if (pct > 0) { - ctx.strokeStyle = 'rgba(48,54,61,0.5)'; - ctx.beginPath(); - ctx.moveTo(pad.left, y); - ctx.lineTo(w - pad.right, y); - ctx.stroke(); - } - } - - // Bars - perDeck.forEach((ds, i) => { - const x = pad.left + (i + 0.5) * (chartW / perDeck.length) - barW / 2; - const barH = (ds.winRate) * chartH; - const y = h - pad.bottom - barH; - - // Bar fill - ctx.fillStyle = SEAT_COLORS[ds.seatIndex] || SEAT_COLORS[0]; - ctx.beginPath(); - roundRect(ctx, x, y, barW, barH, 4); - ctx.fill(); - - // Value on top - ctx.fillStyle = '#c9d1d9'; - ctx.font = 'bold 12px -apple-system, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText((ds.winRate * 100).toFixed(1) + '%', x + barW / 2, y - 6); - - // Label - ctx.fillStyle = '#8b949e'; - ctx.font = '10px -apple-system, sans-serif'; - const label = (decks[ds.seatIndex]?.commanderName || ds.deckName || '').substring(0, 16); - ctx.fillText(label, x + barW / 2, h - pad.bottom + 14); - }); - } - - function drawWinConditionChart(perDeck, decks) { - const canvas = document.getElementById('lab-chart-wincond'); - if (!canvas) return; - const ctx = canvas.getContext('2d'); - const w = canvas.width, h = canvas.height; - ctx.clearRect(0, 0, w, h); - - // Aggregate win conditions across all decks - const totals = {}; - perDeck.forEach(ds => { - const bd = ds.winConditionBreakdown || {}; - Object.entries(bd).forEach(([k, v]) => { - if (v > 0) totals[k] = (totals[k] || 0) + v; - }); - }); - - const entries = Object.entries(totals).filter(([_, v]) => v > 0).sort((a, b) => b[1] - a[1]); - if (entries.length === 0) { - ctx.fillStyle = '#8b949e'; - ctx.font = '13px -apple-system, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText('No win data', w / 2, h / 2); - return; - } - - const total = entries.reduce((s, [_, v]) => s + v, 0); - const pieColors = ['#58a6ff', '#f85149', '#3fb950', '#d29922', '#a371f7', '#f0883e', '#8b949e', '#56d364']; - const cx = w * 0.35, cy = h / 2, radius = Math.min(cx - 20, cy - 20); - - let startAngle = -Math.PI / 2; - const slices = []; - entries.forEach(([k, v], i) => { - const sliceAngle = (v / total) * Math.PI * 2; - ctx.fillStyle = pieColors[i % pieColors.length]; - ctx.beginPath(); - ctx.moveTo(cx, cy); - ctx.arc(cx, cy, radius, startAngle, startAngle + sliceAngle); - ctx.closePath(); - ctx.fill(); - slices.push({ key: k, value: v, color: pieColors[i % pieColors.length], startAngle, endAngle: startAngle + sliceAngle }); - startAngle += sliceAngle; - }); - - // Legend - const legendX = w * 0.65; - let legendY = 30; - ctx.font = '11px -apple-system, sans-serif'; - ctx.textAlign = 'left'; - slices.forEach(s => { - ctx.fillStyle = s.color; - ctx.fillRect(legendX, legendY - 8, 10, 10); - ctx.fillStyle = '#c9d1d9'; - const pct = ((s.value / total) * 100).toFixed(0); - ctx.fillText(`${formatWinCondition(s.key)} (${pct}%)`, legendX + 16, legendY); - legendY += 18; - }); - } - - function drawTurnDistribution(games, decks) { - const canvas = document.getElementById('lab-chart-turns'); - if (!canvas) return; - const ctx = canvas.getContext('2d'); - const w = canvas.width, h = canvas.height; - ctx.clearRect(0, 0, w, h); - - if (games.length === 0) return; - - const pad = { top: 20, right: 20, bottom: 50, left: 50 }; - const chartW = w - pad.left - pad.right; - const chartH = h - pad.top - pad.bottom; - - // Build histogram buckets - const turns = games.map(g => g.totalTurns); - const minT = Math.min(...turns); - const maxT = Math.max(...turns); - const bucketSize = Math.max(1, Math.ceil((maxT - minT + 1) / 15)); - const buckets = []; - for (let t = minT; t <= maxT; t += bucketSize) { - const count = turns.filter(v => v >= t && v < t + bucketSize).length; - buckets.push({ label: bucketSize === 1 ? '' + t : t + '-' + (t + bucketSize - 1), count }); - } - - const maxCount = Math.max(...buckets.map(b => b.count)); - const barW = Math.max(4, chartW / buckets.length - 2); - - // Axes - ctx.strokeStyle = '#30363d'; - ctx.beginPath(); - ctx.moveTo(pad.left, pad.top); - ctx.lineTo(pad.left, h - pad.bottom); - ctx.lineTo(w - pad.right, h - pad.bottom); - ctx.stroke(); - - // Y-axis - ctx.fillStyle = '#8b949e'; - ctx.font = '10px -apple-system, sans-serif'; - ctx.textAlign = 'right'; - for (let i = 0; i <= 4; i++) { - const val = Math.round(maxCount * i / 4); - const y = h - pad.bottom - (i / 4) * chartH; - ctx.fillText(val, pad.left - 8, y + 3); - } - - // Bars - buckets.forEach((b, i) => { - const x = pad.left + i * (chartW / buckets.length) + 1; - const barH = maxCount > 0 ? (b.count / maxCount) * chartH : 0; - const y = h - pad.bottom - barH; - - ctx.fillStyle = '#58a6ff'; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - roundRect(ctx, x, y, barW, barH, 2); - ctx.fill(); - ctx.globalAlpha = 1; - - // Label - if (buckets.length <= 20) { - ctx.fillStyle = '#8b949e'; - ctx.font = '9px -apple-system, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText(b.label, x + barW / 2, h - pad.bottom + 14); - } - }); - - // X-axis label - ctx.fillStyle = '#8b949e'; - ctx.font = '11px -apple-system, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText('Turns', w / 2, h - 6); - } - - function drawWinRateTrend(games, decks) { - const canvas = document.getElementById('lab-chart-wintrend'); - if (!canvas) return; - const ctx = canvas.getContext('2d'); - const w = canvas.width, h = canvas.height; - ctx.clearRect(0, 0, w, h); - - if (games.length < 2) return; - - const pad = { top: 20, right: 20, bottom: 50, left: 50 }; - const chartW = w - pad.left - pad.right; - const chartH = h - pad.top - pad.bottom; - - // Compute cumulative win rate for each seat - const seatWins = [0, 0, 0]; - const seatRates = [[], [], []]; - - // Sample points (don't plot every game for large batches) - const step = Math.max(1, Math.floor(games.length / 100)); - - games.forEach((g, gi) => { - if (g.winningSeat !== null && g.winningSeat !== undefined) { - seatWins[g.winningSeat]++; - } - if (gi % step === 0 || gi === games.length - 1) { - const n = gi + 1; - for (let s = 0; s < 3; s++) { - seatRates[s].push({ x: gi, y: seatWins[s] / n }); - } - } - }); - - // Axes - ctx.strokeStyle = '#30363d'; - ctx.beginPath(); - ctx.moveTo(pad.left, pad.top); - ctx.lineTo(pad.left, h - pad.bottom); - ctx.lineTo(w - pad.right, h - pad.bottom); - ctx.stroke(); - - // Y-axis labels - ctx.fillStyle = '#8b949e'; - ctx.font = '10px -apple-system, sans-serif'; - ctx.textAlign = 'right'; - for (let pct = 0; pct <= 100; pct += 25) { - const y = h - pad.bottom - (pct / 100) * chartH; - ctx.fillText(pct + '%', pad.left - 8, y + 3); - ctx.strokeStyle = 'rgba(48,54,61,0.3)'; - ctx.beginPath(); - ctx.moveTo(pad.left, y); - ctx.lineTo(w - pad.right, y); - ctx.stroke(); - } - - // Lines - const maxX = games.length - 1; - for (let s = 0; s < 3; s++) { - const points = seatRates[s]; - if (points.length < 2) continue; - - ctx.strokeStyle = SEAT_COLORS[s]; - ctx.lineWidth = 2; - ctx.beginPath(); - points.forEach((p, pi) => { - const px = pad.left + (p.x / maxX) * chartW; - const py = h - pad.bottom - p.y * chartH; - if (pi === 0) ctx.moveTo(px, py); - else ctx.lineTo(px, py); - }); - ctx.stroke(); - ctx.lineWidth = 1; - } - - // Legend - ctx.font = '10px -apple-system, sans-serif'; - ctx.textAlign = 'left'; - for (let s = 0; s < 3; s++) { - const lx = pad.left + 10 + s * 120; - ctx.fillStyle = SEAT_COLORS[s]; - ctx.fillRect(lx, pad.top + 2, 12, 3); - ctx.fillStyle = '#8b949e'; - const label = (decks[s]?.commanderName || decks[s]?.deckName || 'Deck ' + (s + 1)).substring(0, 14); - ctx.fillText(label, lx + 16, pad.top + 7); - } - - // X-axis label - ctx.fillStyle = '#8b949e'; - ctx.font = '11px -apple-system, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText('Games Played', w / 2, h - 6); - } - - function roundRect(ctx, x, y, w, h, r) { - if (h <= 0) { ctx.rect(x, y, w, 0); return; } - r = Math.min(r, h / 2, w / 2); - ctx.moveTo(x + r, y); - ctx.arcTo(x + w, y, x + w, y + h, r); - ctx.arcTo(x + w, y + h, x, y + h, r); - ctx.arcTo(x, y + h, x, y, r); - ctx.arcTo(x, y, x + w, y, r); - } - - // ── Head-to-Head Matrix ──────────────────────────────── - - function renderHeadToHead(result) { - const el = document.getElementById('lab-h2h-matrix'); - if (!el) return; - - const games = result.games || []; - const decks = result.decks || []; - if (games.length === 0) { el.innerHTML = '

No game data

'; return; } - - // Compute pairwise: for each pair (A, B), how often did A win when both were in the game (always — 3-player pod) - // Show: when deck A wins, what % of those games vs each opponent - // Better metric: For each pair, count games where A wins and B doesn't (and vice versa) - const n = decks.length; - const h2h = Array.from({ length: n }, () => Array(n).fill(0)); - const pairGames = Array.from({ length: n }, () => Array(n).fill(0)); - - games.forEach(g => { - for (let a = 0; a < n; a++) { - for (let b = 0; b < n; b++) { - if (a === b) continue; - pairGames[a][b]++; - if (g.winningSeat === a) h2h[a][b]++; - } - } - }); - - let html = ''; - decks.forEach((d, i) => { - const name = (d.commanderName || d.deckName || 'Deck ' + (i + 1)).substring(0, 18); - html += ``; - }); - html += ''; - - for (let a = 0; a < n; a++) { - const name = (decks[a].commanderName || decks[a].deckName || 'Deck ' + (a + 1)).substring(0, 18); - html += ``; - for (let b = 0; b < n; b++) { - if (a === b) { - html += ''; - } else { - const wins = h2h[a][b]; - const total = pairGames[a][b]; - const rate = total > 0 ? (wins / total * 100).toFixed(1) : '0.0'; - const rateNum = total > 0 ? wins / total : 0; - const cls = rateNum > 0.5 ? 'lab-h2h-winning' : rateNum < 0.3 ? 'lab-h2h-losing' : ''; - html += ``; - } - } - html += ''; - } - html += '
vs${escapeHtml(name)}
${escapeHtml(name)}—${rate}%${wins}/${total}
'; - - html += '

Reads as: row deck\'s win rate in games where column deck also played.

'; - el.innerHTML = html; - } - - // ── Combat Stats ────────────────────────────────────── - - function renderCombatStats(result) { - const el = document.getElementById('lab-combat-stats'); - if (!el) return; - - const games = result.games || []; - const decks = result.decks || []; - if (games.length === 0) { el.innerHTML = '

No game data

'; return; } - - // Aggregate per-seat combat stats from game-level playerResults - const stats = decks.map((d, seat) => { - const pr = games.map(g => g.playerResults[seat]).filter(Boolean); - const n = pr.length; - if (n === 0) return null; - - const sum = (fn) => pr.reduce((s, p) => s + fn(p), 0); - const avg = (fn) => sum(fn) / n; - const max = (fn) => Math.max(...pr.map(fn)); - - return { - seat, - name: d.commanderName || d.deckName || 'Deck ' + (seat + 1), - avgCmdrDmg: avg(p => p.commanderDamageDealt || 0), - maxCmdrDmg: max(p => p.commanderDamageDealt || 0), - avgCmdrCasts: avg(p => p.commanderCasts || 0), - maxCmdrCasts: max(p => p.commanderCasts || 0), - avgLands: avg(p => p.landsPlayed || 0), - avgSpells: avg(p => p.spellsCast || 0), - maxSpells: max(p => p.spellsCast || 0), - avgCreaturesDestroyed: avg(p => p.creaturesDestroyed || 0), - totalCreaturesDestroyed: sum(p => p.creaturesDestroyed || 0), - avgFinalLife: avg(p => p.finalLife || 0), - }; - }).filter(Boolean); - - let html = '
'; - stats.forEach(s => { - html += ` -
-
- ${escapeHtml(s.name)} -
-
-
- ⚔️ - Cmdr Damage / Game - ${s.avgCmdrDmg.toFixed(1)} - max ${s.maxCmdrDmg} -
-
- 👑 - Cmdr Casts / Game - ${s.avgCmdrCasts.toFixed(2)} - max ${s.maxCmdrCasts} -
-
- 🏔️ - Lands / Game - ${s.avgLands.toFixed(1)} -
-
- ✨ - Spells / Game - ${s.avgSpells.toFixed(1)} - max ${s.maxSpells} -
-
- 💀 - Creatures Destroyed / Game - ${s.avgCreaturesDestroyed.toFixed(1)} - total ${s.totalCreaturesDestroyed} -
-
- ❤️ - Avg Final Life - ${s.avgFinalLife.toFixed(1)} -
-
-
`; - }); - html += '
'; - el.innerHTML = html; - } - - // ── Game-by-Game Log ────────────────────────────────── - - let gameLogData = []; - - function renderGameLog(result) { - const wrapper = document.getElementById('lab-gamelog-wrapper'); - const countEl = document.getElementById('lab-gamelog-count'); - const filterInput = document.getElementById('lab-gamelog-filter'); - if (!wrapper) return; - - const games = result.games || []; - const decks = result.decks || []; - - gameLogData = games.map(g => { - const winnerName = g.winningSeat !== null && g.winningSeat !== undefined - ? (decks[g.winningSeat]?.commanderName || decks[g.winningSeat]?.deckName || 'Deck ' + (g.winningSeat + 1)) - : 'Draw'; - return { - index: g.gameIndex + 1, - winner: winnerName, - winningSeat: g.winningSeat, - turns: g.totalTurns, - winCondition: g.winCondition, - elapsed: g.elapsedMs, - players: g.playerResults.map((pr, i) => ({ - name: (decks[i]?.commanderName || decks[i]?.deckName || 'Deck ' + (i + 1)).substring(0, 16), - life: pr.finalLife, - seat: i, - isWinner: pr.isWinner, - })), - }; - }); - - // Wire up filter - if (filterInput) { - filterInput.oninput = () => filterGameLog(filterInput.value); - } - - renderGameLogTable(gameLogData); - } - - function filterGameLog(query) { - const q = query.toLowerCase().trim(); - if (!q) { - renderGameLogTable(gameLogData); - return; - } - const filtered = gameLogData.filter(g => - g.winner.toLowerCase().includes(q) || - (g.winCondition || '').toLowerCase().includes(q) || - formatWinCondition(g.winCondition).toLowerCase().includes(q) || - g.players.some(p => p.name.toLowerCase().includes(q)) - ); - renderGameLogTable(filtered); - } - - function renderGameLogTable(data) { - const wrapper = document.getElementById('lab-gamelog-wrapper'); - const countEl = document.getElementById('lab-gamelog-count'); - if (!wrapper) return; - - if (countEl) { - countEl.textContent = data.length + ' game' + (data.length !== 1 ? 's' : ''); - } - - // Show max 200 rows, paginated - const pageSize = 200; - const display = data.slice(0, pageSize); - - let html = ` - - - - - `; - // Life columns for each seat - if (display.length > 0 && display[0].players) { - display[0].players.forEach((p, i) => { - html += ``; - }); - } - html += ''; - - display.forEach(g => { - const winColor = g.winningSeat !== null && g.winningSeat !== undefined - ? SEAT_COLORS[g.winningSeat] : '#8b949e'; - html += ` - - - - `; - g.players.forEach(p => { - const lifeClass = p.life <= 0 ? 'lab-gl-dead' : p.isWinner ? 'lab-gl-alive' : ''; - html += ``; - }); - html += ''; - }); - html += '
#WinnerTurnsWin Condition${escapeHtml(p.name)}
${g.index}${escapeHtml(g.winner)}${g.turns}${formatWinCondition(g.winCondition)}${p.life}
'; - - if (data.length > pageSize) { - html += `

Showing ${pageSize} of ${data.length} games

`; - } - - wrapper.innerHTML = html; - } - - // ── History ──────────────────────────────────────────── - - async function showHistory() { - const modal = document.getElementById('lab-history-modal'); - const listEl = document.getElementById('lab-history-list'); - if (!modal || !listEl) return; - - modal.classList.add('active'); - listEl.innerHTML = 'Loading...'; - - if (!backendAvailable) { - listEl.innerHTML = '

History requires a running backend.

'; - return; - } - - try { - const res = await fetch(`${API_BASE}/api/lab/history`); - if (!res.ok) throw new Error('Failed to load history'); - const data = await res.json(); - - if (!data.results || data.results.length === 0) { - listEl.innerHTML = '

No past runs found.

'; - return; - } - - listEl.innerHTML = data.results.map(r => { - const deckLabels = (r.decks || []).map(d => { - const srcTag = d.source ? ` [${d.source}]` : ''; - return `${escapeHtml(d.name)}${srcTag}`; - }).join(', '); - - return ` -
-
${r.batchId}
-
- ${r.totalGames} games · ${r.threads} threads · ${(r.elapsedMs/1000).toFixed(1)}s -
-
${deckLabels}
-
${new Date(r.timestamp).toLocaleString()}
-
- `; - }).join(''); - } catch (err) { - listEl.innerHTML = `

Error: ${err.message}

`; - } - } - - function closeHistory() { - const modal = document.getElementById('lab-history-modal'); - if (modal) modal.classList.remove('active'); - } - - async function loadHistoryResult(filename) { - closeHistory(); - try { - // Fetch from results directory via API - const batchId = filename.replace('batch-', '').replace('.json', ''); - const res = await fetch(`${API_BASE}/api/lab/result?batchId=${batchId}`); - if (res.ok) { - lastResult = await res.json(); - displayResults(lastResult); - } - } catch (err) { - console.error('[AI Lab] Failed to load history result:', err); - } - } - - // ── Helpers ───────────────────────────────────────────── - - function formatWinCondition(key) { - const map = { - combat_damage: 'Combat', - commander_damage: 'Cmdr Dmg', - combo_alt_win: 'Combo', - life_drain: 'Drain', - mill: 'Mill', - concession: 'Concede', - timeout: 'Timeout', - unknown: '???', - }; - return map[key] || key; - } - - function setRunning(running) { - isRunning = running; - const btn = document.getElementById('lab-run-btn'); - if (btn) { - btn.disabled = running; - btn.classList.toggle('running', running); - btn.querySelector('.run-text').textContent = running ? 'Simulating...' : 'Run AI Sims'; - } - if (!running) { - stopPolling(); - // Keep progress visible briefly, then fade - setTimeout(() => { - const progressEl = document.getElementById('lab-progress'); - if (progressEl) progressEl.classList.remove('active'); - }, 3000); - } - } - - function seededRandom(seed) { - let s = seed | 0; - return function() { - s |= 0; s = s + 0x6D2B79F5 | 0; - let t = Math.imul(s ^ s >>> 15, 1 | s); - t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; - return ((t ^ t >>> 14) >>> 0) / 4294967296; - }; - } - - function exportJson() { - if (!lastResult) return; - const json = JSON.stringify(lastResult, null, 2); - const blob = new Blob([json], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `commander-ai-lab-${lastResult.metadata.batchId}.json`; - a.click(); - URL.revokeObjectURL(url); - } - - function escapeHtml(str) { - if (!str) return ''; - return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); - } - - function escapeAttr(str) { - return str.replace(/'/g, "\\'").replace(/"/g, '\\"'); - } - - function bindEvents() { - const input = document.getElementById('lab-game-count'); - if (input) { - input.addEventListener('change', () => { - document.querySelectorAll('.lab-preset-btn').forEach(btn => { - btn.classList.toggle('active', parseInt(btn.textContent) === parseInt(input.value)); - }); - }); - } - - // Close modals on overlay click - document.querySelectorAll('.lab-modal-overlay').forEach(modal => { - modal.addEventListener('click', (e) => { - if (e.target === modal) { - modal.classList.remove('active'); - } - }); - }); - - // Enter key in URL import - const urlInput = document.getElementById('lab-import-url-input'); - if (urlInput) { - urlInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter') importFromUrl(); - }); - } - } - - // ── Public API ───────────────────────────────────────── - - return { - init, - selectDeck, - setGameCount, - startBatch, - exportJson, - showHistory, - closeHistory, - loadHistoryResult, - toggleLog, - getLastResult: () => lastResult, - // v3 - showUrlImport, - hideUrlImport, - importFromUrl, - showMetaPicker, - closeMetaPicker, - filterMetaCommanders, - fetchMetaDeck, - showTextImport, - closeTextImport, - importFromText, - // Precon decks - showPreconPicker, - closePreconPicker, - filterPrecons, - installPrecon, - installAllPrecons, - // v4 — enhanced results - switchTab, - // v5 — analytics, trends, precon refresh - loadAnalytics, - loadTrends, - refreshPrecons, - }; - -})(); - -// Auto-initialize when DOM is ready -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', AiLab.init); -} else { - AiLab.init(); -} +LyoqCiAqIENvbW1hbmRlciBBSSBMYWIg4oCUIFdlYiBVSSBDb250cm9sbGVyIHYzCiAqIOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkOKVkAogKgogKiB2MyBhZGRzOgogKiAgIC0gVVJMIGltcG9ydCAoQXJjaGlkZWt0L0VESFJFQykg4oCUIHBhc3RlIGEgVVJMIHRvIGltcG9ydCBhIGRlY2sKICogICAtIENvbW1hbmRlciBtZXRhIHBpY2tlciDigJQgYnJvd3NlL3NlYXJjaCAxNSBidWlsdC1pbiBjb21tYW5kZXJzLCBvbmUtY2xpY2sgRURIUkVDIGZldGNoCiAqICAgLSBUZXh0IGltcG9ydCBtb2RhbCDigJQgcGFzdGUgYSBjYXJkIGxpc3QgZGlyZWN0bHkKICogICAtIFNvdXJjZSBiYWRnZXMgb24gcmVzdWx0IGNhcmRzIOKAlCBzaG93cyB3aGVyZSBlYWNoIGRlY2sgY2FtZSBmcm9tCiAqICAgLSBJbXBvcnRlZCBkZWNrIGluZm8gaW4gZGVjayBzZWxlY3RvcnMKICoKICogRW5kcG9pbnRzIGNvbnN1bWVkOgogKiAgIEdFVCAgL2FwaS9sYWIvZGVja3MgICAgICAgICAgICAgIOKAlCBMaXN0IGF2YWlsYWJsZSBDb21tYW5kZXIgZGVja3MKICogICBQT1NUIC9hcGkvbGFiL3N0YXJ0ICAgICAgICAgICAgICDigJQgU3RhcnQgYSBiYXRjaCBydW4KICogICBHRVQgIC9hcGkvbGFiL3N0YXR1cyAgICAgICAgICAgICDigJQgUG9sbCBwcm9ncmVzcwogKiAgIEdFVCAgL2FwaS9sYWIvcmVzdWx0ICAgICAgICAgICAgIOKAlCBHZXQgY29tcGxldGVkIHJlc3VsdHMgSlNPTgogKiAgIEdFVCAgL2FwaS9sYWIvbG9nICAgICAgICAgICAgICAgIOKAlCBHZXQgbGl2ZSBsb2cgb3V0cHV0CiAqICAgR0VUICAvYXBpL2xhYi9oaXN0b3J5ICAgICAgICAgICAg4oCUIExpc3QgcGFzdCBiYXRjaCBydW5zCiAqICAgUE9TVCAvYXBpL2xhYi9pbXBvcnQvdXJsICAgICAgICAg4oCUIEltcG9ydCBkZWNrIGZyb20gQXJjaGlkZWt0L0VESFJFQyBVUkwKICogICBQT1NUIC9hcGkvbGFiL2ltcG9ydC90ZXh0ICAgICAgICDigJQgSW1wb3J0IGRlY2sgZnJvbSBjYXJkIGxpc3QgdGV4dAogKiAgIEdFVCAgL2FwaS9sYWIvbWV0YS9jb21tYW5kZXJzICAgIOKAlCBMaXN0IGF2YWlsYWJsZSBjb21tYW5kZXJzIGluIG1ldGEgbWFwcGluZwogKiAgIEdFVCAgL2FwaS9sYWIvbWV0YS9zZWFyY2ggICAgICAgIOKAlCBTZWFyY2ggY29tbWFuZGVycyBieSBuYW1lCiAqICAgUE9TVCAvYXBpL2xhYi9tZXRhL2ZldGNoICAgICAgICAg4oCUIEZldGNoIEVESFJFQyBhdmVyYWdlIGRlY2sgZm9yIGEgY29tbWFuZGVyCiAqLwoKY29uc3QgQWlMYWIgPSAoKCkgPT4gewogICAgJ3VzZSBzdHJpY3QnOwoKICAgIC8vIOKUgOKUgCBTdGF0ZSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKICAgIGxldCBpc1J1bm5pbmcgPSBmYWxzZTsKICAgIGxldCBzZWxlY3RlZERlY2tzID0gW251bGwsIG51bGwsIG51bGwsIG51bGxdOwogICAgbGV0IGxhc3RSZXN1bHQgPSBudWxsOwogICAgbGV0IHBvbGxJbnRlcnZhbCA9IG51bGw7CiAgICBsZXQgbG9nSW50ZXJ2YWwgPSBudWxsOwogICAgbGV0IGJhY2tlbmRBdmFpbGFibGUgPSBmYWxzZTsKICAgIGxldCBhdmFpbGFibGVEZWNrcyA9IFtdOwogICAgbGV0IG1ldGFDb21tYW5kZXJzID0gW107CiAgICBsZXQgcHJlY29uRGVja3MgPSBbXTsgICAgICAgLy8gTG9hZGVkIGZyb20gL2FwaS9sYWIvcHJlY29ucwogICAgbGV0IGltcG9ydGVkRGVja01ldGEgPSB7fTsgIC8vIGRlY2tOYW1lIOKGkiB7c291cmNlLCBzb3VyY2VVcmwsIGNvbW1hbmRlciwgYXJjaGV0eXBlLCBjb2xvcklkZW50aXR5fQoKICAgIC8vIOKUgOKUgCBDb25maWd1cmF0aW9uIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAogICAgY29uc3QgQVBJX0JBU0UgPSB3aW5kb3cubG9jYXRpb24ub3JpZ2luOwogICAgY29uc3QgUFJFU0VUX0dBTUVTID0gWzEwLCAxMDAsIDEwMDBdOwogICAgY29uc3QgUE9MTF9SQVRFX01TID0gNzUwOwogICAgY29uc3QgTE9HX1JBVEVfTVMgPSAxMDAwOwoKICAgIGNvbnN0IENPTE9SX01BUCA9IHsKICAgICAgICBXOiB7IGxhYmVsOiAnVycsIGJnOiAnI2Y5ZmFmNCcsIGNvbG9yOiAnIzNkMzkyOScgfSwKICAgICAgICBVOiB7IGxhYmVsOiAnVScsIGJnOiAnIzBlNjdhYicsIGNvbG9yOiAnI2ZmZicgfSwKICAgICAgICBCOiB7IGxhYmVsOiAnQicsIGJnOiAnIzJiMjQyNCcsIGNvbG9yOiAnI2NjYycgfSwKICAgICAgICBSOiB7IGxhYmVsOiAnUicsIGJnOiAnI2QzMjAyYScsIGNvbG9yOiAnI2ZmZicgfSwKICAgICAgICBHOiB7IGxhYmVsOiAnRycsIGJnOiAnIzAwNzMzZScsIGNvbG9yOiAnI2ZmZicgfSwKICAgIH07CgogICAgLy8g4pSA4pSAIEluaXRpYWxpemF0aW9uIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGFzeW5jIGZ1bmN0aW9uIGluaXQoKSB7CiAgICAgICAgcmVuZGVyUGFuZWwoKTsKICAgICAgICBhd2FpdCBkZXRlY3RCYWNrZW5kKCk7CiAgICAgICAgYXdhaXQgUHJvbWlzZS5hbGwoWwogICAgICAgICAgICBsb2FkQXZhaWxhYmxlRGVja3MoKSwKICAgICAgICAgICAgbG9hZE1ldGFDb21tYW5kZXJzKCksCiAgICAgICAgICAgIGxvYWRQcmVjb25zKCksCiAgICAgICAgXSk7CiAgICAgICAgYmluZEV2ZW50cygpOwogICAgICAgIGNvbnNvbGUubG9nKCdbQUkgTGFiXSB2MyBJbml0aWFsaXplZCDigJQgYmFja2VuZDonLCBiYWNrZW5kQXZhaWxhYmxlID8gJ2Nvbm5lY3RlZCcgOiAnZGVtbyBtb2RlJyk7CiAgICB9CgogICAgYXN5bmMgZnVuY3Rpb24gZGV0ZWN0QmFja2VuZCgpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChgJHtBUElfQkFTRX0vYXBpL2xhYi9kZWNrc2AsIHsgc2lnbmFsOiBBYm9ydFNpZ25hbC50aW1lb3V0KDMwMDApIH0pOwogICAgICAgICAgICBiYWNrZW5kQXZhaWxhYmxlID0gcmVzLm9rOwogICAgICAgIH0gY2F0Y2ggewogICAgICAgICAgICBiYWNrZW5kQXZhaWxhYmxlID0gZmFsc2U7CiAgICAgICAgfQogICAgICAgIHVwZGF0ZUJhY2tlbmRJbmRpY2F0b3IoKTsKICAgIH0KCiAgICAvLyDilIDilIAgUmVuZGVyIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGZ1bmN0aW9uIHJlbmRlclBhbmVsKCkgewogICAgICAgIGNvbnN0IGNvbnRhaW5lciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhaS1sYWItY29udGFpbmVyJyk7CiAgICAgICAgaWYgKCFjb250YWluZXIpIHJldHVybjsKCiAgICAgICAgY29udGFpbmVyLmlubmVySFRNTCA9IGAKICAgICAgICA8ZGl2IGlkPSJhaS1sYWItcGFuZWwiPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItaGVhZGVyIj4KICAgICAgICAgICAgICAgIDxoMj5Db21tYW5kZXIgQUkgTGFiPC9oMj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1iYWNrZW5kLXN0YXR1cyIgaWQ9ImxhYi1iYWNrZW5kLXN0YXR1cyI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9InN0YXR1cy1kb3QiPjwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ic3RhdHVzLXRleHQiPkNoZWNraW5nLi4uPC9zcGFuPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8cCBjbGFzcz0ibGFiLXN1YnRpdGxlIj5IZWFkbGVzcyAzLUFJIGJhdGNoIHNpbXVsYXRpb24g4oCUIHN0cmVzcy10ZXN0IHlvdXIgZGVja3MgYXQgc2NhbGU8L3A+CgogICAgICAgICAgICA8IS0tIHYzOiBJbXBvcnQgQmFyIC0tPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItaW1wb3J0LWJhciI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItaW1wb3J0LWJhci1sYWJlbCI+SW1wb3J0IERlY2s8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1pbXBvcnQtYWN0aW9ucyI+CiAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLWltcG9ydC1hY3Rpb24tYnRuIiBvbmNsaWNrPSJBaUxhYi5zaG93VXJsSW1wb3J0KCkiIHRpdGxlPSJJbXBvcnQgZnJvbSBBcmNoaWRla3Qgb3IgRURIUkVDIFVSTCI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJpbXBvcnQtaWNvbiI+8J+Ulzwvc3Bhbj4gVVJMIEltcG9ydAogICAgICAgICAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi1pbXBvcnQtYWN0aW9uLWJ0biIgb25jbGljaz0iQWlMYWIuc2hvd01ldGFQaWNrZXIoKSIgdGl0bGU9IkJyb3dzZSBFREhSRUMgYXZlcmFnZSBkZWNrcyBmb3IgcG9wdWxhciBjb21tYW5kZXJzIj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImltcG9ydC1pY29uIj7wn5OKPC9zcGFuPiBDb21tYW5kZXIgTWV0YQogICAgICAgICAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi1pbXBvcnQtYWN0aW9uLWJ0biIgb25jbGljaz0iQWlMYWIuc2hvd1RleHRJbXBvcnQoKSIgdGl0bGU9IlBhc3RlIGEgY2FyZCBsaXN0IGRpcmVjdGx5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImltcG9ydC1pY29uIj7wn5OLPC9zcGFuPiBUZXh0IEltcG9ydAogICAgICAgICAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi1pbXBvcnQtYWN0aW9uLWJ0biBsYWItcHJlY29uLWJ0biIgb25jbGljaz0iQWlMYWIuc2hvd1ByZWNvblBpY2tlcigpIiB0aXRsZT0iQnJvd3NlIGFuZCBsb2FkIG9mZmljaWFsIENvbW1hbmRlciBwcmVjb24gZGVja3MiPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iaW1wb3J0LWljb24iPvCfjrQ8L3NwYW4+IFByZWNvbiBEZWNrcwogICAgICAgICAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgPCEtLSB2MzogVVJMIEltcG9ydCBQYW5lbCAoaGlkZGVuIGJ5IGRlZmF1bHQpIC0tPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItdXJsLWltcG9ydCIgaWQ9ImxhYi11cmwtaW1wb3J0Ij4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi11cmwtaW1wb3J0LWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4+SW1wb3J0IGZyb20gVVJMPC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi1wYW5lbC1jbG9zZSIgb25jbGljaz0iQWlMYWIuaGlkZVVybEltcG9ydCgpIj7inJU8L2J1dHRvbj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXVybC1pbXBvcnQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9InRleHQiIGlkPSJsYWItaW1wb3J0LXVybC1pbnB1dCIKICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxhY2Vob2xkZXI9IlBhc3RlIEFyY2hpZGVrdCBvciBFREhSRUMgVVJMIChlLmcuIGh0dHBzOi8vYXJjaGlkZWt0LmNvbS9kZWNrcy8xMjM0NSkiCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9jb21wbGV0ZT0ib2ZmIiAvPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi11cmwtaW1wb3J0LWJ0biIgaWQ9ImxhYi11cmwtaW1wb3J0LWJ0biIgb25jbGljaz0iQWlMYWIuaW1wb3J0RnJvbVVybCgpIj4KICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0CiAgICAgICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1pbXBvcnQtaGludCI+CiAgICAgICAgICAgICAgICAgICAgU3VwcG9ydGVkOiA8c3Ryb25nPmFyY2hpZGVrdC5jb20vZGVja3MvLi4uPC9zdHJvbmc+IMK3IDxzdHJvbmc+ZWRocmVjLmNvbS9hdmVyYWdlLWRlY2tzLy4uLjwvc3Ryb25nPiDCtyA8c3Ryb25nPmVkaHJlYy5jb20vY29tbWFuZGVycy8uLi48L3N0cm9uZz4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWltcG9ydC1zdGF0dXMiIGlkPSJsYWItdXJsLWltcG9ydC1zdGF0dXMiPjwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgIDwhLS0gRGVjayBTZWxlY3Rpb24gLS0+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1kZWNrLWdyaWQiPgogICAgICAgICAgICAgICAgJHtbMCwgMSwgMiwgM10ubWFwKGkgPT4gYAogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWRlY2stc2xvdCIgaWQ9ImxhYi1zbG90LSR7aX0iIGRhdGEtc2VhdD0iJHtpfSI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ic2VhdC1sYWJlbCI+U2VhdCAke2kgKyAxfTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxzZWxlY3QgaWQ9ImxhYi1kZWNrLXNlbGVjdC0ke2l9IiBvbmNoYW5nZT0iQWlMYWIuc2VsZWN0RGVjaygke2l9LCB0aGlzLnZhbHVlKSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxvcHRpb24gdmFsdWU9IiI+4oCUIFNlbGVjdCBEZWNrIOKAlDwvb3B0aW9uPgogICAgICAgICAgICAgICAgICAgIDwvc2VsZWN0PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbW1hbmRlci1uYW1lIiBpZD0ibGFiLWNvbW1hbmRlci0ke2l9Ij48L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItZGVjay1zb3VyY2UtYmFkZ2UiIGlkPSJsYWItc291cmNlLWJhZGdlLSR7aX0iPjwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICBgKS5qb2luKCcnKX0KICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICA8IS0tIENvbnRyb2xzIC0tPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY29udHJvbHMiPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWNvbnRyb2wtZ3JvdXAiPgogICAgICAgICAgICAgICAgICAgIDxsYWJlbD5HYW1lczo8L2xhYmVsPgogICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJudW1iZXIiIGlkPSJsYWItZ2FtZS1jb3VudCIgdmFsdWU9IjEwMCIgbWluPSIxIiBtYXg9IjEwMDAwIiAvPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1wcmVzZXQtYnRucyI+CiAgICAgICAgICAgICAgICAgICAgICAgICR7UFJFU0VUX0dBTUVTLm1hcChuID0+IGAKICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLXByZXNldC1idG4gJHtuID09PSAxMDAgPyAnYWN0aXZlJyA6ICcnfSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmNsaWNrPSJBaUxhYi5zZXRHYW1lQ291bnQoJHtufSkiPiR7bn08L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgYCkuam9pbignJyl9CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1jb250cm9sLWdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8bGFiZWw+VGhyZWFkczo8L2xhYmVsPgogICAgICAgICAgICAgICAgICAgIDxzZWxlY3QgaWQ9ImxhYi10aHJlYWRzIj4KICAgICAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiB2YWx1ZT0iMSI+MTwvb3B0aW9uPgogICAgICAgICAgICAgICAgICAgICAgICA8b3B0aW9uIHZhbHVlPSIyIj4yPC9vcHRpb24+CiAgICAgICAgICAgICAgICAgICAgICAgIDxvcHRpb24gdmFsdWU9IjQiIHNlbGVjdGVkPjQ8L29wdGlvbj4KICAgICAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiB2YWx1ZT0iOCI+ODwvb3B0aW9uPgogICAgICAgICAgICAgICAgICAgIDwvc2VsZWN0PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY29udHJvbC1ncm91cCI+CiAgICAgICAgICAgICAgICAgICAgPGxhYmVsPlNlZWQ6PC9sYWJlbD4KICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0idGV4dCIgaWQ9ImxhYi1zZWVkIiBwbGFjZWhvbGRlcj0iUmFuZG9tIiBzdHlsZT0id2lkdGg6MTAwcHgiIC8+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1jb250cm9sLWdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8bGFiZWw+RW5naW5lOjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgPHNlbGVjdCBpZD0ibGFiLWVuZ2luZSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxvcHRpb24gdmFsdWU9ImphdmEiIHNlbGVjdGVkPkZvcmdlIEhldXJpc3RpYzwvb3B0aW9uPgogICAgICAgICAgICAgICAgICAgICAgICA8b3B0aW9uIHZhbHVlPSJkZWVwc2VlayI+RGVlcFNlZWsgTExNPC9vcHRpb24+CiAgICAgICAgICAgICAgICAgICAgPC9zZWxlY3Q+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi1ydW4tYnRuIiBpZD0ibGFiLXJ1bi1idG4iIG9uY2xpY2s9IkFpTGFiLnN0YXJ0QmF0Y2goKSI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9InJ1bi1pY29uIj7ilrY8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9InNwaW5uZXIiPjwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0icnVuLXRleHQiPlJ1biBBSSBTaW1zPC9zcGFuPgogICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgPCEtLSBQcm9ncmVzcyAtLT4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXByb2dyZXNzIiBpZD0ibGFiLXByb2dyZXNzIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1wcm9ncmVzcy1iYXIiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1wcm9ncmVzcy1maWxsIiBpZD0ibGFiLXByb2dyZXNzLWZpbGwiPjwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItcHJvZ3Jlc3MtdGV4dCI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gaWQ9ImxhYi1wcm9ncmVzcy1jb3VudCI+MCAvIDAgZ2FtZXM8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gaWQ9ImxhYi1wcm9ncmVzcy1ldGEiPjwvc3Bhbj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgIDwhLS0gTGl2ZSBMb2cgLS0+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1sb2ciIGlkPSJsYWItbG9nIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1sb2ctaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICA8c3Bhbj5MaXZlIE91dHB1dDwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItbG9nLXRvZ2dsZSIgaWQ9ImxhYi1sb2ctdG9nZ2xlIiBvbmNsaWNrPSJBaUxhYi50b2dnbGVMb2coKSI+4pa8PC9idXR0b24+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxwcmUgY2xhc3M9ImxhYi1sb2ctY29udGVudCIgaWQ9ImxhYi1sb2ctY29udGVudCI+PC9wcmU+CiAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgPCEtLSBSZXN1bHRzIC0tPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItcmVzdWx0cyIgaWQ9ImxhYi1yZXN1bHRzIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1yZXN1bHRzLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgPGgzPlJlc3VsdHM8L2gzPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1yZXN1bHRzLWFjdGlvbnMiPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItZXhwb3J0LWJ0biIgb25jbGljaz0iQWlMYWIuZXhwb3J0SnNvbigpIj5FeHBvcnQgSlNPTjwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItaGlzdG9yeS1idG4iIG9uY2xpY2s9IkFpTGFiLnNob3dIaXN0b3J5KCkiPkhpc3Rvcnk8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXN0YXRzLWNhcmRzIiBpZD0ibGFiLXN0YXRzLWNhcmRzIj48L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1nbG9iYWwtc3RhdHMiIGlkPSJsYWItZ2xvYmFsLXN0YXRzIj48L2Rpdj4KCiAgICAgICAgICAgICAgICA8IS0tIEVuaGFuY2VkIFJlc3VsdHMgU2VjdGlvbnMgLS0+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItc2VjdGlvbi1kaXZpZGVyIj48L2Rpdj4KCiAgICAgICAgICAgICAgICA8IS0tIFRhYiBOYXZpZ2F0aW9uIC0tPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXJlc3VsdHMtdGFicyIgaWQ9ImxhYi1yZXN1bHRzLXRhYnMiPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi10YWIgYWN0aXZlIiBkYXRhLXRhYj0idGFiLWNoYXJ0cyIgb25jbGljaz0iQWlMYWIuc3dpdGNoVGFiKCd0YWItY2hhcnRzJykiPkNoYXJ0czwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi10YWIiIGRhdGEtdGFiPSJ0YWItaDJoIiBvbmNsaWNrPSJBaUxhYi5zd2l0Y2hUYWIoJ3RhYi1oMmgnKSI+SGVhZC10by1IZWFkPC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLXRhYiIgZGF0YS10YWI9InRhYi1jb21iYXQiIG9uY2xpY2s9IkFpTGFiLnN3aXRjaFRhYigndGFiLWNvbWJhdCcpIj5Db21iYXQgU3RhdHM8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItdGFiIiBkYXRhLXRhYj0idGFiLWdhbWVsb2ciIG9uY2xpY2s9IkFpTGFiLnN3aXRjaFRhYigndGFiLWdhbWVsb2cnKSI+R2FtZSBMb2c8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItdGFiIiBkYXRhLXRhYj0idGFiLWFuYWx5dGljcyIgb25jbGljaz0iQWlMYWIuc3dpdGNoVGFiKCd0YWItYW5hbHl0aWNzJykiPkFuYWx5dGljczwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi10YWIiIGRhdGEtdGFiPSJ0YWItdHJlbmRzIiBvbmNsaWNrPSJBaUxhYi5zd2l0Y2hUYWIoJ3RhYi10cmVuZHMnKSI+VHJlbmRzPC9idXR0b24+CiAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICA8IS0tIFRhYjogQ2hhcnRzIC0tPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXRhYi1wYW5lbCBhY3RpdmUiIGlkPSJ0YWItY2hhcnRzIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY2hhcnRzLWdyaWQiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY2hhcnQtYm94Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxoND5XaW4gUmF0ZSBDb21wYXJpc29uPC9oND4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjYW52YXMgaWQ9ImxhYi1jaGFydC13aW5yYXRlIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjI1MCI+PC9jYW52YXM+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY2hhcnQtYm94Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxoND5XaW4gQ29uZGl0aW9uczwvaDQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Y2FudmFzIGlkPSJsYWItY2hhcnQtd2luY29uZCIgd2lkdGg9IjQwMCIgaGVpZ2h0PSIyNTAiPjwvY2FudmFzPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWNoYXJ0LWJveCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aDQ+VHVybiBEaXN0cmlidXRpb248L2g0PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNhbnZhcyBpZD0ibGFiLWNoYXJ0LXR1cm5zIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjI1MCI+PC9jYW52YXM+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY2hhcnQtYm94Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxoND5XaW4gUmF0ZSBPdmVyIFRpbWU8L2g0PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNhbnZhcyBpZD0ibGFiLWNoYXJ0LXdpbnRyZW5kIiB3aWR0aD0iNDAwIiBoZWlnaHQ9IjI1MCI+PC9jYW52YXM+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgPCEtLSBUYWI6IEhlYWQtdG8tSGVhZCAtLT4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10YWItcGFuZWwiIGlkPSJ0YWItaDJoIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGlkPSJsYWItaDJoLW1hdHJpeCI+PC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICA8IS0tIFRhYjogQ29tYmF0IFN0YXRzIC0tPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXRhYi1wYW5lbCIgaWQ9InRhYi1jb21iYXQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgaWQ9ImxhYi1jb21iYXQtc3RhdHMiPjwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgPCEtLSBUYWI6IEdhbWUgTG9nIC0tPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXRhYi1wYW5lbCIgaWQ9InRhYi1nYW1lbG9nIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItZ2FtZWxvZy1jb250cm9scyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJ0ZXh0IiBpZD0ibGFiLWdhbWVsb2ctZmlsdGVyIiBwbGFjZWhvbGRlcj0iRmlsdGVyIGJ5IGRlY2sgbmFtZSwgd2luIGNvbmRpdGlvbi4uLiIgY2xhc3M9ImxhYi1nYW1lbG9nLXNlYXJjaCI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItZ2FtZWxvZy1jb3VudCIgaWQ9ImxhYi1nYW1lbG9nLWNvdW50Ij48L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWdhbWVsb2ctd3JhcHBlciIgaWQ9ImxhYi1nYW1lbG9nLXdyYXBwZXIiPjwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgPCEtLSBUYWI6IEFuYWx5dGljcyAtLT4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10YWItcGFuZWwiIGlkPSJ0YWItYW5hbHl0aWNzIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItYW5hbHl0aWNzLWNvbnRlbnQiIGlkPSJsYWItYW5hbHl0aWNzLWNvbnRlbnQiPgogICAgICAgICAgICAgICAgICAgICAgICA8cCBjbGFzcz0ibGFiLWVtcHR5LWhpbnQiPlNlbGVjdCBkZWNrcyBhbmQgcnVuIGEgYmF0Y2ggdG8gc2VlIHBlci1kZWNrIGFuYWx5dGljcy48L3A+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICA8IS0tIFRhYjogVHJlbmRzIC0tPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXRhYi1wYW5lbCIgaWQ9InRhYi10cmVuZHMiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10cmVuZHMtY29udGVudCIgaWQ9ImxhYi10cmVuZHMtY29udGVudCI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwIGNsYXNzPSJsYWItZW1wdHktaGludCI+UnVuIG11bHRpcGxlIGJhdGNoZXMgdG8gdHJhY2sgd2luIHJhdGUgdHJlbmRzIG92ZXIgdGltZS48L3A+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGNhbnZhcyBpZD0ibGFiLWNoYXJ0LXRyZW5kcyIgd2lkdGg9IjcwMCIgaGVpZ2h0PSIzMDAiIHN0eWxlPSJkaXNwbGF5Om5vbmU7bWFyZ2luLXRvcDoxNnB4OyI+PC9jYW52YXM+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICA8IS0tIEhpc3RvcnkgTW9kYWwgLS0+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1tb2RhbC1vdmVybGF5IiBpZD0ibGFiLWhpc3RvcnktbW9kYWwiPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1vZGFsLWNvbnRlbnQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1tb2RhbC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICA8aDM+QmF0Y2ggSGlzdG9yeTwvaDM+CiAgICAgICAgICAgICAgICAgICAgICAgIDxidXR0b24gY2xhc3M9ImxhYi1tb2RhbC1jbG9zZSIgb25jbGljaz0iQWlMYWIuY2xvc2VIaXN0b3J5KCkiPuKclTwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1oaXN0b3J5LWxpc3QiIGlkPSJsYWItaGlzdG9yeS1saXN0Ij5Mb2FkaW5nLi4uPC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICA8IS0tIHYzOiBDb21tYW5kZXIgTWV0YSBQaWNrZXIgTW9kYWwgLS0+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1tb2RhbC1vdmVybGF5IiBpZD0ibGFiLW1ldGEtbW9kYWwiPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1vZGFsLWNvbnRlbnQgbGFiLW1vZGFsLXdpZGUiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1tb2RhbC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICA8aDM+Q29tbWFuZGVyIE1ldGEg4oCUIEVESFJFQyBBdmVyYWdlIERlY2tzPC9oMz4KICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLW1vZGFsLWNsb3NlIiBvbmNsaWNrPSJBaUxhYi5jbG9zZU1ldGFQaWNrZXIoKSI+4pyVPC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1ldGEtc2VhcmNoLXJvdyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJ0ZXh0IiBpZD0ibGFiLW1ldGEtc2VhcmNoIiBwbGFjZWhvbGRlcj0iU2VhcmNoIGNvbW1hbmRlcnMuLi4iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmlucHV0PSJBaUxhYi5maWx0ZXJNZXRhQ29tbWFuZGVycyh0aGlzLnZhbHVlKSIgYXV0b2NvbXBsZXRlPSJvZmYiIC8+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1ldGEtbGlzdCIgaWQ9ImxhYi1tZXRhLWxpc3QiPkxvYWRpbmcuLi48L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItaW1wb3J0LXN0YXR1cyIgaWQ9ImxhYi1tZXRhLWltcG9ydC1zdGF0dXMiPjwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgPCEtLSB2MzogUHJlY29uIFBpY2tlciBNb2RhbCAtLT4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1vZGFsLW92ZXJsYXkiIGlkPSJsYWItcHJlY29uLW1vZGFsIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1tb2RhbC1jb250ZW50IGxhYi1tb2RhbC13aWRlIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItbW9kYWwtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGgzPlByZWNvbiBEZWNrcyDigJQgT2ZmaWNpYWwgQ29tbWFuZGVyIFByZWNvbnM8L2gzPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItbW9kYWwtY2xvc2UiIG9uY2xpY2s9IkFpTGFiLmNsb3NlUHJlY29uUGlja2VyKCkiPuKclTwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1wcmVjb24tY29udHJvbHMiPgogICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0idGV4dCIgaWQ9ImxhYi1wcmVjb24tc2VhcmNoIiBwbGFjZWhvbGRlcj0iU2VhcmNoIHByZWNvbnMuLi4iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbmlucHV0PSJBaUxhYi5maWx0ZXJQcmVjb25zKHRoaXMudmFsdWUpIiBhdXRvY29tcGxldGU9Im9mZiIgLz4KICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLXByZWNvbi1pbnN0YWxsLWFsbC1idG4iIG9uY2xpY2s9IkFpTGFiLmluc3RhbGxBbGxQcmVjb25zKCkiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGU9Ikluc3RhbGwgYWxsIHByZWNvbiBkZWNrcyB0byBGb3JnZSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbnN0YWxsIEFsbAogICAgICAgICAgICAgICAgICAgICAgICA8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLXByZWNvbi1yZWZyZXNoLWJ0biIgb25jbGljaz0iQWlMYWIucmVmcmVzaFByZWNvbnMoKSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZT0iUmUtZG93bmxvYWQgcHJlY29uIGRhdGFiYXNlIGZyb20gR2l0SHViIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZnJlc2ggSW5kZXgKICAgICAgICAgICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXByZWNvbi1ncmlkIiBpZD0ibGFiLXByZWNvbi1ncmlkIj5Mb2FkaW5nLi4uPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWltcG9ydC1zdGF0dXMiIGlkPSJsYWItcHJlY29uLXN0YXR1cyI+PC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICA8IS0tIHYzOiBUZXh0IEltcG9ydCBNb2RhbCAtLT4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1vZGFsLW92ZXJsYXkiIGlkPSJsYWItdGV4dC1tb2RhbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItbW9kYWwtY29udGVudCBsYWItbW9kYWwtd2lkZSI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLW1vZGFsLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxoMz5JbXBvcnQgZnJvbSBDYXJkIExpc3Q8L2gzPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItbW9kYWwtY2xvc2UiIG9uY2xpY2s9IkFpTGFiLmNsb3NlVGV4dEltcG9ydCgpIj7inJU8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItdGV4dC1pbXBvcnQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10ZXh0LWltcG9ydC1oaW50Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIFBhc3RlIGEgY2FyZCBsaXN0IChvbmUgY2FyZCBwZXIgbGluZSkuIFVzZSA8Y29kZT4xIENhcmQgTmFtZTwvY29kZT4gb3IganVzdCA8Y29kZT5DYXJkIE5hbWU8L2NvZGU+LgogICAgICAgICAgICAgICAgICAgICAgICAgICAgQWRkIGEgPGNvZGU+W0NvbW1hbmRlcl08L2NvZGU+IHNlY3Rpb24gaGVhZGVyIGFib3ZlIHRoZSBjb21tYW5kZXIsIG9yIGVudGVyIGl0IGJlbG93LgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRleHRhcmVhIGlkPSJsYWItdGV4dC1pbXBvcnQtYXJlYSIgcm93cz0iMTIiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcj0iW0NvbW1hbmRlcl0KMSBFZGdhciBNYXJrb3YKCltNYWluXQoxIFNvbCBSaW5nCjEgQXJjYW5lIFNpZ25ldAoxIENvbW1hbmQgVG93ZXIKLi4uIj48L3RleHRhcmVhPgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItdGV4dC1pbXBvcnQtb3B0aW9ucyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8bGFiZWw+Q29tbWFuZGVyIG92ZXJyaWRlOjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0idGV4dCIgaWQ9ImxhYi10ZXh0LWNvbW1hbmRlci1vdmVycmlkZSIgcGxhY2Vob2xkZXI9IihhdXRvLWRldGVjdCBmcm9tIGxpc3QpIiAvPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLWltcG9ydC1zdWJtaXQtYnRuIiBvbmNsaWNrPSJBaUxhYi5pbXBvcnRGcm9tVGV4dCgpIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydCBEZWNrCiAgICAgICAgICAgICAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1pbXBvcnQtc3RhdHVzIiBpZD0ibGFiLXRleHQtaW1wb3J0LXN0YXR1cyI+PC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgICAgYDsKICAgIH0KCiAgICBmdW5jdGlvbiB1cGRhdGVCYWNrZW5kSW5kaWNhdG9yKCkgewogICAgICAgIGNvbnN0IGVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1iYWNrZW5kLXN0YXR1cycpOwogICAgICAgIGlmICghZWwpIHJldHVybjsKICAgICAgICBpZiAoYmFja2VuZEF2YWlsYWJsZSkgewogICAgICAgICAgICBlbC5jbGFzc0xpc3QuYWRkKCdjb25uZWN0ZWQnKTsKICAgICAgICAgICAgZWwuY2xhc3NMaXN0LnJlbW92ZSgnZGlzY29ubmVjdGVkJyk7CiAgICAgICAgICAgIGVsLnF1ZXJ5U2VsZWN0b3IoJy5zdGF0dXMtdGV4dCcpLnRleHRDb250ZW50ID0gJ0JhY2tlbmQgQ29ubmVjdGVkJzsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBlbC5jbGFzc0xpc3QuYWRkKCdkaXNjb25uZWN0ZWQnKTsKICAgICAgICAgICAgZWwuY2xhc3NMaXN0LnJlbW92ZSgnY29ubmVjdGVkJyk7CiAgICAgICAgICAgIGVsLnF1ZXJ5U2VsZWN0b3IoJy5zdGF0dXMtdGV4dCcpLnRleHRDb250ZW50ID0gJ0RlbW8gTW9kZSc7CiAgICAgICAgfQogICAgfQoKICAgIC8vIOKUgOKUgCBEZWNrIE1hbmFnZW1lbnQg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgYXN5bmMgZnVuY3Rpb24gbG9hZEF2YWlsYWJsZURlY2tzKCkgewogICAgICAgIGxldCBkZWNrcyA9IFtdOwoKICAgICAgICBpZiAoYmFja2VuZEF2YWlsYWJsZSkgewogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvZGVja3NgKTsKICAgICAgICAgICAgICAgIGlmIChyZXMub2spIHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKICAgICAgICAgICAgICAgICAgICBkZWNrcyA9IChkYXRhLmRlY2tzIHx8IFtdKS5tYXAoZCA9PiAoewogICAgICAgICAgICAgICAgICAgICAgICBuYW1lOiBkLm5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogZC5jb21tYW5kZXIgfHwgZC5uYW1lLnJlcGxhY2UoL18vZywgJyAnKSwKICAgICAgICAgICAgICAgICAgICB9KSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICAgICAgY29uc29sZS53YXJuKCdbQUkgTGFiXSBGYWlsZWQgdG8gbG9hZCBkZWNrcyBmcm9tIGJhY2tlbmQ6JywgZXJyKTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8gRmFsbGJhY2sgdG8gc2FtcGxlIGRlY2tzIGlmIGJhY2tlbmQgcmV0dXJuZWQgbm90aGluZwogICAgICAgIGlmIChkZWNrcy5sZW5ndGggPT09IDApIHsKICAgICAgICAgICAgZGVja3MgPSBnZXRTYW1wbGVEZWNrcygpOwogICAgICAgIH0KCiAgICAgICAgYXZhaWxhYmxlRGVja3MgPSBkZWNrczsKICAgICAgICByZWZyZXNoRGVja1NlbGVjdG9ycygpOwogICAgfQoKICAgIGZ1bmN0aW9uIHJlZnJlc2hEZWNrU2VsZWN0b3JzKCkgewogICAgICAgIGZvciAobGV0IHNlYXQgPSAwOyBzZWF0IDwgNDsgc2VhdCsrKSB7CiAgICAgICAgICAgIGNvbnN0IHNlbGVjdCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGBsYWItZGVjay1zZWxlY3QtJHtzZWF0fWApOwogICAgICAgICAgICBpZiAoIXNlbGVjdCkgY29udGludWU7CiAgICAgICAgICAgIGNvbnN0IHByZXYgPSBzZWxlY3QudmFsdWU7CiAgICAgICAgICAgIHNlbGVjdC5pbm5lckhUTUwgPSAnPG9wdGlvbiB2YWx1ZT0iIj7igJQgU2VsZWN0IERlY2sg4oCUPC9vcHRpb24+JzsKCiAgICAgICAgICAgIC8vIEdyb3VwOiBpbXBvcnRlZCBkZWNrcyBmaXJzdAogICAgICAgICAgICBjb25zdCBpbXBvcnRlZCA9IGF2YWlsYWJsZURlY2tzLmZpbHRlcihkID0+IGltcG9ydGVkRGVja01ldGFbZC5uYW1lXSk7CiAgICAgICAgICAgIGNvbnN0IGxvY2FsID0gYXZhaWxhYmxlRGVja3MuZmlsdGVyKGQgPT4gIWltcG9ydGVkRGVja01ldGFbZC5uYW1lXSk7CgogICAgICAgICAgICBpZiAoaW1wb3J0ZWQubGVuZ3RoID4gMCkgewogICAgICAgICAgICAgICAgY29uc3QgZ3JvdXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdvcHRncm91cCcpOwogICAgICAgICAgICAgICAgZ3JvdXAubGFiZWwgPSAnSW1wb3J0ZWQgRGVja3MnOwogICAgICAgICAgICAgICAgaW1wb3J0ZWQuZm9yRWFjaChkZWNrID0+IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBvcHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdvcHRpb24nKTsKICAgICAgICAgICAgICAgICAgICBvcHQudmFsdWUgPSBkZWNrLm5hbWU7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgbWV0YSA9IGltcG9ydGVkRGVja01ldGFbZGVjay5uYW1lXTsKICAgICAgICAgICAgICAgICAgICBjb25zdCBzcmNUYWcgPSBtZXRhID8gYCBbJHttZXRhLnNvdXJjZX1dYCA6ICcnOwogICAgICAgICAgICAgICAgICAgIG9wdC50ZXh0Q29udGVudCA9IGRlY2submFtZSArIHNyY1RhZzsKICAgICAgICAgICAgICAgICAgICBvcHQuZGF0YXNldC5jb21tYW5kZXIgPSBkZWNrLmNvbW1hbmRlciB8fCAnJzsKICAgICAgICAgICAgICAgICAgICBncm91cC5hcHBlbmRDaGlsZChvcHQpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICBzZWxlY3QuYXBwZW5kQ2hpbGQoZ3JvdXApOwogICAgICAgICAgICB9CgogICAgICAgICAgICBpZiAobG9jYWwubGVuZ3RoID4gMCkgewogICAgICAgICAgICAgICAgY29uc3QgZ3JvdXAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdvcHRncm91cCcpOwogICAgICAgICAgICAgICAgZ3JvdXAubGFiZWwgPSAnTG9jYWwgRGVja3MnOwogICAgICAgICAgICAgICAgbG9jYWwuZm9yRWFjaChkZWNrID0+IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBvcHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdvcHRpb24nKTsKICAgICAgICAgICAgICAgICAgICBvcHQudmFsdWUgPSBkZWNrLm5hbWU7CiAgICAgICAgICAgICAgICAgICAgb3B0LnRleHRDb250ZW50ID0gZGVjay5uYW1lOwogICAgICAgICAgICAgICAgICAgIG9wdC5kYXRhc2V0LmNvbW1hbmRlciA9IGRlY2suY29tbWFuZGVyIHx8ICcnOwogICAgICAgICAgICAgICAgICAgIGdyb3VwLmFwcGVuZENoaWxkKG9wdCk7CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIHNlbGVjdC5hcHBlbmRDaGlsZChncm91cCk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIC8vIFJlc3RvcmUgcHJldmlvdXMgc2VsZWN0aW9uCiAgICAgICAgICAgIGlmIChwcmV2KSB7CiAgICAgICAgICAgICAgICBzZWxlY3QudmFsdWUgPSBwcmV2OwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQoKICAgIGZ1bmN0aW9uIGdldFNhbXBsZURlY2tzKCkgewogICAgICAgIHJldHVybiBbCiAgICAgICAgICAgIHsgbmFtZTogJ0F0cmF4YV9TdXBlcmZyaWVuZHMnLCBjb21tYW5kZXI6ICJBdHJheGEsIFByYWV0b3JzJyBWb2ljZSIgfSwKICAgICAgICAgICAgeyBuYW1lOiAnS29ydm9sZF9BcmlzdG9jcmF0cycsIGNvbW1hbmRlcjogJ0tvcnZvbGQsIEZhZS1DdXJzZWQgS2luZycgfSwKICAgICAgICAgICAgeyBuYW1lOiAnTXVsZHJvdGhhX1ZhbHVlJywgY29tbWFuZGVyOiAnTXVsZHJvdGhhLCB0aGUgR3JhdmV0aWRlJyB9LAogICAgICAgICAgICB7IG5hbWU6ICdBbmdlbHMnLCBjb21tYW5kZXI6ICdBbmdlbHMnIH0sCiAgICAgICAgICAgIHsgbmFtZTogJ0VkZ2FyIE1hcmtvdicsIGNvbW1hbmRlcjogJ0VkZ2FyIE1hcmtvdicgfSwKICAgICAgICAgICAgeyBuYW1lOiAnR3JpbWdyaW4nLCBjb21tYW5kZXI6ICdHcmltZ3JpbiwgQ29ycHNlLUJvcm4nIH0sCiAgICAgICAgICAgIHsgbmFtZTogJ1h5cmlzJywgY29tbWFuZGVyOiAnWHlyaXMsIHRoZSBXcml0aGluZyBTdG9ybScgfSwKICAgICAgICAgICAgeyBuYW1lOiAnQ29udHJvbC1UYWxyYW5kJywgY29tbWFuZGVyOiAnVGFscmFuZCwgU2t5IFN1bW1vbmVyJyB9LAogICAgICAgICAgICB7IG5hbWU6ICdBc2hsaW5nIDIuMCcsIGNvbW1hbmRlcjogJ0FzaGxpbmcgdGhlIFBpbGdyaW0nIH0sCiAgICAgICAgXTsKICAgIH0KCiAgICBmdW5jdGlvbiBzZWxlY3REZWNrKHNlYXQsIGRlY2tOYW1lKSB7CiAgICAgICAgc2VsZWN0ZWREZWNrc1tzZWF0XSA9IGRlY2tOYW1lIHx8IG51bGw7CgogICAgICAgIGNvbnN0IHNsb3QgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChgbGFiLXNsb3QtJHtzZWF0fWApOwogICAgICAgIGNvbnN0IGNtZEVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoYGxhYi1jb21tYW5kZXItJHtzZWF0fWApOwogICAgICAgIGNvbnN0IHNlbGVjdCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGBsYWItZGVjay1zZWxlY3QtJHtzZWF0fWApOwogICAgICAgIGNvbnN0IGJhZGdlRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChgbGFiLXNvdXJjZS1iYWRnZS0ke3NlYXR9YCk7CgogICAgICAgIGlmIChkZWNrTmFtZSkgewogICAgICAgICAgICBzbG90LmNsYXNzTGlzdC5hZGQoJ2ZpbGxlZCcpOwogICAgICAgICAgICBjb25zdCBvcHQgPSBzZWxlY3Quc2VsZWN0ZWRPcHRpb25zWzBdOwogICAgICAgICAgICBjbWRFbC50ZXh0Q29udGVudCA9IG9wdD8uZGF0YXNldC5jb21tYW5kZXIgfHwgJyc7CgogICAgICAgICAgICAvLyBTaG93IHNvdXJjZSBiYWRnZSBpZiBpbXBvcnRlZAogICAgICAgICAgICBjb25zdCBtZXRhID0gaW1wb3J0ZWREZWNrTWV0YVtkZWNrTmFtZV07CiAgICAgICAgICAgIGlmIChtZXRhICYmIGJhZGdlRWwpIHsKICAgICAgICAgICAgICAgIGJhZGdlRWwuaW5uZXJIVE1MID0gcmVuZGVyU291cmNlQmFkZ2UobWV0YS5zb3VyY2UsIG1ldGEuc291cmNlVXJsKTsKICAgICAgICAgICAgICAgIGJhZGdlRWwuc3R5bGUuZGlzcGxheSA9ICdibG9jayc7CiAgICAgICAgICAgIH0gZWxzZSBpZiAoYmFkZ2VFbCkgewogICAgICAgICAgICAgICAgYmFkZ2VFbC5pbm5lckhUTUwgPSAnJzsKICAgICAgICAgICAgICAgIGJhZGdlRWwuc3R5bGUuZGlzcGxheSA9ICdub25lJzsKICAgICAgICAgICAgfQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHNsb3QuY2xhc3NMaXN0LnJlbW92ZSgnZmlsbGVkJyk7CiAgICAgICAgICAgIGNtZEVsLnRleHRDb250ZW50ID0gJyc7CiAgICAgICAgICAgIGlmIChiYWRnZUVsKSB7CiAgICAgICAgICAgICAgICBiYWRnZUVsLmlubmVySFRNTCA9ICcnOwogICAgICAgICAgICAgICAgYmFkZ2VFbC5zdHlsZS5kaXNwbGF5ID0gJ25vbmUnOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQoKICAgIGZ1bmN0aW9uIHJlbmRlclNvdXJjZUJhZGdlKHNvdXJjZSwgc291cmNlVXJsKSB7CiAgICAgICAgY29uc3Qgc291cmNlTGFiZWxzID0gewogICAgICAgICAgICAnQXJjaGlkZWt0JzogeyBpY29uOiAn8J+Pl++4jycsIGNsczogJ2JhZGdlLWFyY2hpZGVrdCcgfSwKICAgICAgICAgICAgJ0VESFJFQyBBdmVyYWdlJzogeyBpY29uOiAn8J+TiicsIGNsczogJ2JhZGdlLWVkaHJlYycgfSwKICAgICAgICAgICAgJ1RleHQgSW1wb3J0JzogeyBpY29uOiAn8J+TiycsIGNsczogJ2JhZGdlLXRleHQnIH0sCiAgICAgICAgICAgICdQcmVjb24nOiB7IGljb246ICfwn460JywgY2xzOiAnYmFkZ2UtcHJlY29uJyB9LAogICAgICAgIH07CiAgICAgICAgY29uc3QgaW5mbyA9IHNvdXJjZUxhYmVsc1tzb3VyY2VdIHx8IHsgaWNvbjogJ/Cfk6YnLCBjbHM6ICdiYWRnZS1kZWZhdWx0JyB9OwogICAgICAgIGNvbnN0IGxpbmtIdG1sID0gc291cmNlVXJsCiAgICAgICAgICAgID8gYDxhIGhyZWY9IiR7c291cmNlVXJsfSIgdGFyZ2V0PSJfYmxhbmsiIHJlbD0ibm9vcGVuZXIiIGNsYXNzPSJzb3VyY2UtbGluayI+JHtzb3VyY2V9PC9hPmAKICAgICAgICAgICAgOiBgPHNwYW4+JHtzb3VyY2V9PC9zcGFuPmA7CiAgICAgICAgcmV0dXJuIGA8c3BhbiBjbGFzcz0ibGFiLXNvdXJjZS1iYWRnZSAke2luZm8uY2xzfSI+JHtpbmZvLmljb259ICR7bGlua0h0bWx9PC9zcGFuPmA7CiAgICB9CgogICAgLy8g4pSA4pSAIHYzOiBVUkwgSW1wb3J0IOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGZ1bmN0aW9uIHNob3dVcmxJbXBvcnQoKSB7CiAgICAgICAgY29uc3QgcGFuZWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXVybC1pbXBvcnQnKTsKICAgICAgICBpZiAocGFuZWwpIHsKICAgICAgICAgICAgcGFuZWwuY2xhc3NMaXN0LnRvZ2dsZSgnYWN0aXZlJyk7CiAgICAgICAgICAgIGlmIChwYW5lbC5jbGFzc0xpc3QuY29udGFpbnMoJ2FjdGl2ZScpKSB7CiAgICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWltcG9ydC11cmwtaW5wdXQnKT8uZm9jdXMoKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KCiAgICBmdW5jdGlvbiBoaWRlVXJsSW1wb3J0KCkgewogICAgICAgIGNvbnN0IHBhbmVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi11cmwtaW1wb3J0Jyk7CiAgICAgICAgaWYgKHBhbmVsKSBwYW5lbC5jbGFzc0xpc3QucmVtb3ZlKCdhY3RpdmUnKTsKICAgIH0KCiAgICBhc3luYyBmdW5jdGlvbiBpbXBvcnRGcm9tVXJsKCkgewogICAgICAgIGNvbnN0IGlucHV0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1pbXBvcnQtdXJsLWlucHV0Jyk7CiAgICAgICAgY29uc3Qgc3RhdHVzRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXVybC1pbXBvcnQtc3RhdHVzJyk7CiAgICAgICAgY29uc3QgYnRuID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi11cmwtaW1wb3J0LWJ0bicpOwogICAgICAgIGNvbnN0IHVybCA9IGlucHV0Py52YWx1ZS50cmltKCk7CgogICAgICAgIGlmICghdXJsKSB7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsICdQbGVhc2UgZW50ZXIgYSBVUkwuJywgJ2Vycm9yJyk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIGlmICghYmFja2VuZEF2YWlsYWJsZSkgewogICAgICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCAnVVJMIGltcG9ydCByZXF1aXJlcyBhIHJ1bm5pbmcgYmFja2VuZC4nLCAnZXJyb3InKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgYnRuLmRpc2FibGVkID0gdHJ1ZTsKICAgICAgICBidG4udGV4dENvbnRlbnQgPSAnSW1wb3J0aW5nLi4uJzsKICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCAnRmV0Y2hpbmcgZGVjayBmcm9tIFVSTC4uLicsICdsb2FkaW5nJyk7CgogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGAke0FQSV9CQVNFfS9hcGkvbGFiL2ltcG9ydC91cmxgLCB7CiAgICAgICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LAogICAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyB1cmwgfSksCiAgICAgICAgICAgIH0pOwogICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKCiAgICAgICAgICAgIGlmICghcmVzLm9rKSB7CiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZGF0YS5kZXRhaWwgfHwgJ0ltcG9ydCBmYWlsZWQnKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gVHJhY2sgaW1wb3J0ZWQgZGVjayBtZXRhZGF0YQogICAgICAgICAgICBpbXBvcnRlZERlY2tNZXRhW2RhdGEuZGNrRmlsZV0gPSB7CiAgICAgICAgICAgICAgICBzb3VyY2U6IGRhdGEuc291cmNlLAogICAgICAgICAgICAgICAgc291cmNlVXJsOiBkYXRhLnNvdXJjZVVybCwKICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogZGF0YS5jb21tYW5kZXIsCiAgICAgICAgICAgICAgICBhcmNoZXR5cGU6IGRhdGEuYXJjaGV0eXBlLAogICAgICAgICAgICAgICAgY29sb3JJZGVudGl0eTogZGF0YS5jb2xvcklkZW50aXR5IHx8IFtdLAogICAgICAgICAgICB9OwoKICAgICAgICAgICAgLy8gQWRkIHRvIGF2YWlsYWJsZSBkZWNrcyBpZiBub3QgYWxyZWFkeSBwcmVzZW50CiAgICAgICAgICAgIGlmICghYXZhaWxhYmxlRGVja3MuZmluZChkID0+IGQubmFtZSA9PT0gZGF0YS5kY2tGaWxlKSkgewogICAgICAgICAgICAgICAgYXZhaWxhYmxlRGVja3MudW5zaGlmdCh7CiAgICAgICAgICAgICAgICAgICAgbmFtZTogZGF0YS5kY2tGaWxlLAogICAgICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogZGF0YS5jb21tYW5kZXIsCiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIHJlZnJlc2hEZWNrU2VsZWN0b3JzKCk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsCiAgICAgICAgICAgICAgICBgSW1wb3J0ZWQgIiR7ZGF0YS5kZWNrTmFtZX0iICgke2RhdGEudG90YWxDYXJkc30gY2FyZHMpIGZyb20gJHtkYXRhLnNvdXJjZX1gLAogICAgICAgICAgICAgICAgJ3N1Y2Nlc3MnCiAgICAgICAgICAgICk7CiAgICAgICAgICAgIGlucHV0LnZhbHVlID0gJyc7CgogICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCBgRXJyb3I6ICR7ZXJyLm1lc3NhZ2V9YCwgJ2Vycm9yJyk7CiAgICAgICAgfSBmaW5hbGx5IHsKICAgICAgICAgICAgYnRuLmRpc2FibGVkID0gZmFsc2U7CiAgICAgICAgICAgIGJ0bi50ZXh0Q29udGVudCA9ICdJbXBvcnQnOwogICAgICAgIH0KICAgIH0KCiAgICAvLyDilIDilIAgdjM6IENvbW1hbmRlciBNZXRhIFBpY2tlciDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBhc3luYyBmdW5jdGlvbiBsb2FkTWV0YUNvbW1hbmRlcnMoKSB7CiAgICAgICAgaWYgKCFiYWNrZW5kQXZhaWxhYmxlKSByZXR1cm47CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvbWV0YS9jb21tYW5kZXJzYCk7CiAgICAgICAgICAgIGlmIChyZXMub2spIHsKICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpOwogICAgICAgICAgICAgICAgbWV0YUNvbW1hbmRlcnMgPSBkYXRhLmNvbW1hbmRlcnMgfHwgW107CiAgICAgICAgICAgIH0KICAgICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAgICAgICAgY29uc29sZS53YXJuKCdbQUkgTGFiXSBGYWlsZWQgdG8gbG9hZCBtZXRhIGNvbW1hbmRlcnM6JywgZXJyKTsKICAgICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gc2hvd01ldGFQaWNrZXIoKSB7CiAgICAgICAgY29uc3QgbW9kYWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLW1ldGEtbW9kYWwnKTsKICAgICAgICBpZiAobW9kYWwpIHsKICAgICAgICAgICAgbW9kYWwuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7CiAgICAgICAgICAgIHJlbmRlck1ldGFMaXN0KG1ldGFDb21tYW5kZXJzKTsKICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1tZXRhLXNlYXJjaCcpPy5mb2N1cygpOwogICAgICAgIH0KICAgIH0KCiAgICBmdW5jdGlvbiBjbG9zZU1ldGFQaWNrZXIoKSB7CiAgICAgICAgY29uc3QgbW9kYWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLW1ldGEtbW9kYWwnKTsKICAgICAgICBpZiAobW9kYWwpIG1vZGFsLmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpOwogICAgICAgIGNvbnN0IHN0YXR1c0VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1tZXRhLWltcG9ydC1zdGF0dXMnKTsKICAgICAgICBpZiAoc3RhdHVzRWwpIHN0YXR1c0VsLmlubmVySFRNTCA9ICcnOwogICAgfQoKICAgIGZ1bmN0aW9uIGZpbHRlck1ldGFDb21tYW5kZXJzKHF1ZXJ5KSB7CiAgICAgICAgaWYgKCFxdWVyeSkgewogICAgICAgICAgICByZW5kZXJNZXRhTGlzdChtZXRhQ29tbWFuZGVycyk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CiAgICAgICAgY29uc3QgcSA9IHF1ZXJ5LnRvTG93ZXJDYXNlKCk7CiAgICAgICAgY29uc3QgZmlsdGVyZWQgPSBtZXRhQ29tbWFuZGVycy5maWx0ZXIoYyA9PiBjLm5hbWUudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKSk7CiAgICAgICAgcmVuZGVyTWV0YUxpc3QoZmlsdGVyZWQpOwogICAgfQoKICAgIGZ1bmN0aW9uIHJlbmRlck1ldGFMaXN0KGNvbW1hbmRlcnMpIHsKICAgICAgICBjb25zdCBsaXN0RWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLW1ldGEtbGlzdCcpOwogICAgICAgIGlmICghbGlzdEVsKSByZXR1cm47CgogICAgICAgIGlmIChjb21tYW5kZXJzLmxlbmd0aCA9PT0gMCkgewogICAgICAgICAgICBsaXN0RWwuaW5uZXJIVE1MID0gJzxkaXYgY2xhc3M9ImxhYi1tZXRhLWVtcHR5Ij5ObyBjb21tYW5kZXJzIGZvdW5kLjwvZGl2Pic7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIGxpc3RFbC5pbm5lckhUTUwgPSBjb21tYW5kZXJzLm1hcChjID0+IHsKICAgICAgICAgICAgY29uc3QgY29sb3JzID0gKGMuY29sb3JJZGVudGl0eSB8fCBbXSkubWFwKGNvbCA9PiB7CiAgICAgICAgICAgICAgICBjb25zdCBjaSA9IENPTE9SX01BUFtjb2xdOwogICAgICAgICAgICAgICAgcmV0dXJuIGNpID8gYDxzcGFuIGNsYXNzPSJtYW5hLXBpcCIgc3R5bGU9ImJhY2tncm91bmQ6JHtjaS5iZ307Y29sb3I6JHtjaS5jb2xvcn0iPiR7Y2kubGFiZWx9PC9zcGFuPmAgOiAnJzsKICAgICAgICAgICAgfSkuam9pbignJyk7CgogICAgICAgICAgICBjb25zdCBhcmNoQmFkZ2UgPSBjLmFyY2hldHlwZQogICAgICAgICAgICAgICAgPyBgPHNwYW4gY2xhc3M9ImxhYi1hcmNoLWJhZGdlIGFyY2gtJHtjLmFyY2hldHlwZX0iPiR7Yy5hcmNoZXR5cGV9PC9zcGFuPmAKICAgICAgICAgICAgICAgIDogJyc7CgogICAgICAgICAgICByZXR1cm4gYAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItbWV0YS1jYXJkIiBvbmNsaWNrPSJBaUxhYi5mZXRjaE1ldGFEZWNrKCcke2VzY2FwZUF0dHIoYy5uYW1lKX0nKSI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtZXRhLWNhcmQtbGVmdCI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibWV0YS1jYXJkLW5hbWUiPiR7ZXNjYXBlSHRtbChjLm5hbWUpfTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1ldGEtY2FyZC1pbmZvIj4ke2NvbG9yc30gJHthcmNoQmFkZ2V9PC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1ldGEtY2FyZC1yaWdodCI+CiAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibGFiLW1ldGEtZmV0Y2gtYnRuIiB0aXRsZT0iRmV0Y2ggRURIUkVDIGF2ZXJhZ2UgZGVjayI+CiAgICAgICAgICAgICAgICAgICAgICAgIEZldGNoIERlY2sKICAgICAgICAgICAgICAgICAgICA8L2J1dHRvbj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj5gOwogICAgICAgIH0pLmpvaW4oJycpOwogICAgfQoKICAgIGFzeW5jIGZ1bmN0aW9uIGZldGNoTWV0YURlY2soY29tbWFuZGVyTmFtZSkgewogICAgICAgIGNvbnN0IHN0YXR1c0VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1tZXRhLWltcG9ydC1zdGF0dXMnKTsKCiAgICAgICAgaWYgKCFiYWNrZW5kQXZhaWxhYmxlKSB7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsICdNZXRhIGZldGNoIHJlcXVpcmVzIGEgcnVubmluZyBiYWNrZW5kLicsICdlcnJvcicpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQoKICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCBgRmV0Y2hpbmcgRURIUkVDIGF2ZXJhZ2UgZGVjayBmb3IgJHtjb21tYW5kZXJOYW1lfS4uLmAsICdsb2FkaW5nJyk7CgogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGAke0FQSV9CQVNFfS9hcGkvbGFiL21ldGEvZmV0Y2hgLCB7CiAgICAgICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LAogICAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBjb21tYW5kZXI6IGNvbW1hbmRlck5hbWUgfSksCiAgICAgICAgICAgIH0pOwogICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKCiAgICAgICAgICAgIGlmICghcmVzLm9rKSB7CiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZGF0YS5kZXRhaWwgfHwgJ0ZldGNoIGZhaWxlZCcpOwogICAgICAgICAgICB9CgogICAgICAgICAgICBpbXBvcnRlZERlY2tNZXRhW2RhdGEuZGNrRmlsZV0gPSB7CiAgICAgICAgICAgICAgICBzb3VyY2U6ICdFREhSRUMgQXZlcmFnZScsCiAgICAgICAgICAgICAgICBzb3VyY2VVcmw6IGRhdGEuc291cmNlVXJsLAogICAgICAgICAgICAgICAgY29tbWFuZGVyOiBkYXRhLmNvbW1hbmRlciwKICAgICAgICAgICAgICAgIGNvbG9ySWRlbnRpdHk6IGRhdGEuY29sb3JJZGVudGl0eSB8fCBbXSwKICAgICAgICAgICAgICAgIHNhbXBsZVNpemU6IGRhdGEuc2FtcGxlU2l6ZSwKICAgICAgICAgICAgfTsKCiAgICAgICAgICAgIGlmICghYXZhaWxhYmxlRGVja3MuZmluZChkID0+IGQubmFtZSA9PT0gZGF0YS5kY2tGaWxlKSkgewogICAgICAgICAgICAgICAgYXZhaWxhYmxlRGVja3MudW5zaGlmdCh7CiAgICAgICAgICAgICAgICAgICAgbmFtZTogZGF0YS5kY2tGaWxlLAogICAgICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogZGF0YS5jb21tYW5kZXIsCiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIHJlZnJlc2hEZWNrU2VsZWN0b3JzKCk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGNvbnN0IHNhbXBsZUluZm8gPSBkYXRhLnNhbXBsZVNpemUgPyBgICgke2RhdGEuc2FtcGxlU2l6ZX0gZGVja3Mgc2FtcGxlZClgIDogJyc7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsCiAgICAgICAgICAgICAgICBgSW1wb3J0ZWQgIiR7ZGF0YS5kZWNrTmFtZX0iICgke2RhdGEudG90YWxDYXJkc30gY2FyZHMpJHtzYW1wbGVJbmZvfWAsCiAgICAgICAgICAgICAgICAnc3VjY2VzcycKICAgICAgICAgICAgKTsKCiAgICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsIGBFcnJvcjogJHtlcnIubWVzc2FnZX1gLCAnZXJyb3InKTsKICAgICAgICB9CiAgICB9CgogICAgLy8g4pSA4pSAIHYzOiBQcmVjb24gRGVja3Mg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgYXN5bmMgZnVuY3Rpb24gbG9hZFByZWNvbnMoKSB7CiAgICAgICAgaWYgKCFiYWNrZW5kQXZhaWxhYmxlKSByZXR1cm47CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvcHJlY29uc2ApOwogICAgICAgICAgICBpZiAocmVzLm9rKSB7CiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKICAgICAgICAgICAgICAgIHByZWNvbkRlY2tzID0gZGF0YS5wcmVjb25zIHx8IFtdOwogICAgICAgICAgICB9CiAgICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgICAgIGNvbnNvbGUud2FybignW0FJIExhYl0gRmFpbGVkIHRvIGxvYWQgcHJlY29uczonLCBlcnIpOwogICAgICAgIH0KICAgIH0KCiAgICBmdW5jdGlvbiBzaG93UHJlY29uUGlja2VyKCkgewogICAgICAgIGNvbnN0IG1vZGFsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1wcmVjb24tbW9kYWwnKTsKICAgICAgICBpZiAobW9kYWwpIHsKICAgICAgICAgICAgbW9kYWwuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7CiAgICAgICAgICAgIHJlbmRlclByZWNvbkdyaWQocHJlY29uRGVja3MpOwogICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXByZWNvbi1zZWFyY2gnKT8uZm9jdXMoKTsKICAgICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gY2xvc2VQcmVjb25QaWNrZXIoKSB7CiAgICAgICAgY29uc3QgbW9kYWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXByZWNvbi1tb2RhbCcpOwogICAgICAgIGlmIChtb2RhbCkgbW9kYWwuY2xhc3NMaXN0LnJlbW92ZSgnYWN0aXZlJyk7CiAgICAgICAgY29uc3Qgc3RhdHVzRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXByZWNvbi1zdGF0dXMnKTsKICAgICAgICBpZiAoc3RhdHVzRWwpIHN0YXR1c0VsLmlubmVySFRNTCA9ICcnOwogICAgfQoKICAgIGZ1bmN0aW9uIGZpbHRlclByZWNvbnMocXVlcnkpIHsKICAgICAgICBpZiAoIXF1ZXJ5KSB7CiAgICAgICAgICAgIHJlbmRlclByZWNvbkdyaWQocHJlY29uRGVja3MpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQogICAgICAgIGNvbnN0IHEgPSBxdWVyeS50b0xvd2VyQ2FzZSgpOwogICAgICAgIGNvbnN0IGZpbHRlcmVkID0gcHJlY29uRGVja3MuZmlsdGVyKHAgPT4KICAgICAgICAgICAgcC5uYW1lLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMocSkgfHwKICAgICAgICAgICAgcC5jb21tYW5kZXIudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKSB8fAogICAgICAgICAgICBwLnNldC50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKHEpIHx8CiAgICAgICAgICAgIChwLnRoZW1lICYmIHAudGhlbWUudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKSkKICAgICAgICApOwogICAgICAgIHJlbmRlclByZWNvbkdyaWQoZmlsdGVyZWQpOwogICAgfQoKICAgIGZ1bmN0aW9uIHJlbmRlclByZWNvbkdyaWQocHJlY29ucykgewogICAgICAgIGNvbnN0IGdyaWRFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItcHJlY29uLWdyaWQnKTsKICAgICAgICBpZiAoIWdyaWRFbCkgcmV0dXJuOwoKICAgICAgICBpZiAocHJlY29ucy5sZW5ndGggPT09IDApIHsKICAgICAgICAgICAgZ3JpZEVsLmlubmVySFRNTCA9ICc8ZGl2IGNsYXNzPSJsYWItbWV0YS1lbXB0eSI+Tm8gcHJlY29uIGRlY2tzIGZvdW5kLjwvZGl2Pic7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIGdyaWRFbC5pbm5lckhUTUwgPSBwcmVjb25zLm1hcChwID0+IHsKICAgICAgICAgICAgY29uc3QgY29sb3JzID0gKHAuY29sb3JzIHx8IFtdKS5tYXAoY29sID0+IHsKICAgICAgICAgICAgICAgIGlmIChjb2wgPT09ICdDJykgcmV0dXJuICc8c3BhbiBjbGFzcz0ibWFuYS1waXAiIHN0eWxlPSJiYWNrZ3JvdW5kOiM5YzhlODI7Y29sb3I6I2ZmZiI+Qzwvc3Bhbj4nOwogICAgICAgICAgICAgICAgY29uc3QgY2kgPSBDT0xPUl9NQVBbY29sXTsKICAgICAgICAgICAgICAgIHJldHVybiBjaSA/IGA8c3BhbiBjbGFzcz0ibWFuYS1waXAiIHN0eWxlPSJiYWNrZ3JvdW5kOiR7Y2kuYmd9O2NvbG9yOiR7Y2kuY29sb3J9Ij4ke2NpLmxhYmVsfTwvc3Bhbj5gIDogJyc7CiAgICAgICAgICAgIH0pLmpvaW4oJycpOwoKICAgICAgICAgICAgY29uc3QgaXNJbnN0YWxsZWQgPSBhdmFpbGFibGVEZWNrcy5zb21lKGQgPT4KICAgICAgICAgICAgICAgIGQubmFtZSA9PT0gcC5maWxlTmFtZS5yZXBsYWNlKCcuZGNrJywgJycpIHx8IGQubmFtZSA9PT0gcC5uYW1lLnJlcGxhY2UoLyAvZywgJ18nKQogICAgICAgICAgICApOwogICAgICAgICAgICBjb25zdCBpbnN0YWxsTGFiZWwgPSBpc0luc3RhbGxlZCA/ICdJbnN0YWxsZWQnIDogJ0luc3RhbGwgJiBVc2UnOwogICAgICAgICAgICBjb25zdCBpbnN0YWxsQ2xhc3MgPSBpc0luc3RhbGxlZCA/ICdsYWItcHJlY29uLWNhcmQtaW5zdGFsbGVkJyA6ICcnOwoKICAgICAgICAgICAgcmV0dXJuIGAKICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXByZWNvbi1jYXJkICR7aW5zdGFsbENsYXNzfSIgb25jbGljaz0iQWlMYWIuaW5zdGFsbFByZWNvbignJHtlc2NhcGVBdHRyKHAuZmlsZU5hbWUpfScpIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InByZWNvbi1jYXJkLWNvbG9ycyI+JHtjb2xvcnN9PC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJwcmVjb24tY2FyZC1uYW1lIj4ke2VzY2FwZUh0bWwocC5uYW1lKX08L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InByZWNvbi1jYXJkLWNvbW1hbmRlciI+JHtlc2NhcGVIdG1sKHAuY29tbWFuZGVyKX08L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InByZWNvbi1jYXJkLXNldCI+JHtlc2NhcGVIdG1sKHAuc2V0KX0gKCR7cC55ZWFyfSk8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InByZWNvbi1jYXJkLXRoZW1lIj4ke2VzY2FwZUh0bWwocC50aGVtZSB8fCAnJyl9PC9kaXY+CiAgICAgICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJsYWItcHJlY29uLWluc3RhbGwtYnRuIiB0aXRsZT0iSW5zdGFsbCB0byBGb3JnZSBhbmQgYWRkIHRvIGRlY2sgbGlzdCI+CiAgICAgICAgICAgICAgICAgICAgJHtpbnN0YWxsTGFiZWx9CiAgICAgICAgICAgICAgICA8L2J1dHRvbj4KICAgICAgICAgICAgPC9kaXY+YDsKICAgICAgICB9KS5qb2luKCcnKTsKICAgIH0KCiAgICBhc3luYyBmdW5jdGlvbiBpbnN0YWxsUHJlY29uKGZpbGVOYW1lKSB7CiAgICAgICAgY29uc3Qgc3RhdHVzRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXByZWNvbi1zdGF0dXMnKTsKCiAgICAgICAgaWYgKCFiYWNrZW5kQXZhaWxhYmxlKSB7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsICdQcmVjb24gaW5zdGFsbCByZXF1aXJlcyBhIHJ1bm5pbmcgYmFja2VuZC4nLCAnZXJyb3InKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgY29uc3QgcHJlY29uID0gcHJlY29uRGVja3MuZmluZChwID0+IHAuZmlsZU5hbWUgPT09IGZpbGVOYW1lKTsKICAgICAgICBjb25zdCBkaXNwbGF5TmFtZSA9IHByZWNvbiA/IHByZWNvbi5uYW1lIDogZmlsZU5hbWU7CiAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgYEluc3RhbGxpbmcgJHtkaXNwbGF5TmFtZX0uLi5gLCAnbG9hZGluZycpOwoKICAgICAgICB0cnkgewogICAgICAgICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChgJHtBUElfQkFTRX0vYXBpL2xhYi9wcmVjb25zL2luc3RhbGxgLCB7CiAgICAgICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LAogICAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBmaWxlTmFtZSB9KSwKICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpOwoKICAgICAgICAgICAgaWYgKCFyZXMub2spIHsKICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihkYXRhLmRldGFpbCB8fCAnSW5zdGFsbCBmYWlsZWQnKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgY29uc3QgZGVja05hbWUgPSBkYXRhLmRlY2tOYW1lOwoKICAgICAgICAgICAgaW1wb3J0ZWREZWNrTWV0YVtkZWNrTmFtZV0gPSB7CiAgICAgICAgICAgICAgICBzb3VyY2U6ICdQcmVjb24nLAogICAgICAgICAgICAgICAgc291cmNlVXJsOiBudWxsLAogICAgICAgICAgICAgICAgY29tbWFuZGVyOiBwcmVjb24gPyBwcmVjb24uY29tbWFuZGVyIDogbnVsbCwKICAgICAgICAgICAgICAgIGFyY2hldHlwZTogcHJlY29uID8gcHJlY29uLnRoZW1lIDogbnVsbCwKICAgICAgICAgICAgICAgIGNvbG9ySWRlbnRpdHk6IHByZWNvbiA/IHByZWNvbi5jb2xvcnMgOiBbXSwKICAgICAgICAgICAgfTsKCiAgICAgICAgICAgIGlmICghYXZhaWxhYmxlRGVja3MuZmluZChkID0+IGQubmFtZSA9PT0gZGVja05hbWUpKSB7CiAgICAgICAgICAgICAgICBhdmFpbGFibGVEZWNrcy51bnNoaWZ0KHsKICAgICAgICAgICAgICAgICAgICBuYW1lOiBkZWNrTmFtZSwKICAgICAgICAgICAgICAgICAgICBjb21tYW5kZXI6IHByZWNvbiA/IHByZWNvbi5jb21tYW5kZXIgOiBkZWNrTmFtZSwKICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgcmVmcmVzaERlY2tTZWxlY3RvcnMoKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gQXV0by1hc3NpZ24gdG8gZmlyc3QgZW1wdHkgc2VhdAogICAgICAgICAgICBjb25zdCBlbXB0eVNlYXQgPSBzZWxlY3RlZERlY2tzLmluZGV4T2YobnVsbCk7CiAgICAgICAgICAgIGlmIChlbXB0eVNlYXQgPj0gMCkgewogICAgICAgICAgICAgICAgY29uc3Qgc2VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoYGxhYi1kZWNrLXNlbGVjdC0ke2VtcHR5U2VhdH1gKTsKICAgICAgICAgICAgICAgIGlmIChzZWwpIHsKICAgICAgICAgICAgICAgICAgICBzZWwudmFsdWUgPSBkZWNrTmFtZTsKICAgICAgICAgICAgICAgICAgICBzZWxlY3REZWNrKGVtcHR5U2VhdCwgZGVja05hbWUpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCBgSW5zdGFsbGVkICIke2Rpc3BsYXlOYW1lfSIgXHUyMDE0IGFkZGVkIHRvIGRlY2sgbGlzdGAsICdzdWNjZXNzJyk7CiAgICAgICAgICAgIHJlbmRlclByZWNvbkdyaWQocHJlY29uRGVja3MpOwoKICAgICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgYEVycm9yOiAke2Vyci5tZXNzYWdlfWAsICdlcnJvcicpOwogICAgICAgIH0KICAgIH0KCiAgICBhc3luYyBmdW5jdGlvbiBpbnN0YWxsQWxsUHJlY29ucygpIHsKICAgICAgICBjb25zdCBzdGF0dXNFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItcHJlY29uLXN0YXR1cycpOwoKICAgICAgICBpZiAoIWJhY2tlbmRBdmFpbGFibGUpIHsKICAgICAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgJ1ByZWNvbiBpbnN0YWxsIHJlcXVpcmVzIGEgcnVubmluZyBiYWNrZW5kLicsICdlcnJvcicpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQoKICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCBgSW5zdGFsbGluZyBhbGwgJHtwcmVjb25EZWNrcy5sZW5ndGh9IHByZWNvbiBkZWNrcy4uLmAsICdsb2FkaW5nJyk7CgogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGNvbnN0IGZpbGVOYW1lcyA9IHByZWNvbkRlY2tzLm1hcChwID0+IHAuZmlsZU5hbWUpOwogICAgICAgICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChgJHtBUElfQkFTRX0vYXBpL2xhYi9wcmVjb25zL2luc3RhbGwtYmF0Y2hgLCB7CiAgICAgICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LAogICAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBmaWxlTmFtZXMgfSksCiAgICAgICAgICAgIH0pOwogICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKCiAgICAgICAgICAgIGlmICghcmVzLm9rKSB7CiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZGF0YS5kZXRhaWwgfHwgJ0JhdGNoIGluc3RhbGwgZmFpbGVkJyk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGxldCBpbnN0YWxsZWQgPSAwOwogICAgICAgICAgICBmb3IgKGNvbnN0IHIgb2YgKGRhdGEucmVzdWx0cyB8fCBbXSkpIHsKICAgICAgICAgICAgICAgIGlmIChyLmluc3RhbGxlZCkgewogICAgICAgICAgICAgICAgICAgIGluc3RhbGxlZCsrOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IGRlY2tOYW1lID0gci5kZWNrTmFtZTsKICAgICAgICAgICAgICAgICAgICBjb25zdCBwcmVjb24gPSBwcmVjb25EZWNrcy5maW5kKHAgPT4gcC5maWxlTmFtZSA9PT0gci5maWxlTmFtZSk7CgogICAgICAgICAgICAgICAgICAgIGltcG9ydGVkRGVja01ldGFbZGVja05hbWVdID0gewogICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2U6ICdQcmVjb24nLAogICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2VVcmw6IG51bGwsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogcHJlY29uID8gcHJlY29uLmNvbW1hbmRlciA6IG51bGwsCiAgICAgICAgICAgICAgICAgICAgICAgIGFyY2hldHlwZTogcHJlY29uID8gcHJlY29uLnRoZW1lIDogbnVsbCwKICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JJZGVudGl0eTogcHJlY29uID8gcHJlY29uLmNvbG9ycyA6IFtdLAogICAgICAgICAgICAgICAgICAgIH07CgogICAgICAgICAgICAgICAgICAgIGlmICghYXZhaWxhYmxlRGVja3MuZmluZChkID0+IGQubmFtZSA9PT0gZGVja05hbWUpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGF2YWlsYWJsZURlY2tzLnB1c2goewogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZTogZGVja05hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tYW5kZXI6IHByZWNvbiA/IHByZWNvbi5jb21tYW5kZXIgOiBkZWNrTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgICAgICByZWZyZXNoRGVja1NlbGVjdG9ycygpOwogICAgICAgICAgICByZW5kZXJQcmVjb25HcmlkKHByZWNvbkRlY2tzKTsKICAgICAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgYEluc3RhbGxlZCAke2luc3RhbGxlZH0gb2YgJHtmaWxlTmFtZXMubGVuZ3RofSBwcmVjb24gZGVja3NgLCAnc3VjY2VzcycpOwoKICAgICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgYEVycm9yOiAke2Vyci5tZXNzYWdlfWAsICdlcnJvcicpOwogICAgICAgIH0KICAgIH0KCiAgICAvLyDilIDilIAgdjM6IFRleHQgSW1wb3J0IOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGZ1bmN0aW9uIHNob3dUZXh0SW1wb3J0KCkgewogICAgICAgIGNvbnN0IG1vZGFsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi10ZXh0LW1vZGFsJyk7CiAgICAgICAgaWYgKG1vZGFsKSB7CiAgICAgICAgICAgIG1vZGFsLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpOwogICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXRleHQtaW1wb3J0LWFyZWEnKT8uZm9jdXMoKTsKICAgICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gY2xvc2VUZXh0SW1wb3J0KCkgewogICAgICAgIGNvbnN0IG1vZGFsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi10ZXh0LW1vZGFsJyk7CiAgICAgICAgaWYgKG1vZGFsKSBtb2RhbC5jbGFzc0xpc3QucmVtb3ZlKCdhY3RpdmUnKTsKICAgICAgICBjb25zdCBzdGF0dXNFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItdGV4dC1pbXBvcnQtc3RhdHVzJyk7CiAgICAgICAgaWYgKHN0YXR1c0VsKSBzdGF0dXNFbC5pbm5lckhUTUwgPSAnJzsKICAgIH0KCiAgICBhc3luYyBmdW5jdGlvbiBpbXBvcnRGcm9tVGV4dCgpIHsKICAgICAgICBjb25zdCB0ZXh0QXJlYSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItdGV4dC1pbXBvcnQtYXJlYScpOwogICAgICAgIGNvbnN0IGNvbW1hbmRlcklucHV0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi10ZXh0LWNvbW1hbmRlci1vdmVycmlkZScpOwogICAgICAgIGNvbnN0IHN0YXR1c0VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi10ZXh0LWltcG9ydC1zdGF0dXMnKTsKCiAgICAgICAgY29uc3QgdGV4dCA9IHRleHRBcmVhPy52YWx1ZS50cmltKCk7CiAgICAgICAgY29uc3QgY29tbWFuZGVyID0gY29tbWFuZGVySW5wdXQ/LnZhbHVlLnRyaW0oKSB8fCBudWxsOwoKICAgICAgICBpZiAoIXRleHQpIHsKICAgICAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgJ1BsZWFzZSBwYXN0ZSBhIGNhcmQgbGlzdC4nLCAnZXJyb3InKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgaWYgKCFiYWNrZW5kQXZhaWxhYmxlKSB7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsICdUZXh0IGltcG9ydCByZXF1aXJlcyBhIHJ1bm5pbmcgYmFja2VuZC4nLCAnZXJyb3InKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgc2hvd0ltcG9ydFN0YXR1cyhzdGF0dXNFbCwgJ1BhcnNpbmcgY2FyZCBsaXN0Li4uJywgJ2xvYWRpbmcnKTsKCiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvaW1wb3J0L3RleHRgLCB7CiAgICAgICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LAogICAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyB0ZXh0LCBjb21tYW5kZXIgfSksCiAgICAgICAgICAgIH0pOwogICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKCiAgICAgICAgICAgIGlmICghcmVzLm9rKSB7CiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZGF0YS5kZXRhaWwgfHwgJ0ltcG9ydCBmYWlsZWQnKTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgaW1wb3J0ZWREZWNrTWV0YVtkYXRhLmRja0ZpbGVdID0gewogICAgICAgICAgICAgICAgc291cmNlOiAnVGV4dCBJbXBvcnQnLAogICAgICAgICAgICAgICAgc291cmNlVXJsOiBudWxsLAogICAgICAgICAgICAgICAgY29tbWFuZGVyOiBkYXRhLmNvbW1hbmRlciwKICAgICAgICAgICAgfTsKCiAgICAgICAgICAgIGlmICghYXZhaWxhYmxlRGVja3MuZmluZChkID0+IGQubmFtZSA9PT0gZGF0YS5kY2tGaWxlKSkgewogICAgICAgICAgICAgICAgYXZhaWxhYmxlRGVja3MudW5zaGlmdCh7CiAgICAgICAgICAgICAgICAgICAgbmFtZTogZGF0YS5kY2tGaWxlLAogICAgICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogZGF0YS5jb21tYW5kZXIsCiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIHJlZnJlc2hEZWNrU2VsZWN0b3JzKCk7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsCiAgICAgICAgICAgICAgICBgSW1wb3J0ZWQgIiR7ZGF0YS5kZWNrTmFtZX0iICgke2RhdGEudG90YWxDYXJkc30gY2FyZHMpYCwKICAgICAgICAgICAgICAgICdzdWNjZXNzJwogICAgICAgICAgICApOwogICAgICAgICAgICB0ZXh0QXJlYS52YWx1ZSA9ICcnOwogICAgICAgICAgICBpZiAoY29tbWFuZGVySW5wdXQpIGNvbW1hbmRlcklucHV0LnZhbHVlID0gJyc7CgogICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCBgRXJyb3I6ICR7ZXJyLm1lc3NhZ2V9YCwgJ2Vycm9yJyk7CiAgICAgICAgfQogICAgfQoKICAgIC8vIOKUgOKUgCBJbXBvcnQgU3RhdHVzIEhlbHBlciDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBmdW5jdGlvbiBzaG93SW1wb3J0U3RhdHVzKGVsLCBtZXNzYWdlLCB0eXBlKSB7CiAgICAgICAgaWYgKCFlbCkgcmV0dXJuOwogICAgICAgIGVsLmNsYXNzTmFtZSA9ICdsYWItaW1wb3J0LXN0YXR1cyc7CiAgICAgICAgaWYgKHR5cGUpIGVsLmNsYXNzTGlzdC5hZGQoYHN0YXR1cy0ke3R5cGV9YCk7CiAgICAgICAgZWwuaW5uZXJIVE1MID0gbWVzc2FnZTsKICAgICAgICBlbC5zdHlsZS5kaXNwbGF5ID0gJ2Jsb2NrJzsKCiAgICAgICAgaWYgKHR5cGUgPT09ICdzdWNjZXNzJykgewogICAgICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHsKICAgICAgICAgICAgICAgIGlmIChlbC5pbm5lckhUTUwgPT09IG1lc3NhZ2UpIGVsLnN0eWxlLmRpc3BsYXkgPSAnbm9uZSc7CiAgICAgICAgICAgIH0sIDgwMDApOwogICAgICAgIH0KICAgIH0KCiAgICAvLyDilIDilIAgQ29udHJvbHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgZnVuY3Rpb24gc2V0R2FtZUNvdW50KG4pIHsKICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWdhbWUtY291bnQnKS52YWx1ZSA9IG47CiAgICAgICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLmxhYi1wcmVzZXQtYnRuJykuZm9yRWFjaChidG4gPT4gewogICAgICAgICAgICBidG4uY2xhc3NMaXN0LnRvZ2dsZSgnYWN0aXZlJywgcGFyc2VJbnQoYnRuLnRleHRDb250ZW50KSA9PT0gbik7CiAgICAgICAgfSk7CiAgICB9CgogICAgLy8g4pSA4pSAIEJhdGNoIEV4ZWN1dGlvbiDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBhc3luYyBmdW5jdGlvbiBzdGFydEJhdGNoKCkgewogICAgICAgIGNvbnN0IGVuZ2luZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItZW5naW5lJykudmFsdWU7CgogICAgICAgIGlmIChlbmdpbmUgPT09ICdkZWVwc2VlaycpIHsKICAgICAgICAgICAgLy8gRGVlcFNlZWs6IGF0IGxlYXN0IDEgZGVjayByZXF1aXJlZCAobm90IGFsbCA0KQogICAgICAgICAgICBjb25zdCBmaWxsZWREZWNrcyA9IHNlbGVjdGVkRGVja3MuZmlsdGVyKGQgPT4gZCk7CiAgICAgICAgICAgIGlmIChmaWxsZWREZWNrcy5sZW5ndGggPT09IDApIHsKICAgICAgICAgICAgICAgIGFsZXJ0KCdTZWxlY3QgYXQgbGVhc3QgMSBkZWNrLicpOwogICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICB9CgogICAgICAgICAgICBjb25zdCBudW1HYW1lcyA9IHBhcnNlSW50KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItZ2FtZS1jb3VudCcpLnZhbHVlKSB8fCAzMDsKCiAgICAgICAgICAgIHNldFJ1bm5pbmcodHJ1ZSk7CiAgICAgICAgICAgIGNsZWFyTG9nKCk7CiAgICAgICAgICAgIHNob3dQcm9ncmVzcygpOwogICAgICAgICAgICBhcHBlbmRMb2coJ1tEZWVwU2VlayBFbmdpbmVdIFN0YXJ0aW5nIGJhdGNoIHdpdGggTExNIG9wcG9uZW50Li4uJyk7CgogICAgICAgICAgICBhd2FpdCBzdGFydERlZXBTZWVrQmF0Y2goZmlsbGVkRGVja3MsIG51bUdhbWVzKTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAvLyBKYXZhIEZvcmdlOiByZXF1aXJlcyBhbGwgNCBkZWNrcwogICAgICAgICAgICBpZiAoc2VsZWN0ZWREZWNrcy5zb21lKGQgPT4gIWQpKSB7CiAgICAgICAgICAgICAgICBhbGVydCgnU2VsZWN0IGEgZGVjayBmb3IgYWxsIDQgc2VhdHMuJyk7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGNvbnN0IG51bUdhbWVzID0gcGFyc2VJbnQoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1nYW1lLWNvdW50JykudmFsdWUpIHx8IDEwMDsKICAgICAgICAgICAgY29uc3QgdGhyZWFkcyA9IHBhcnNlSW50KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItdGhyZWFkcycpLnZhbHVlKSB8fCA0OwogICAgICAgICAgICBjb25zdCBzZWVkSW5wdXQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXNlZWQnKS52YWx1ZS50cmltKCk7CiAgICAgICAgICAgIGNvbnN0IHNlZWQgPSBzZWVkSW5wdXQgPyBwYXJzZUludChzZWVkSW5wdXQpIDogbnVsbDsKCiAgICAgICAgICAgIGNvbnN0IGRlY2tTb3VyY2VzID0gc2VsZWN0ZWREZWNrcy5tYXAobmFtZSA9PiB7CiAgICAgICAgICAgICAgICBjb25zdCBtZXRhID0gaW1wb3J0ZWREZWNrTWV0YVtuYW1lXTsKICAgICAgICAgICAgICAgIHJldHVybiBtZXRhID8gewogICAgICAgICAgICAgICAgICAgIHNvdXJjZTogbWV0YS5zb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgc291cmNlVXJsOiBtZXRhLnNvdXJjZVVybCB8fCBudWxsLAogICAgICAgICAgICAgICAgICAgIGNvbW1hbmRlcjogbWV0YS5jb21tYW5kZXIgfHwgbnVsbCwKICAgICAgICAgICAgICAgICAgICBhcmNoZXR5cGU6IG1ldGEuYXJjaGV0eXBlIHx8IG51bGwsCiAgICAgICAgICAgICAgICB9IDoge307CiAgICAgICAgICAgIH0pOwoKICAgICAgICAgICAgc2V0UnVubmluZyh0cnVlKTsKICAgICAgICAgICAgY2xlYXJMb2coKTsKICAgICAgICAgICAgc2hvd1Byb2dyZXNzKCk7CgogICAgICAgICAgICBpZiAoYmFja2VuZEF2YWlsYWJsZSkgewogICAgICAgICAgICAgICAgYXdhaXQgc3RhcnRCYWNrZW5kQmF0Y2gobnVtR2FtZXMsIHRocmVhZHMsIHNlZWQsIGRlY2tTb3VyY2VzKTsKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybignW0FJIExhYl0gTm8gYmFja2VuZCDigJQgcnVubmluZyBsb2NhbCBzaW11bGF0aW9uJyk7CiAgICAgICAgICAgICAgICBydW5Mb2NhbFNpbXVsYXRpb24obnVtR2FtZXMsIHNlZWQpOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQoKICAgIGFzeW5jIGZ1bmN0aW9uIHN0YXJ0RGVlcFNlZWtCYXRjaChkZWNrTmFtZXMsIG51bUdhbWVzKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvc3RhcnQtZGVlcHNlZWtgLCB7CiAgICAgICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LAogICAgICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoewogICAgICAgICAgICAgICAgICAgIGRlY2tzOiBkZWNrTmFtZXMsCiAgICAgICAgICAgICAgICAgICAgbnVtR2FtZXMsCiAgICAgICAgICAgICAgICB9KSwKICAgICAgICAgICAgfSk7CgogICAgICAgICAgICBpZiAocmVzLm9rKSB7CiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKICAgICAgICAgICAgICAgIGFwcGVuZExvZyhgRGVlcFNlZWsgYmF0Y2ggJHtkYXRhLmJhdGNoSWR9IHN0YXJ0ZWQ6ICR7bnVtR2FtZXN9IGdhbWVzIGFjcm9zcyAke2RlY2tOYW1lcy5sZW5ndGh9IGRlY2tzYCk7CiAgICAgICAgICAgICAgICBzdGFydFBvbGxpbmcoZGF0YS5iYXRjaElkKTsKICAgICAgICAgICAgICAgIHN0YXJ0TG9nUG9sbGluZyhkYXRhLmJhdGNoSWQpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgY29uc3QgZXJyID0gYXdhaXQgcmVzLnRleHQoKTsKICAgICAgICAgICAgICAgIGFwcGVuZExvZyhgRVJST1I6ICR7ZXJyfWApOwogICAgICAgICAgICAgICAgc2V0UnVubmluZyhmYWxzZSk7CiAgICAgICAgICAgIH0KICAgICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAgICAgICAgYXBwZW5kTG9nKGBFUlJPUjogJHtlcnIubWVzc2FnZX1gKTsKICAgICAgICAgICAgc2V0UnVubmluZyhmYWxzZSk7CiAgICAgICAgfQogICAgfQoKICAgIGFzeW5jIGZ1bmN0aW9uIHN0YXJ0QmFja2VuZEJhdGNoKG51bUdhbWVzLCB0aHJlYWRzLCBzZWVkLCBkZWNrU291cmNlcykgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGAke0FQSV9CQVNFfS9hcGkvbGFiL3N0YXJ0YCwgewogICAgICAgICAgICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgICAgICAgICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfSwKICAgICAgICAgICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHsKICAgICAgICAgICAgICAgICAgICBkZWNrczogc2VsZWN0ZWREZWNrcywKICAgICAgICAgICAgICAgICAgICBudW1HYW1lcywKICAgICAgICAgICAgICAgICAgICB0aHJlYWRzLAogICAgICAgICAgICAgICAgICAgIHNlZWQsCiAgICAgICAgICAgICAgICAgICAgZGVja1NvdXJjZXMsCiAgICAgICAgICAgICAgICB9KSwKICAgICAgICAgICAgfSk7CgogICAgICAgICAgICBpZiAocmVzLm9rKSB7CiAgICAgICAgICAgICAgICBjb25zdCBkYXRhID0gYXdhaXQgcmVzLmpzb24oKTsKICAgICAgICAgICAgICAgIGFwcGVuZExvZyhgQmF0Y2ggJHtkYXRhLmJhdGNoSWR9IHN0YXJ0ZWQ6ICR7bnVtR2FtZXN9IGdhbWVzLCAke3RocmVhZHN9IHRocmVhZHNgKTsKICAgICAgICAgICAgICAgIHN0YXJ0UG9sbGluZyhkYXRhLmJhdGNoSWQpOwogICAgICAgICAgICAgICAgc3RhcnRMb2dQb2xsaW5nKGRhdGEuYmF0Y2hJZCk7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBjb25zdCBlcnIgPSBhd2FpdCByZXMudGV4dCgpOwogICAgICAgICAgICAgICAgYXBwZW5kTG9nKGBFUlJPUjogJHtlcnJ9YCk7CiAgICAgICAgICAgICAgICBzZXRSdW5uaW5nKGZhbHNlKTsKICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICBhcHBlbmRMb2coYENvbm5lY3Rpb24gZXJyb3I6ICR7ZXJyLm1lc3NhZ2V9YCk7CiAgICAgICAgICAgIGNvbnNvbGUud2FybignW0FJIExhYl0gQmFja2VuZCBlcnJvciwgZmFsbGluZyBiYWNrIHRvIGxvY2FsIHNpbTonLCBlcnIpOwogICAgICAgICAgICBiYWNrZW5kQXZhaWxhYmxlID0gZmFsc2U7CiAgICAgICAgICAgIHVwZGF0ZUJhY2tlbmRJbmRpY2F0b3IoKTsKICAgICAgICAgICAgcnVuTG9jYWxTaW11bGF0aW9uKG51bUdhbWVzLCBudWxsKTsKICAgICAgICB9CiAgICB9CgogICAgLyoqCiAgICAgKiBMb2NhbCBzaW11bGF0aW9uIGZhbGxiYWNrIOKAlCBnZW5lcmF0ZXMgc3ludGhldGljIHJlc3VsdHMKICAgICAqIGZvciB0ZXN0aW5nIHRoZSBVSSB3aXRob3V0IGEgcnVubmluZyBGb3JnZSBiYWNrZW5kLgogICAgICovCiAgICBmdW5jdGlvbiBydW5Mb2NhbFNpbXVsYXRpb24obnVtR2FtZXMsIHNlZWQpIHsKICAgICAgICBjb25zdCBybmcgPSBzZWVkICE9PSBudWxsID8gc2VlZGVkUmFuZG9tKHNlZWQpIDogTWF0aC5yYW5kb207CgogICAgICAgIGNvbnN0IGdhbWVzID0gW107CiAgICAgICAgbGV0IHN0YXJ0VGltZSA9IHBlcmZvcm1hbmNlLm5vdygpOwogICAgICAgIGxldCBjb21wbGV0ZWQgPSAwOwoKICAgICAgICBjb25zdCBiYXRjaEludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4gewogICAgICAgICAgICBjb25zdCBjaHVuayA9IE1hdGgubWluKE1hdGguY2VpbChudW1HYW1lcyAvIDIwKSwgbnVtR2FtZXMgLSBjb21wbGV0ZWQpOwogICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGNodW5rICYmIGNvbXBsZXRlZCA8IG51bUdhbWVzOyBpKyspIHsKICAgICAgICAgICAgICAgIGdhbWVzLnB1c2goc2ltdWxhdGVPbmVHYW1lKGNvbXBsZXRlZCwgcm5nKSk7CiAgICAgICAgICAgICAgICBjb21wbGV0ZWQrKzsKICAgICAgICAgICAgfQogICAgICAgICAgICB1cGRhdGVQcm9ncmVzcyhjb21wbGV0ZWQsIG51bUdhbWVzLCBwZXJmb3JtYW5jZS5ub3coKSAtIHN0YXJ0VGltZSk7CiAgICAgICAgICAgIGFwcGVuZExvZyhgW0dhbWUgJHtjb21wbGV0ZWR9LyR7bnVtR2FtZXN9XSBzaW11bGF0ZWQgbG9jYWxseWApOwoKICAgICAgICAgICAgaWYgKGNvbXBsZXRlZCA+PSBudW1HYW1lcykgewogICAgICAgICAgICAgICAgY2xlYXJJbnRlcnZhbChiYXRjaEludGVydmFsKTsKICAgICAgICAgICAgICAgIGNvbnN0IGVsYXBzZWQgPSBwZXJmb3JtYW5jZS5ub3coKSAtIHN0YXJ0VGltZTsKICAgICAgICAgICAgICAgIGZpbmFsaXplQmF0Y2goZ2FtZXMsIGVsYXBzZWQsIG51bUdhbWVzLCBzZWVkKTsKICAgICAgICAgICAgfQogICAgICAgIH0sIDUwKTsKICAgIH0KCiAgICBmdW5jdGlvbiBzaW11bGF0ZU9uZUdhbWUoaW5kZXgsIHJuZykgewogICAgICAgIGNvbnN0IHRvdGFsVHVybnMgPSBNYXRoLmZsb29yKHJuZygpICogMjApICsgNTsKICAgICAgICBjb25zdCB3aW5uaW5nU2VhdCA9IHJuZygpIDwgMC4wNSA/IG51bGwgOiBNYXRoLmZsb29yKHJuZygpICogNCk7CgogICAgICAgIGNvbnN0IHdpbkNvbmRpdGlvbnMgPSBbJ2NvbWJhdF9kYW1hZ2UnLCAnY29tbWFuZGVyX2RhbWFnZScsICdjb21ib19hbHRfd2luJywgJ2xpZmVfZHJhaW4nXTsKICAgICAgICBjb25zdCB3aW5Db25kaXRpb24gPSB3aW5uaW5nU2VhdCAhPT0gbnVsbAogICAgICAgICAgICA/IHdpbkNvbmRpdGlvbnNbTWF0aC5mbG9vcihybmcoKSAqIHdpbkNvbmRpdGlvbnMubGVuZ3RoKV0KICAgICAgICAgICAgOiAndGltZW91dCc7CgogICAgICAgIGNvbnN0IHBsYXllclJlc3VsdHMgPSBbMCwgMSwgMiwgM10ubWFwKHNlYXQgPT4gKHsKICAgICAgICAgICAgc2VhdEluZGV4OiBzZWF0LAogICAgICAgICAgICBmaW5hbExpZmU6IHNlYXQgPT09IHdpbm5pbmdTZWF0ID8gTWF0aC5mbG9vcihybmcoKSAqIDMwKSArIDEwIDogTWF0aC5mbG9vcihybmcoKSAqIDIwKSAtIDEwLAogICAgICAgICAgICBtdWxsaWdhbnM6IHJuZygpIDwgMC4zID8gMSA6IDAsCiAgICAgICAgICAgIGlzV2lubmVyOiBzZWF0ID09PSB3aW5uaW5nU2VhdCwKICAgICAgICAgICAgY29tbWFuZGVyRGFtYWdlRGVhbHQ6IE1hdGguZmxvb3Iocm5nKCkgKiAxNSksCiAgICAgICAgICAgIGNvbW1hbmRlckNhc3RzOiBNYXRoLmZsb29yKHJuZygpICogMyksCiAgICAgICAgICAgIGxhbmRzUGxheWVkOiBNYXRoLmZsb29yKHRvdGFsVHVybnMgKiAwLjYpLAogICAgICAgICAgICBzcGVsbHNDYXN0OiBNYXRoLmZsb29yKHRvdGFsVHVybnMgKiAxLjUpLAogICAgICAgICAgICBjcmVhdHVyZXNEZXN0cm95ZWQ6IE1hdGguZmxvb3Iocm5nKCkgKiA1KSwKICAgICAgICB9KSk7CgogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIGdhbWVJbmRleDogaW5kZXgsCiAgICAgICAgICAgIHdpbm5pbmdTZWF0LAogICAgICAgICAgICB0b3RhbFR1cm5zLAogICAgICAgICAgICB3aW5Db25kaXRpb24sCiAgICAgICAgICAgIGdhbWVTZWVkOiBNYXRoLmZsb29yKHJuZygpICogMTAwMDAwMCksCiAgICAgICAgICAgIGVsYXBzZWRNczogTWF0aC5mbG9vcihybmcoKSAqIDMwMDApICsgNTAwLAogICAgICAgICAgICBwbGF5ZXJSZXN1bHRzLAogICAgICAgIH07CiAgICB9CgogICAgZnVuY3Rpb24gZmluYWxpemVCYXRjaChnYW1lcywgZWxhcHNlZE1zLCBudW1HYW1lcywgc2VlZCkgewogICAgICAgIGNvbnN0IHN1bW1hcnkgPSBjb21wdXRlTG9jYWxTdW1tYXJ5KGdhbWVzLCBlbGFwc2VkTXMpOwoKICAgICAgICBsYXN0UmVzdWx0ID0gewogICAgICAgICAgICBtZXRhZGF0YTogewogICAgICAgICAgICAgICAgc2NoZW1hVmVyc2lvbjogJzEuMC4wJywKICAgICAgICAgICAgICAgIGJhdGNoSWQ6IGNyeXB0by5yYW5kb21VVUlEPy4oKSB8fCAnbG9jYWwtJyArIERhdGUubm93KCksCiAgICAgICAgICAgICAgICB0aW1lc3RhbXA6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKSwKICAgICAgICAgICAgICAgIHRvdGFsR2FtZXM6IG51bUdhbWVzLAogICAgICAgICAgICAgICAgY29tcGxldGVkR2FtZXM6IGdhbWVzLmxlbmd0aCwKICAgICAgICAgICAgICAgIGZvcm1hdDogJ2NvbW1hbmRlcicsCiAgICAgICAgICAgICAgICBwb2RTaXplOiA0LAogICAgICAgICAgICAgICAgZW5naW5lVmVyc2lvbjogYmFja2VuZEF2YWlsYWJsZSA/ICdmb3JnZS0yLjAuMTItU05BUFNIT1QnIDogJ2xvY2FsLXNpbS0xLjAnLAogICAgICAgICAgICAgICAgbWFzdGVyU2VlZDogc2VlZCwKICAgICAgICAgICAgICAgIHRocmVhZHM6IDEsCiAgICAgICAgICAgICAgICBlbGFwc2VkTXM6IE1hdGgucm91bmQoZWxhcHNlZE1zKSwKICAgICAgICAgICAgfSwKICAgICAgICAgICAgZGVja3M6IHNlbGVjdGVkRGVja3MubWFwKChuYW1lLCBpKSA9PiB7CiAgICAgICAgICAgICAgICBjb25zdCBtZXRhID0gaW1wb3J0ZWREZWNrTWV0YVtuYW1lXTsKICAgICAgICAgICAgICAgIHJldHVybiB7CiAgICAgICAgICAgICAgICAgICAgc2VhdEluZGV4OiBpLAogICAgICAgICAgICAgICAgICAgIGRlY2tOYW1lOiBuYW1lLnJlcGxhY2UoJy5kY2snLCAnJyksCiAgICAgICAgICAgICAgICAgICAgY29tbWFuZGVyTmFtZTogbWV0YT8uY29tbWFuZGVyIHx8IG5hbWUucmVwbGFjZSgnLmRjaycsICcnKS5yZXBsYWNlKC9fL2csICcgJyksCiAgICAgICAgICAgICAgICAgICAgZGVja0ZpbGU6IG5hbWUsCiAgICAgICAgICAgICAgICAgICAgY29sb3JJZGVudGl0eTogbWV0YT8uY29sb3JJZGVudGl0eSB8fCBbXSwKICAgICAgICAgICAgICAgICAgICBjYXJkQ291bnQ6IDEwMCwKICAgICAgICAgICAgICAgICAgICBzb3VyY2U6IG1ldGE/LnNvdXJjZSB8fCAnTG9jYWwgRmlsZScsCiAgICAgICAgICAgICAgICAgICAgc291cmNlVXJsOiBtZXRhPy5zb3VyY2VVcmwgfHwgbnVsbCwKICAgICAgICAgICAgICAgIH07CiAgICAgICAgICAgIH0pLAogICAgICAgICAgICBnYW1lcywKICAgICAgICAgICAgc3VtbWFyeSwKICAgICAgICB9OwoKICAgICAgICBkaXNwbGF5UmVzdWx0cyhsYXN0UmVzdWx0KTsKICAgICAgICBzZXRSdW5uaW5nKGZhbHNlKTsKICAgIH0KCiAgICBmdW5jdGlvbiBjb21wdXRlTG9jYWxTdW1tYXJ5KGdhbWVzLCBlbGFwc2VkTXMpIHsKICAgICAgICBjb25zdCBwZXJEZWNrID0gWzAsIDEsIDIsIDNdLm1hcChzZWF0ID0+IHsKICAgICAgICAgICAgY29uc3Qgc2VhdEdhbWVzID0gZ2FtZXMubWFwKGcgPT4gZy5wbGF5ZXJSZXN1bHRzW3NlYXRdKTsKICAgICAgICAgICAgY29uc3Qgd2lucyA9IHNlYXRHYW1lcy5maWx0ZXIocCA9PiBwLmlzV2lubmVyKS5sZW5ndGg7CiAgICAgICAgICAgIGNvbnN0IGxvc3NlcyA9IGdhbWVzLmZpbHRlcihnID0+IGcud2lubmluZ1NlYXQgIT09IG51bGwgJiYgZy53aW5uaW5nU2VhdCAhPT0gc2VhdCkubGVuZ3RoOwogICAgICAgICAgICBjb25zdCBkcmF3cyA9IGdhbWVzLmZpbHRlcihnID0+IGcud2lubmluZ1NlYXQgPT09IG51bGwpLmxlbmd0aDsKICAgICAgICAgICAgY29uc3Qgd2luR2FtZXMgPSBnYW1lcy5maWx0ZXIoZyA9PiBnLndpbm5pbmdTZWF0ID09PSBzZWF0KTsKCiAgICAgICAgICAgIGNvbnN0IGJyZWFrZG93biA9IHsgY29tYmF0X2RhbWFnZTogMCwgY29tbWFuZGVyX2RhbWFnZTogMCwgY29tYm9fYWx0X3dpbjogMCwgbGlmZV9kcmFpbjogMCwgbWlsbDogMCwgY29uY2Vzc2lvbjogMCwgdGltZW91dDogMCwgdW5rbm93bjogMCB9OwogICAgICAgICAgICB3aW5HYW1lcy5mb3JFYWNoKGcgPT4geyBicmVha2Rvd25bZy53aW5Db25kaXRpb25dID0gKGJyZWFrZG93bltnLndpbkNvbmRpdGlvbl0gfHwgMCkgKyAxOyB9KTsKCiAgICAgICAgICAgIHJldHVybiB7CiAgICAgICAgICAgICAgICBzZWF0SW5kZXg6IHNlYXQsCiAgICAgICAgICAgICAgICBkZWNrTmFtZTogc2VsZWN0ZWREZWNrc1tzZWF0XT8ucmVwbGFjZSgnLmRjaycsICcnKSB8fCBgRGVjayAke3NlYXQgKyAxfWAsCiAgICAgICAgICAgICAgICB3aW5zLAogICAgICAgICAgICAgICAgbG9zc2VzLAogICAgICAgICAgICAgICAgZHJhd3MsCiAgICAgICAgICAgICAgICB3aW5SYXRlOiBnYW1lcy5sZW5ndGggPiAwID8gd2lucyAvIGdhbWVzLmxlbmd0aCA6IDAsCiAgICAgICAgICAgICAgICBhdmdUdXJuc1RvV2luOiB3aW5zID4gMCA/IHdpbkdhbWVzLnJlZHVjZSgocywgZykgPT4gcyArIGcudG90YWxUdXJucywgMCkgLyB3aW5zIDogbnVsbCwKICAgICAgICAgICAgICAgIGF2Z011bGxpZ2Fuczogc2VhdEdhbWVzLmxlbmd0aCA+IDAgPyBzZWF0R2FtZXMucmVkdWNlKChzLCBwKSA9PiBzICsgcC5tdWxsaWdhbnMsIDApIC8gc2VhdEdhbWVzLmxlbmd0aCA6IDAsCiAgICAgICAgICAgICAgICBhdmdGaW5hbExpZmU6IHNlYXRHYW1lcy5sZW5ndGggPiAwID8gc2VhdEdhbWVzLnJlZHVjZSgocywgcCkgPT4gcyArIHAuZmluYWxMaWZlLCAwKSAvIHNlYXRHYW1lcy5sZW5ndGggOiAwLAogICAgICAgICAgICAgICAgd2luQ29uZGl0aW9uQnJlYWtkb3duOiBicmVha2Rvd24sCiAgICAgICAgICAgIH07CiAgICAgICAgfSk7CgogICAgICAgIGNvbnN0IHRvdGFsVHVybnMgPSBnYW1lcy5yZWR1Y2UoKHMsIGcpID0+IHMgKyBnLnRvdGFsVHVybnMsIDApOwogICAgICAgIGNvbnN0IHRvdGFsVGltZSA9IGdhbWVzLnJlZHVjZSgocywgZykgPT4gcyArIGcuZWxhcHNlZE1zLCAwKTsKCiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcGVyRGVjaywKICAgICAgICAgICAgYXZnR2FtZVR1cm5zOiBnYW1lcy5sZW5ndGggPiAwID8gdG90YWxUdXJucyAvIGdhbWVzLmxlbmd0aCA6IDAsCiAgICAgICAgICAgIGF2Z0dhbWVUaW1lTXM6IGdhbWVzLmxlbmd0aCA+IDAgPyB0b3RhbFRpbWUgLyBnYW1lcy5sZW5ndGggOiAwLAogICAgICAgICAgICBzaW1zUGVyU2Vjb25kOiBlbGFwc2VkTXMgPiAwID8gKGdhbWVzLmxlbmd0aCAvIChlbGFwc2VkTXMgLyAxMDAwKSkgOiAwLAogICAgICAgIH07CiAgICB9CgogICAgLy8g4pSA4pSAIFBvbGxpbmcg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgZnVuY3Rpb24gc3RhcnRQb2xsaW5nKGJhdGNoSWQpIHsKICAgICAgICBwb2xsSW50ZXJ2YWwgPSBzZXRJbnRlcnZhbChhc3luYyAoKSA9PiB7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChgJHtBUElfQkFTRX0vYXBpL2xhYi9zdGF0dXM/YmF0Y2hJZD0ke2JhdGNoSWR9YCk7CiAgICAgICAgICAgICAgICBpZiAoIXJlcy5vaykgcmV0dXJuOwogICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlcy5qc29uKCk7CgogICAgICAgICAgICAgICAgdXBkYXRlUHJvZ3Jlc3MoZGF0YS5jb21wbGV0ZWQsIGRhdGEudG90YWwsIGRhdGEuZWxhcHNlZE1zKTsKCiAgICAgICAgICAgICAgICBpZiAoIWRhdGEucnVubmluZykgewogICAgICAgICAgICAgICAgICAgIHN0b3BQb2xsaW5nKCk7CgogICAgICAgICAgICAgICAgICAgIGlmIChkYXRhLmVycm9yKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGFwcGVuZExvZyhgRVJST1I6ICR7ZGF0YS5lcnJvcn1gKTsKICAgICAgICAgICAgICAgICAgICAgICAgc2V0UnVubmluZyhmYWxzZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgIC8vIEZldGNoIGZpbmFsIHJlc3VsdAogICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlc3VsdFJlcyA9IGF3YWl0IGZldGNoKGAke0FQSV9CQVNFfS9hcGkvbGFiL3Jlc3VsdD9iYXRjaElkPSR7YmF0Y2hJZH1gKTsKICAgICAgICAgICAgICAgICAgICBpZiAocmVzdWx0UmVzLm9rKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RSZXN1bHQgPSBhd2FpdCByZXN1bHRSZXMuanNvbigpOwogICAgICAgICAgICAgICAgICAgICAgICBkaXNwbGF5UmVzdWx0cyhsYXN0UmVzdWx0KTsKICAgICAgICAgICAgICAgICAgICAgICAgYXBwZW5kTG9nKGBCYXRjaCBjb21wbGV0ZS4gJHtsYXN0UmVzdWx0Lm1ldGFkYXRhLmNvbXBsZXRlZEdhbWVzfSBnYW1lcyBpbiAkeyhsYXN0UmVzdWx0Lm1ldGFkYXRhLmVsYXBzZWRNcy8xMDAwKS50b0ZpeGVkKDEpfXNgKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgc2V0UnVubmluZyhmYWxzZSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcignW0FJIExhYl0gUG9sbCBlcnJvcjonLCBlcnIpOwogICAgICAgICAgICB9CiAgICAgICAgfSwgUE9MTF9SQVRFX01TKTsKICAgIH0KCiAgICBmdW5jdGlvbiBzdGFydExvZ1BvbGxpbmcoYmF0Y2hJZCkgewogICAgICAgIGxvZ0ludGVydmFsID0gc2V0SW50ZXJ2YWwoYXN5bmMgKCkgPT4gewogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvbG9nP2JhdGNoSWQ9JHtiYXRjaElkfWApOwogICAgICAgICAgICAgICAgaWYgKCFyZXMub2spIHJldHVybjsKICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpOwogICAgICAgICAgICAgICAgY29uc3QgbG9nRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWxvZy1jb250ZW50Jyk7CiAgICAgICAgICAgICAgICBpZiAobG9nRWwgJiYgZGF0YS5saW5lcykgewogICAgICAgICAgICAgICAgICAgIC8vIFNob3cgbGFzdCBOIGxpbmVzCiAgICAgICAgICAgICAgICAgICAgY29uc3QgdGFpbCA9IGRhdGEubGluZXMuc2xpY2UoLTMwKTsKICAgICAgICAgICAgICAgICAgICBsb2dFbC50ZXh0Q29udGVudCA9IHRhaWwuam9pbignXG4nKTsKICAgICAgICAgICAgICAgICAgICBsb2dFbC5zY3JvbGxUb3AgPSBsb2dFbC5zY3JvbGxIZWlnaHQ7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gY2F0Y2ggeyAvKiBpZ25vcmUgKi8gfQogICAgICAgIH0sIExPR19SQVRFX01TKTsKICAgIH0KCiAgICBmdW5jdGlvbiBzdG9wUG9sbGluZygpIHsKICAgICAgICBpZiAocG9sbEludGVydmFsKSB7IGNsZWFySW50ZXJ2YWwocG9sbEludGVydmFsKTsgcG9sbEludGVydmFsID0gbnVsbDsgfQogICAgICAgIGlmIChsb2dJbnRlcnZhbCkgeyBjbGVhckludGVydmFsKGxvZ0ludGVydmFsKTsgbG9nSW50ZXJ2YWwgPSBudWxsOyB9CiAgICB9CgogICAgLy8g4pSA4pSAIFByb2dyZXNzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGZ1bmN0aW9uIHNob3dQcm9ncmVzcygpIHsKICAgICAgICBjb25zdCBwcm9ncmVzc0VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1wcm9ncmVzcycpOwogICAgICAgIGlmIChwcm9ncmVzc0VsKSBwcm9ncmVzc0VsLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpOwogICAgfQoKICAgIGZ1bmN0aW9uIHVwZGF0ZVByb2dyZXNzKGNvbXBsZXRlZCwgdG90YWwsIGVsYXBzZWRNcykgewogICAgICAgIGNvbnN0IHBjdCA9IHRvdGFsID4gMCA/IChjb21wbGV0ZWQgLyB0b3RhbCkgKiAxMDAgOiAwOwogICAgICAgIGNvbnN0IGZpbGxFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItcHJvZ3Jlc3MtZmlsbCcpOwogICAgICAgIGNvbnN0IGNvdW50RWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXByb2dyZXNzLWNvdW50Jyk7CiAgICAgICAgY29uc3QgZXRhRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXByb2dyZXNzLWV0YScpOwoKICAgICAgICBpZiAoZmlsbEVsKSBmaWxsRWwuc3R5bGUud2lkdGggPSBwY3QgKyAnJSc7CiAgICAgICAgaWYgKGNvdW50RWwpIGNvdW50RWwudGV4dENvbnRlbnQgPSBgJHtjb21wbGV0ZWR9IC8gJHt0b3RhbH0gZ2FtZXNgOwoKICAgICAgICBpZiAoY29tcGxldGVkID4gMCAmJiBlbGFwc2VkTXMgPiAwKSB7CiAgICAgICAgICAgIGNvbnN0IHJhdGUgPSBjb21wbGV0ZWQgLyAoZWxhcHNlZE1zIC8gMTAwMCk7CiAgICAgICAgICAgIGNvbnN0IHJlbWFpbmluZyA9ICh0b3RhbCAtIGNvbXBsZXRlZCkgLyByYXRlOwogICAgICAgICAgICBpZiAoZXRhRWwpIGV0YUVsLnRleHRDb250ZW50ID0gcmVtYWluaW5nID4gMCA/IGB+JHtNYXRoLmNlaWwocmVtYWluaW5nKX1zIHJlbWFpbmluZ2AgOiAnRG9uZSc7CiAgICAgICAgfQogICAgfQoKICAgIC8vIOKUgOKUgCBMb2cgUGFuZWwg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgZnVuY3Rpb24gY2xlYXJMb2coKSB7CiAgICAgICAgY29uc3QgbG9nRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWxvZy1jb250ZW50Jyk7CiAgICAgICAgaWYgKGxvZ0VsKSBsb2dFbC50ZXh0Q29udGVudCA9ICcnOwogICAgfQoKICAgIGZ1bmN0aW9uIGFwcGVuZExvZyhtc2cpIHsKICAgICAgICBjb25zdCBsb2dFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItbG9nLWNvbnRlbnQnKTsKICAgICAgICBjb25zdCBsb2dDb250YWluZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWxvZycpOwogICAgICAgIGlmICghbG9nRWwpIHJldHVybjsKICAgICAgICBsb2dFbC50ZXh0Q29udGVudCArPSBtc2cgKyAnXG4nOwogICAgICAgIGxvZ0VsLnNjcm9sbFRvcCA9IGxvZ0VsLnNjcm9sbEhlaWdodDsKICAgICAgICBpZiAobG9nQ29udGFpbmVyKSBsb2dDb250YWluZXIuY2xhc3NMaXN0LmFkZCgnYWN0aXZlJyk7CiAgICB9CgogICAgZnVuY3Rpb24gdG9nZ2xlTG9nKCkgewogICAgICAgIGNvbnN0IGxvZ0NvbnRhaW5lciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItbG9nJyk7CiAgICAgICAgY29uc3QgdG9nZ2xlQnRuID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1sb2ctdG9nZ2xlJyk7CiAgICAgICAgaWYgKGxvZ0NvbnRhaW5lcikgewogICAgICAgICAgICBsb2dDb250YWluZXIuY2xhc3NMaXN0LnRvZ2dsZSgnY29sbGFwc2VkJyk7CiAgICAgICAgICAgIGlmICh0b2dnbGVCdG4pIHRvZ2dsZUJ0bi50ZXh0Q29udGVudCA9IGxvZ0NvbnRhaW5lci5jbGFzc0xpc3QuY29udGFpbnMoJ2NvbGxhcHNlZCcpID8gJ+KWticgOiAn4pa8JzsKICAgICAgICB9CiAgICB9CgogICAgLy8g4pSA4pSAIERpc3BsYXkgUmVzdWx0cyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBmdW5jdGlvbiBkaXNwbGF5UmVzdWx0cyhyZXN1bHQpIHsKICAgICAgICBjb25zdCByZXN1bHRzRWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXJlc3VsdHMnKTsKICAgICAgICBjb25zdCBjYXJkc0VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1zdGF0cy1jYXJkcycpOwogICAgICAgIGNvbnN0IGdsb2JhbEVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1nbG9iYWwtc3RhdHMnKTsKCiAgICAgICAgaWYgKCFyZXN1bHRzRWwgfHwgIWNhcmRzRWwgfHwgIWdsb2JhbEVsKSByZXR1cm47CiAgICAgICAgcmVzdWx0c0VsLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpOwoKICAgICAgICAvLyBTb3J0IGRlY2tzIGJ5IHdpbiByYXRlIGZvciByYW5raW5nCiAgICAgICAgY29uc3QgcmFua2VkID0gWy4uLnJlc3VsdC5zdW1tYXJ5LnBlckRlY2tdLnNvcnQoKGEsIGIpID0+IGIud2luUmF0ZSAtIGEud2luUmF0ZSk7CiAgICAgICAgY29uc3QgcmFua01hcCA9IHt9OwogICAgICAgIHJhbmtlZC5mb3JFYWNoKChkLCBpKSA9PiB7IHJhbmtNYXBbZC5zZWF0SW5kZXhdID0gaSArIDE7IH0pOwoKICAgICAgICAvLyBSZW5kZXIgc3RhdCBjYXJkcwogICAgICAgIGNhcmRzRWwuaW5uZXJIVE1MID0gcmVzdWx0LnN1bW1hcnkucGVyRGVjay5tYXAoZHMgPT4gewogICAgICAgICAgICBjb25zdCByYW5rID0gcmFua01hcFtkcy5zZWF0SW5kZXhdOwogICAgICAgICAgICBjb25zdCBpc1dpbm5lciA9IHJhbmsgPT09IDE7CiAgICAgICAgICAgIGNvbnN0IGRlY2tJbmZvID0gcmVzdWx0LmRlY2tzW2RzLnNlYXRJbmRleF07CiAgICAgICAgICAgIGNvbnN0IGJyZWFrZG93biA9IGRzLndpbkNvbmRpdGlvbkJyZWFrZG93biB8fCB7fTsKCiAgICAgICAgICAgIC8vIHYzOiBTb3VyY2UgYmFkZ2UgaW4gcmVzdWx0cwogICAgICAgICAgICBjb25zdCBzb3VyY2UgPSBkZWNrSW5mby5zb3VyY2UgfHwgJyc7CiAgICAgICAgICAgIGNvbnN0IHNvdXJjZVVybCA9IGRlY2tJbmZvLnNvdXJjZVVybCB8fCAnJzsKICAgICAgICAgICAgY29uc3Qgc291cmNlQmFkZ2VIdG1sID0gc291cmNlCiAgICAgICAgICAgICAgICA/IGA8ZGl2IGNsYXNzPSJsYWItcmVzdWx0LXNvdXJjZSI+JHtyZW5kZXJTb3VyY2VCYWRnZShzb3VyY2UsIHNvdXJjZVVybCl9PC9kaXY+YAogICAgICAgICAgICAgICAgOiAnJzsKCiAgICAgICAgICAgIHJldHVybiBgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1zdGF0LWNhcmQgJHtpc1dpbm5lciA/ICd3aW5uZXInIDogJyd9Ij4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9InJhbmsiPiMke3Jhbmt9PC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWRlY2stbmFtZSI+JHtlc2NhcGVIdG1sKGRzLmRlY2tOYW1lKX08L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtY29tbWFuZGVyIj4ke2VzY2FwZUh0bWwoZGVja0luZm8uY29tbWFuZGVyTmFtZSl9PC9kaXY+CiAgICAgICAgICAgICAgICAke3NvdXJjZUJhZGdlSHRtbH0KCiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItc3RhdC1yb3ciPgogICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJzdGF0LWxhYmVsIj5XaW4gUmF0ZTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ic3RhdC12YWx1ZSBoaWdobGlnaHQiPiR7KGRzLndpblJhdGUgKiAxMDApLnRvRml4ZWQoMSl9JTwvc3Bhbj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXN0YXQtcm93Ij4KICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ic3RhdC1sYWJlbCI+UmVjb3JkPC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJzdGF0LXZhbHVlIj4ke2RzLndpbnN9VyAvICR7ZHMubG9zc2VzfUwgLyAke2RzLmRyYXdzfUQ8L3NwYW4+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1zdGF0LXJvdyI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9InN0YXQtbGFiZWwiPkF2ZyBUdXJucyB0byBXaW48L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9InN0YXQtdmFsdWUiPiR7ZHMuYXZnVHVybnNUb1dpbiAhPT0gbnVsbCA/IGRzLmF2Z1R1cm5zVG9XaW4udG9GaXhlZCgxKSA6ICdOL0EnfTwvc3Bhbj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXN0YXQtcm93Ij4KICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ic3RhdC1sYWJlbCI+QXZnIE11bGxpZ2Fuczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ic3RhdC12YWx1ZSI+JHtkcy5hdmdNdWxsaWdhbnMudG9GaXhlZCgyKX08L3NwYW4+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1zdGF0LXJvdyI+CiAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9InN0YXQtbGFiZWwiPkF2ZyBGaW5hbCBMaWZlPC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJzdGF0LXZhbHVlIj4ke2RzLmF2Z0ZpbmFsTGlmZS50b0ZpeGVkKDEpfTwvc3Bhbj4KICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi13aW5yYXRlLWJhciI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXdpbnJhdGUtZmlsbCBzZWF0LSR7ZHMuc2VhdEluZGV4fS1jb2xvciIKICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJ3aWR0aDogJHsoZHMud2luUmF0ZSAqIDEwMCkudG9GaXhlZCgxKX0lIj48L2Rpdj4KICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICR7ZHMud2lucyA+IDAgPyBgCiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItd2luLWNvbmRpdGlvbnMiPgogICAgICAgICAgICAgICAgICAgICR7T2JqZWN0LmVudHJpZXMoYnJlYWtkb3duKS5maWx0ZXIoKFtfLCB2XSkgPT4gdiA+IDApLm1hcCgoW2ssIHZdKSA9PiBgCiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItd2MtY2hpcCI+PHNwYW4gY2xhc3M9IndjLWNvdW50Ij4ke3Z9PC9zcGFuPiAke2Zvcm1hdFdpbkNvbmRpdGlvbihrKX08L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgYCkuam9pbignJyl9CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIGAgOiAnJ30KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIGA7CiAgICAgICAgfSkuam9pbignJyk7CgogICAgICAgIC8vIFJlbmRlciBnbG9iYWwgc3RhdHMKICAgICAgICBjb25zdCBzID0gcmVzdWx0LnN1bW1hcnk7CiAgICAgICAgY29uc3QgbSA9IHJlc3VsdC5tZXRhZGF0YTsKICAgICAgICBnbG9iYWxFbC5pbm5lckhUTUwgPSBgCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1nbG9iYWwtc3RhdCI+CiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iZy12YWx1ZSI+JHttLmNvbXBsZXRlZEdhbWVzfTwvc3Bhbj4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJnLWxhYmVsIj5HYW1lczwvc3Bhbj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1nbG9iYWwtc3RhdCI+CiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iZy12YWx1ZSI+JHtzLmF2Z0dhbWVUdXJucy50b0ZpeGVkKDEpfTwvc3Bhbj4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJnLWxhYmVsIj5BdmcgVHVybnM8L3NwYW4+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItZ2xvYmFsLXN0YXQiPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImctdmFsdWUiPiR7KG0uZWxhcHNlZE1zIC8gMTAwMCkudG9GaXhlZCgxKX1zPC9zcGFuPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImctbGFiZWwiPldhbGwgVGltZTwvc3Bhbj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1nbG9iYWwtc3RhdCI+CiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iZy12YWx1ZSI+JHtzLnNpbXNQZXJTZWNvbmQudG9GaXhlZCgyKX08L3NwYW4+CiAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iZy1sYWJlbCI+U2ltcy9zZWM8L3NwYW4+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItZ2xvYmFsLXN0YXQiPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImctdmFsdWUiPiR7bS50aHJlYWRzfTwvc3Bhbj4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJnLWxhYmVsIj5UaHJlYWRzPC9zcGFuPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWdsb2JhbC1zdGF0Ij4KICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJnLXZhbHVlIj4ke20uZW5naW5lVmVyc2lvbiB8fCAnPyd9PC9zcGFuPgogICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImctbGFiZWwiPkVuZ2luZTwvc3Bhbj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgYDsKCiAgICAgICAgLy8gUmVuZGVyIGVuaGFuY2VkIHJlc3VsdCBzZWN0aW9ucwogICAgICAgIHJlbmRlckNoYXJ0cyhyZXN1bHQpOwogICAgICAgIHJlbmRlckhlYWRUb0hlYWQocmVzdWx0KTsKICAgICAgICByZW5kZXJDb21iYXRTdGF0cyhyZXN1bHQpOwogICAgICAgIHJlbmRlckdhbWVMb2cocmVzdWx0KTsKICAgIH0KCiAgICAvLyDilIDilIAgVGFiIFN3aXRjaGluZyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICAvLyDilIDilIAgQW5hbHl0aWNzICYgVHJlbmRzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGFzeW5jIGZ1bmN0aW9uIGxvYWRBbmFseXRpY3MoKSB7CiAgICAgICAgY29uc3QgY29udGVudEVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1hbmFseXRpY3MtY29udGVudCcpOwogICAgICAgIGlmICghY29udGVudEVsKSByZXR1cm47CgogICAgICAgIGNvbnN0IGZpbGxlZERlY2tzID0gc2VsZWN0ZWREZWNrcy5maWx0ZXIoZCA9PiBkKTsKICAgICAgICBpZiAoZmlsbGVkRGVja3MubGVuZ3RoID09PSAwKSB7CiAgICAgICAgICAgIGNvbnRlbnRFbC5pbm5lckhUTUwgPSAnPHAgY2xhc3M9ImxhYi1lbXB0eS1oaW50Ij5TZWxlY3QgZGVja3MgdG8gc2VlIGFuYWx5dGljcy48L3A+JzsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgY29udGVudEVsLmlubmVySFRNTCA9ICc8ZGl2IGNsYXNzPSJsYWItbG9hZGluZy1oaW50Ij48ZGl2IGNsYXNzPSJzcGlubmVyIj48L2Rpdj4gTG9hZGluZyBhbmFseXRpY3MuLi48L2Rpdj4nOwoKICAgICAgICB0cnkgewogICAgICAgICAgICBjb25zdCByZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwoCiAgICAgICAgICAgICAgICBmaWxsZWREZWNrcy5tYXAoYXN5bmMgKGRlY2tOYW1lKSA9PiB7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvYW5hbHl0aWNzLyR7ZW5jb2RlVVJJQ29tcG9uZW50KGRlY2tOYW1lKX1gKTsKICAgICAgICAgICAgICAgICAgICBpZiAoIXJlcy5vaykgcmV0dXJuIHsgZGVja05hbWUsIGVycm9yOiB0cnVlIH07CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGF3YWl0IHJlcy5qc29uKCk7CiAgICAgICAgICAgICAgICB9KQogICAgICAgICAgICApOwoKICAgICAgICAgICAgbGV0IGh0bWwgPSAnPGRpdiBjbGFzcz0ibGFiLWFuYWx5dGljcy1ncmlkIj4nOwogICAgICAgICAgICBmb3IgKGNvbnN0IGRhdGEgb2YgcmVzdWx0cykgewogICAgICAgICAgICAgICAgaWYgKGRhdGEuZXJyb3IpIHsKICAgICAgICAgICAgICAgICAgICBodG1sICs9IGA8ZGl2IGNsYXNzPSJsYWItYW5hbHl0aWNzLWNhcmQgbGFiLWFuYWx5dGljcy1lcnJvciI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1hbmFseXRpY3MtbmFtZSI+JHtlc2NhcGVIdG1sKGRhdGEuZGVja05hbWUpfTwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItYW5hbHl0aWNzLWVyci1tc2ciPkFuYWx5dGljcyB1bmF2YWlsYWJsZTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PmA7CiAgICAgICAgICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgLy8gQ291bnQgY2FyZHMgYnkgc2VjdGlvbgogICAgICAgICAgICAgICAgY29uc3Qgc2VjdGlvbnMgPSB7fTsKICAgICAgICAgICAgICAgIGZvciAoY29uc3QgY2FyZCBvZiAoZGF0YS5jYXJkcyB8fCBbXSkpIHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBzZWMgPSBjYXJkLnNlY3Rpb24gfHwgJ01haW4nOwogICAgICAgICAgICAgICAgICAgIHNlY3Rpb25zW3NlY10gPSAoc2VjdGlvbnNbc2VjXSB8fCAwKSArIGNhcmQucXVhbnRpdHk7CiAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgaHRtbCArPSBgPGRpdiBjbGFzcz0ibGFiLWFuYWx5dGljcy1jYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItYW5hbHl0aWNzLW5hbWUiPiR7ZXNjYXBlSHRtbChkYXRhLmRlY2tOYW1lKX08L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItYW5hbHl0aWNzLWNvbW1hbmRlciI+JHtlc2NhcGVIdG1sKGRhdGEuY29tbWFuZGVyTmFtZSB8fCAnVW5rbm93bicpfTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1hbmFseXRpY3Mtc3RhdCI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItYW5hbHl0aWNzLWxhYmVsIj5Ub3RhbCBDYXJkczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1hbmFseXRpY3MtdmFsdWUiPiR7ZGF0YS50b3RhbENhcmRzIHx8IDB9PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1hbmFseXRpY3Mtc3RhdCI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItYW5hbHl0aWNzLWxhYmVsIj5VbmlxdWUgQ2FyZHM8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItYW5hbHl0aWNzLXZhbHVlIj4ke2RhdGEuY2FyZENvdW50IHx8IDB9PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1hbmFseXRpY3Mtc2VjdGlvbnMiPgogICAgICAgICAgICAgICAgICAgICAgICAke09iamVjdC5lbnRyaWVzKHNlY3Rpb25zKS5tYXAoKFtzZWMsIGNvdW50XSkgPT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGA8c3BhbiBjbGFzcz0ibGFiLWFuYWx5dGljcy1zZWN0aW9uLXBpbGwiPiR7ZXNjYXBlSHRtbChzZWMpfTogJHtjb3VudH08L3NwYW4+YAogICAgICAgICAgICAgICAgICAgICAgICApLmpvaW4oJycpfQogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+YDsKICAgICAgICAgICAgfQogICAgICAgICAgICBodG1sICs9ICc8L2Rpdj4nOwogICAgICAgICAgICBjb250ZW50RWwuaW5uZXJIVE1MID0gaHRtbDsKCiAgICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgICAgIGNvbnRlbnRFbC5pbm5lckhUTUwgPSAnPGRpdiBjbGFzcz0ibGFiLWVtcHR5LWhpbnQiPkVycm9yIGxvYWRpbmcgYW5hbHl0aWNzOiAnICsgZXNjYXBlSHRtbChlcnIubWVzc2FnZSkgKyAnPC9kaXY+JzsKICAgICAgICB9CiAgICB9CgogICAgYXN5bmMgZnVuY3Rpb24gbG9hZFRyZW5kcygpIHsKICAgICAgICBjb25zdCBjb250ZW50RWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLXRyZW5kcy1jb250ZW50Jyk7CiAgICAgICAgY29uc3QgY2FudmFzID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1jaGFydC10cmVuZHMnKTsKICAgICAgICBpZiAoIWNvbnRlbnRFbCkgcmV0dXJuOwoKICAgICAgICBjb25zdCBmaWxsZWREZWNrcyA9IHNlbGVjdGVkRGVja3MuZmlsdGVyKGQgPT4gZCk7CiAgICAgICAgaWYgKGZpbGxlZERlY2tzLmxlbmd0aCA9PT0gMCkgewogICAgICAgICAgICBjb250ZW50RWwuaW5uZXJIVE1MID0gJzxwIGNsYXNzPSJsYWItZW1wdHktaGludCI+U2VsZWN0IGRlY2tzIHRvIHNlZSB0cmVuZHMuPC9wPic7CiAgICAgICAgICAgIGlmIChjYW52YXMpIGNhbnZhcy5zdHlsZS5kaXNwbGF5ID0gJ25vbmUnOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQoKICAgICAgICBjb250ZW50RWwuaW5uZXJIVE1MID0gJzxkaXYgY2xhc3M9ImxhYi1sb2FkaW5nLWhpbnQiPjxkaXYgY2xhc3M9InNwaW5uZXIiPjwvZGl2PiBMb2FkaW5nIHRyZW5kcy4uLjwvZGl2Pic7CgogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbCgKICAgICAgICAgICAgICAgIGZpbGxlZERlY2tzLm1hcChhc3luYyAoZGVja05hbWUpID0+IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChgJHtBUElfQkFTRX0vYXBpL2xhYi90cmVuZHMvJHtlbmNvZGVVUklDb21wb25lbnQoZGVja05hbWUpfWApOwogICAgICAgICAgICAgICAgICAgIGlmICghcmVzLm9rKSByZXR1cm4geyBkZWNrTmFtZSwgaGlzdG9yeTogW10gfTsKICAgICAgICAgICAgICAgICAgICByZXR1cm4gYXdhaXQgcmVzLmpzb24oKTsKICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICk7CgogICAgICAgICAgICAvLyBDaGVjayBpZiBhbnkgZGVjayBoYXMgaGlzdG9yeQogICAgICAgICAgICBjb25zdCBhbnlIaXN0b3J5ID0gcmVzdWx0cy5zb21lKHIgPT4gKHIuaGlzdG9yeSB8fCBbXSkubGVuZ3RoID4gMCk7CiAgICAgICAgICAgIGlmICghYW55SGlzdG9yeSkgewogICAgICAgICAgICAgICAgY29udGVudEVsLmlubmVySFRNTCA9ICc8cCBjbGFzcz0ibGFiLWVtcHR5LWhpbnQiPk5vIGJhdGNoIGhpc3RvcnkgZm91bmQuIFJ1biBtdWx0aXBsZSBiYXRjaGVzIHRvIHRyYWNrIHRyZW5kcy48L3A+JzsKICAgICAgICAgICAgICAgIGlmIChjYW52YXMpIGNhbnZhcy5zdHlsZS5kaXNwbGF5ID0gJ25vbmUnOwogICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBCdWlsZCBzdW1tYXJ5IGNhcmRzCiAgICAgICAgICAgIGxldCBodG1sID0gJzxkaXYgY2xhc3M9ImxhYi10cmVuZHMtc3VtbWFyeSI+JzsKICAgICAgICAgICAgZm9yIChjb25zdCBkYXRhIG9mIHJlc3VsdHMpIHsKICAgICAgICAgICAgICAgIGNvbnN0IGhpc3RvcnkgPSBkYXRhLmhpc3RvcnkgfHwgW107CiAgICAgICAgICAgICAgICBpZiAoaGlzdG9yeS5sZW5ndGggPT09IDApIGNvbnRpbnVlOwogICAgICAgICAgICAgICAgY29uc3QgbGF0ZXN0ID0gaGlzdG9yeVtoaXN0b3J5Lmxlbmd0aCAtIDFdOwogICAgICAgICAgICAgICAgY29uc3QgYmVzdCA9IE1hdGgubWF4KC4uLmhpc3RvcnkubWFwKGggPT4gaC53aW5SYXRlKSk7CiAgICAgICAgICAgICAgICBjb25zdCBhdmcgPSBoaXN0b3J5LnJlZHVjZSgoYSwgaCkgPT4gYSArIGgud2luUmF0ZSwgMCkgLyBoaXN0b3J5Lmxlbmd0aDsKCiAgICAgICAgICAgICAgICBodG1sICs9IGA8ZGl2IGNsYXNzPSJsYWItdHJlbmRzLWNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10cmVuZHMtZGVjay1uYW1lIj4ke2VzY2FwZUh0bWwoZGF0YS5kZWNrTmFtZSl9PC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0cyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10cmVuZHMtc3RhdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0LWxhYmVsIj5MYXRlc3Q8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0LXZhbHVlIj4ke2xhdGVzdC53aW5SYXRlLnRvRml4ZWQoMSl9JTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10cmVuZHMtc3RhdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0LWxhYmVsIj5CZXN0PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi10cmVuZHMtc3RhdC12YWx1ZSI+JHtiZXN0LnRvRml4ZWQoMSl9JTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi10cmVuZHMtc3RhdCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0LWxhYmVsIj5BdmVyYWdlPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi10cmVuZHMtc3RhdC12YWx1ZSI+JHthdmcudG9GaXhlZCgxKX0lPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItdHJlbmRzLXN0YXQtbGFiZWwiPkJhdGNoZXM8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLXRyZW5kcy1zdGF0LXZhbHVlIj4ke2hpc3RvcnkubGVuZ3RofTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj5gOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGh0bWwgKz0gJzwvZGl2Pic7CiAgICAgICAgICAgIGNvbnRlbnRFbC5pbm5lckhUTUwgPSBodG1sOwoKICAgICAgICAgICAgLy8gRHJhdyB0cmVuZHMgY2hhcnQgaWYgY2FudmFzIGF2YWlsYWJsZQogICAgICAgICAgICBpZiAoY2FudmFzICYmIHR5cGVvZiBDaGFydCAhPT0gJ3VuZGVmaW5lZCcpIHsKICAgICAgICAgICAgICAgIGNhbnZhcy5zdHlsZS5kaXNwbGF5ID0gJ2Jsb2NrJzsKICAgICAgICAgICAgICAgIGRyYXdUcmVuZHNDaGFydChyZXN1bHRzLCBjYW52YXMpOwogICAgICAgICAgICB9CgogICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICBjb250ZW50RWwuaW5uZXJIVE1MID0gJzxkaXYgY2xhc3M9ImxhYi1lbXB0eS1oaW50Ij5FcnJvciBsb2FkaW5nIHRyZW5kczogJyArIGVzY2FwZUh0bWwoZXJyLm1lc3NhZ2UpICsgJzwvZGl2Pic7CiAgICAgICAgfQogICAgfQoKICAgIGZ1bmN0aW9uIGRyYXdUcmVuZHNDaGFydChyZXN1bHRzLCBjYW52YXMpIHsKICAgICAgICBjb25zdCBjdHggPSBjYW52YXMuZ2V0Q29udGV4dCgnMmQnKTsKICAgICAgICBjdHguY2xlYXJSZWN0KDAsIDAsIGNhbnZhcy53aWR0aCwgY2FudmFzLmhlaWdodCk7CgogICAgICAgIGNvbnN0IGNvbG9ycyA9IFsnIzU4YTZmZicsICcjZjg1MTQ5JywgJyMzZmI5NTAnLCAnI2E3OGJmYScsICcjZmJiZjI0J107CiAgICAgICAgY29uc3QgcGFkZGluZyA9IHsgdG9wOiAzMCwgcmlnaHQ6IDIwLCBib3R0b206IDQwLCBsZWZ0OiA1MCB9OwogICAgICAgIGNvbnN0IHcgPSBjYW52YXMud2lkdGggLSBwYWRkaW5nLmxlZnQgLSBwYWRkaW5nLnJpZ2h0OwogICAgICAgIGNvbnN0IGggPSBjYW52YXMuaGVpZ2h0IC0gcGFkZGluZy50b3AgLSBwYWRkaW5nLmJvdHRvbTsKCiAgICAgICAgLy8gRmluZCBtYXggZGF0YSBwb2ludHMKICAgICAgICBsZXQgbWF4UG9pbnRzID0gMDsKICAgICAgICBmb3IgKGNvbnN0IHIgb2YgcmVzdWx0cykgewogICAgICAgICAgICBtYXhQb2ludHMgPSBNYXRoLm1heChtYXhQb2ludHMsIChyLmhpc3RvcnkgfHwgW10pLmxlbmd0aCk7CiAgICAgICAgfQogICAgICAgIGlmIChtYXhQb2ludHMgPCAyKSByZXR1cm47CgogICAgICAgIC8vIERyYXcgYXhlcwogICAgICAgIGN0eC5zdHJva2VTdHlsZSA9ICcjMmEyZDNhJzsKICAgICAgICBjdHgubGluZVdpZHRoID0gMTsKICAgICAgICBjdHguYmVnaW5QYXRoKCk7CiAgICAgICAgY3R4Lm1vdmVUbyhwYWRkaW5nLmxlZnQsIHBhZGRpbmcudG9wKTsKICAgICAgICBjdHgubGluZVRvKHBhZGRpbmcubGVmdCwgY2FudmFzLmhlaWdodCAtIHBhZGRpbmcuYm90dG9tKTsKICAgICAgICBjdHgubGluZVRvKGNhbnZhcy53aWR0aCAtIHBhZGRpbmcucmlnaHQsIGNhbnZhcy5oZWlnaHQgLSBwYWRkaW5nLmJvdHRvbSk7CiAgICAgICAgY3R4LnN0cm9rZSgpOwoKICAgICAgICAvLyBZLWF4aXMgbGFiZWxzICgwLTEwMCUpCiAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICcjOGI5MGEwJzsKICAgICAgICBjdHguZm9udCA9ICcxMXB4IEludGVyLCBzYW5zLXNlcmlmJzsKICAgICAgICBjdHgudGV4dEFsaWduID0gJ3JpZ2h0JzsKICAgICAgICBmb3IgKGxldCBwY3QgPSAwOyBwY3QgPD0gMTAwOyBwY3QgKz0gMjUpIHsKICAgICAgICAgICAgY29uc3QgeSA9IGNhbnZhcy5oZWlnaHQgLSBwYWRkaW5nLmJvdHRvbSAtIChwY3QgLyAxMDApICogaDsKICAgICAgICAgICAgY3R4LmZpbGxUZXh0KHBjdCArICclJywgcGFkZGluZy5sZWZ0IC0gOCwgeSArIDQpOwogICAgICAgICAgICAvLyBHcmlkIGxpbmUKICAgICAgICAgICAgY3R4LnN0cm9rZVN0eWxlID0gJyMxZTIxMzAnOwogICAgICAgICAgICBjdHguYmVnaW5QYXRoKCk7CiAgICAgICAgICAgIGN0eC5tb3ZlVG8ocGFkZGluZy5sZWZ0LCB5KTsKICAgICAgICAgICAgY3R4LmxpbmVUbyhjYW52YXMud2lkdGggLSBwYWRkaW5nLnJpZ2h0LCB5KTsKICAgICAgICAgICAgY3R4LnN0cm9rZSgpOwogICAgICAgIH0KCiAgICAgICAgLy8gRHJhdyBsaW5lcyBmb3IgZWFjaCBkZWNrCiAgICAgICAgcmVzdWx0cy5mb3JFYWNoKChkYXRhLCBkaSkgPT4gewogICAgICAgICAgICBjb25zdCBoaXN0b3J5ID0gZGF0YS5oaXN0b3J5IHx8IFtdOwogICAgICAgICAgICBpZiAoaGlzdG9yeS5sZW5ndGggPCAyKSByZXR1cm47CgogICAgICAgICAgICBjdHguc3Ryb2tlU3R5bGUgPSBjb2xvcnNbZGkgJSBjb2xvcnMubGVuZ3RoXTsKICAgICAgICAgICAgY3R4LmxpbmVXaWR0aCA9IDI7CiAgICAgICAgICAgIGN0eC5iZWdpblBhdGgoKTsKCiAgICAgICAgICAgIGhpc3RvcnkuZm9yRWFjaCgocG9pbnQsIGkpID0+IHsKICAgICAgICAgICAgICAgIGNvbnN0IHggPSBwYWRkaW5nLmxlZnQgKyAoaSAvIChtYXhQb2ludHMgLSAxKSkgKiB3OwogICAgICAgICAgICAgICAgY29uc3QgeSA9IGNhbnZhcy5oZWlnaHQgLSBwYWRkaW5nLmJvdHRvbSAtIChwb2ludC53aW5SYXRlIC8gMTAwKSAqIGg7CiAgICAgICAgICAgICAgICBpZiAoaSA9PT0gMCkgY3R4Lm1vdmVUbyh4LCB5KTsKICAgICAgICAgICAgICAgIGVsc2UgY3R4LmxpbmVUbyh4LCB5KTsKICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIGN0eC5zdHJva2UoKTsKCiAgICAgICAgICAgIC8vIERyYXcgcG9pbnRzCiAgICAgICAgICAgIGN0eC5maWxsU3R5bGUgPSBjb2xvcnNbZGkgJSBjb2xvcnMubGVuZ3RoXTsKICAgICAgICAgICAgaGlzdG9yeS5mb3JFYWNoKChwb2ludCwgaSkgPT4gewogICAgICAgICAgICAgICAgY29uc3QgeCA9IHBhZGRpbmcubGVmdCArIChpIC8gKG1heFBvaW50cyAtIDEpKSAqIHc7CiAgICAgICAgICAgICAgICBjb25zdCB5ID0gY2FudmFzLmhlaWdodCAtIHBhZGRpbmcuYm90dG9tIC0gKHBvaW50LndpblJhdGUgLyAxMDApICogaDsKICAgICAgICAgICAgICAgIGN0eC5iZWdpblBhdGgoKTsKICAgICAgICAgICAgICAgIGN0eC5hcmMoeCwgeSwgMywgMCwgTWF0aC5QSSAqIDIpOwogICAgICAgICAgICAgICAgY3R4LmZpbGwoKTsKICAgICAgICAgICAgfSk7CgogICAgICAgICAgICAvLyBMYWJlbAogICAgICAgICAgICBjb25zdCBsYXN0UHQgPSBoaXN0b3J5W2hpc3RvcnkubGVuZ3RoIC0gMV07CiAgICAgICAgICAgIGNvbnN0IGx4ID0gcGFkZGluZy5sZWZ0ICsgKChoaXN0b3J5Lmxlbmd0aCAtIDEpIC8gKG1heFBvaW50cyAtIDEpKSAqIHc7CiAgICAgICAgICAgIGNvbnN0IGx5ID0gY2FudmFzLmhlaWdodCAtIHBhZGRpbmcuYm90dG9tIC0gKGxhc3RQdC53aW5SYXRlIC8gMTAwKSAqIGg7CiAgICAgICAgICAgIGN0eC5maWxsU3R5bGUgPSBjb2xvcnNbZGkgJSBjb2xvcnMubGVuZ3RoXTsKICAgICAgICAgICAgY3R4LnRleHRBbGlnbiA9ICdsZWZ0JzsKICAgICAgICAgICAgY3R4LmZvbnQgPSAnMTFweCBJbnRlciwgc2Fucy1zZXJpZic7CiAgICAgICAgICAgIGN0eC5maWxsVGV4dChkYXRhLmRlY2tOYW1lLCBseCArIDYsIGx5ICsgNCk7CiAgICAgICAgfSk7CiAgICB9CgogICAgLy8g4pSA4pSAIFByZWNvbiBSZWZyZXNoIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKICAgIGFzeW5jIGZ1bmN0aW9uIHJlZnJlc2hQcmVjb25zKCkgewogICAgICAgIGNvbnN0IHN0YXR1c0VsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1wcmVjb24tc3RhdHVzJyk7CgogICAgICAgIGlmICghYmFja2VuZEF2YWlsYWJsZSkgewogICAgICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLCAnUHJlY29uIHJlZnJlc2ggcmVxdWlyZXMgYSBydW5uaW5nIGJhY2tlbmQuJywgJ2Vycm9yJyk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsICdSZWZyZXNoaW5nIHByZWNvbiBkYXRhYmFzZSBmcm9tIEdpdEh1Yi4uLicsICdsb2FkaW5nJyk7CgogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGAke0FQSV9CQVNFfS9hcGkvbGFiL3ByZWNvbnMvcmVmcmVzaGAsIHsKICAgICAgICAgICAgICAgIG1ldGhvZDogJ1BPU1QnLAogICAgICAgICAgICB9KTsKICAgICAgICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlcy5qc29uKCk7CgogICAgICAgICAgICBpZiAoIXJlcy5vaykgewogICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGRhdGEuZGV0YWlsIHx8ICdSZWZyZXNoIGZhaWxlZCcpOwogICAgICAgICAgICB9CgogICAgICAgICAgICBzaG93SW1wb3J0U3RhdHVzKHN0YXR1c0VsLAogICAgICAgICAgICAgICAgZGF0YS5tZXNzYWdlIHx8ICgnRG93bmxvYWRlZCAnICsgZGF0YS50b3RhbCArICcgcHJlY29uIGRlY2tzJyksCiAgICAgICAgICAgICAgICAnc3VjY2VzcycKICAgICAgICAgICAgKTsKCiAgICAgICAgICAgIC8vIFJlbG9hZCBwcmVjb24gbGlzdAogICAgICAgICAgICBhd2FpdCBsb2FkUHJlY29ucygpOwogICAgICAgICAgICByZW5kZXJQcmVjb25HcmlkKHByZWNvbkRlY2tzKTsKCiAgICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgICAgIHNob3dJbXBvcnRTdGF0dXMoc3RhdHVzRWwsICdFcnJvcjogJyArIGVyci5tZXNzYWdlLCAnZXJyb3InKTsKICAgICAgICB9CiAgICB9CgogICAgICAgIGZ1bmN0aW9uIHN3aXRjaFRhYih0YWJJZCkgewogICAgICAgIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJy5sYWItdGFiJykuZm9yRWFjaChidG4gPT4gewogICAgICAgICAgICBidG4uY2xhc3NMaXN0LnRvZ2dsZSgnYWN0aXZlJywgYnRuLmRhdGFzZXQudGFiID09PSB0YWJJZCk7CiAgICAgICAgfSk7CiAgICAgICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLmxhYi10YWItcGFuZWwnKS5mb3JFYWNoKHBhbmVsID0+IHsKICAgICAgICAgICAgcGFuZWwuY2xhc3NMaXN0LnRvZ2dsZSgnYWN0aXZlJywgcGFuZWwuaWQgPT09IHRhYklkKTsKICAgICAgICB9KTsKCiAgICAgICAgLy8gTG9hZCBkYXRhIGZvciBhbmFseXRpY3MvdHJlbmRzIHRhYnMgb24gZGVtYW5kCiAgICAgICAgaWYgKHRhYklkID09PSAndGFiLWFuYWx5dGljcycpIGxvYWRBbmFseXRpY3MoKTsKICAgICAgICBpZiAodGFiSWQgPT09ICd0YWItdHJlbmRzJykgbG9hZFRyZW5kcygpOwogICAgfQoKICAgIC8vIOKUgOKUgCBDaGFydHMgJiBWaXN1YWxpemF0aW9ucyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBjb25zdCBTRUFUX0NPTE9SUyA9IFsnIzU4YTZmZicsICcjZjg1MTQ5JywgJyMzZmI5NTAnLCAnI2QyOTkyMiddOwogICAgY29uc3QgU0VBVF9DT0xPUlNfRElNID0gWydyZ2JhKDg4LDE2NiwyNTUsMC4zKScsICdyZ2JhKDI0OCw4MSw3MywwLjMpJywgJ3JnYmEoNjMsMTg1LDgwLDAuMyknLCAncmdiYSgyMTAsMTUzLDM0LDAuMyknXTsKCiAgICBmdW5jdGlvbiByZW5kZXJDaGFydHMocmVzdWx0KSB7CiAgICAgICAgY29uc3QgZ2FtZXMgPSByZXN1bHQuZ2FtZXMgfHwgW107CiAgICAgICAgY29uc3QgcGVyRGVjayA9IHJlc3VsdC5zdW1tYXJ5LnBlckRlY2sgfHwgW107CiAgICAgICAgY29uc3QgZGVja3MgPSByZXN1bHQuZGVja3MgfHwgW107CgogICAgICAgIGRyYXdXaW5SYXRlQ2hhcnQocGVyRGVjaywgZGVja3MpOwogICAgICAgIGRyYXdXaW5Db25kaXRpb25DaGFydChwZXJEZWNrLCBkZWNrcyk7CiAgICAgICAgZHJhd1R1cm5EaXN0cmlidXRpb24oZ2FtZXMsIGRlY2tzKTsKICAgICAgICBkcmF3V2luUmF0ZVRyZW5kKGdhbWVzLCBkZWNrcyk7CiAgICB9CgogICAgZnVuY3Rpb24gZHJhd1dpblJhdGVDaGFydChwZXJEZWNrLCBkZWNrcykgewogICAgICAgIGNvbnN0IGNhbnZhcyA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItY2hhcnQtd2lucmF0ZScpOwogICAgICAgIGlmICghY2FudmFzKSByZXR1cm47CiAgICAgICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJyk7CiAgICAgICAgY29uc3QgdyA9IGNhbnZhcy53aWR0aCwgaCA9IGNhbnZhcy5oZWlnaHQ7CiAgICAgICAgY3R4LmNsZWFyUmVjdCgwLCAwLCB3LCBoKTsKCiAgICAgICAgY29uc3QgcGFkID0geyB0b3A6IDIwLCByaWdodDogMjAsIGJvdHRvbTogNTAsIGxlZnQ6IDUwIH07CiAgICAgICAgY29uc3QgY2hhcnRXID0gdyAtIHBhZC5sZWZ0IC0gcGFkLnJpZ2h0OwogICAgICAgIGNvbnN0IGNoYXJ0SCA9IGggLSBwYWQudG9wIC0gcGFkLmJvdHRvbTsKICAgICAgICBjb25zdCBiYXJXID0gTWF0aC5taW4oNjAsIGNoYXJ0VyAvIHBlckRlY2subGVuZ3RoIC0gMjApOwoKICAgICAgICAvLyBBeGVzCiAgICAgICAgY3R4LnN0cm9rZVN0eWxlID0gJyMzMDM2M2QnOwogICAgICAgIGN0eC5saW5lV2lkdGggPSAxOwogICAgICAgIGN0eC5iZWdpblBhdGgoKTsKICAgICAgICBjdHgubW92ZVRvKHBhZC5sZWZ0LCBwYWQudG9wKTsKICAgICAgICBjdHgubGluZVRvKHBhZC5sZWZ0LCBoIC0gcGFkLmJvdHRvbSk7CiAgICAgICAgY3R4LmxpbmVUbyh3IC0gcGFkLnJpZ2h0LCBoIC0gcGFkLmJvdHRvbSk7CiAgICAgICAgY3R4LnN0cm9rZSgpOwoKICAgICAgICAvLyBZLWF4aXMgbGFiZWxzICgwJSwgMjUlLCA1MCUsIDc1JSwgMTAwJSkKICAgICAgICBjdHguZmlsbFN0eWxlID0gJyM4Yjk0OWUnOwogICAgICAgIGN0eC5mb250ID0gJzExcHggLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZic7CiAgICAgICAgY3R4LnRleHRBbGlnbiA9ICdyaWdodCc7CiAgICAgICAgZm9yIChsZXQgcGN0ID0gMDsgcGN0IDw9IDEwMDsgcGN0ICs9IDI1KSB7CiAgICAgICAgICAgIGNvbnN0IHkgPSBoIC0gcGFkLmJvdHRvbSAtIChwY3QgLyAxMDApICogY2hhcnRIOwogICAgICAgICAgICBjdHguZmlsbFRleHQocGN0ICsgJyUnLCBwYWQubGVmdCAtIDgsIHkgKyA0KTsKICAgICAgICAgICAgaWYgKHBjdCA+IDApIHsKICAgICAgICAgICAgICAgIGN0eC5zdHJva2VTdHlsZSA9ICdyZ2JhKDQ4LDU0LDYxLDAuNSknOwogICAgICAgICAgICAgICAgY3R4LmJlZ2luUGF0aCgpOwogICAgICAgICAgICAgICAgY3R4Lm1vdmVUbyhwYWQubGVmdCwgeSk7CiAgICAgICAgICAgICAgICBjdHgubGluZVRvKHcgLSBwYWQucmlnaHQsIHkpOwogICAgICAgICAgICAgICAgY3R4LnN0cm9rZSgpOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICAvLyBCYXJzCiAgICAgICAgcGVyRGVjay5mb3JFYWNoKChkcywgaSkgPT4gewogICAgICAgICAgICBjb25zdCB4ID0gcGFkLmxlZnQgKyAoaSArIDAuNSkgKiAoY2hhcnRXIC8gcGVyRGVjay5sZW5ndGgpIC0gYmFyVyAvIDI7CiAgICAgICAgICAgIGNvbnN0IGJhckggPSAoZHMud2luUmF0ZSkgKiBjaGFydEg7CiAgICAgICAgICAgIGNvbnN0IHkgPSBoIC0gcGFkLmJvdHRvbSAtIGJhckg7CgogICAgICAgICAgICAvLyBCYXIgZmlsbAogICAgICAgICAgICBjdHguZmlsbFN0eWxlID0gU0VBVF9DT0xPUlNbZHMuc2VhdEluZGV4XSB8fCBTRUFUX0NPTE9SU1swXTsKICAgICAgICAgICAgY3R4LmJlZ2luUGF0aCgpOwogICAgICAgICAgICByb3VuZFJlY3QoY3R4LCB4LCB5LCBiYXJXLCBiYXJILCA0KTsKICAgICAgICAgICAgY3R4LmZpbGwoKTsKCiAgICAgICAgICAgIC8vIFZhbHVlIG9uIHRvcAogICAgICAgICAgICBjdHguZmlsbFN0eWxlID0gJyNjOWQxZDknOwogICAgICAgICAgICBjdHguZm9udCA9ICdib2xkIDEycHggLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZic7CiAgICAgICAgICAgIGN0eC50ZXh0QWxpZ24gPSAnY2VudGVyJzsKICAgICAgICAgICAgY3R4LmZpbGxUZXh0KChkcy53aW5SYXRlICogMTAwKS50b0ZpeGVkKDEpICsgJyUnLCB4ICsgYmFyVyAvIDIsIHkgLSA2KTsKCiAgICAgICAgICAgIC8vIExhYmVsCiAgICAgICAgICAgIGN0eC5maWxsU3R5bGUgPSAnIzhiOTQ5ZSc7CiAgICAgICAgICAgIGN0eC5mb250ID0gJzEwcHggLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZic7CiAgICAgICAgICAgIGNvbnN0IGxhYmVsID0gKGRlY2tzW2RzLnNlYXRJbmRleF0/LmNvbW1hbmRlck5hbWUgfHwgZHMuZGVja05hbWUgfHwgJycpLnN1YnN0cmluZygwLCAxNik7CiAgICAgICAgICAgIGN0eC5maWxsVGV4dChsYWJlbCwgeCArIGJhclcgLyAyLCBoIC0gcGFkLmJvdHRvbSArIDE0KTsKICAgICAgICB9KTsKICAgIH0KCiAgICBmdW5jdGlvbiBkcmF3V2luQ29uZGl0aW9uQ2hhcnQocGVyRGVjaywgZGVja3MpIHsKICAgICAgICBjb25zdCBjYW52YXMgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWNoYXJ0LXdpbmNvbmQnKTsKICAgICAgICBpZiAoIWNhbnZhcykgcmV0dXJuOwogICAgICAgIGNvbnN0IGN0eCA9IGNhbnZhcy5nZXRDb250ZXh0KCcyZCcpOwogICAgICAgIGNvbnN0IHcgPSBjYW52YXMud2lkdGgsIGggPSBjYW52YXMuaGVpZ2h0OwogICAgICAgIGN0eC5jbGVhclJlY3QoMCwgMCwgdywgaCk7CgogICAgICAgIC8vIEFnZ3JlZ2F0ZSB3aW4gY29uZGl0aW9ucyBhY3Jvc3MgYWxsIGRlY2tzCiAgICAgICAgY29uc3QgdG90YWxzID0ge307CiAgICAgICAgcGVyRGVjay5mb3JFYWNoKGRzID0+IHsKICAgICAgICAgICAgY29uc3QgYmQgPSBkcy53aW5Db25kaXRpb25CcmVha2Rvd24gfHwge307CiAgICAgICAgICAgIE9iamVjdC5lbnRyaWVzKGJkKS5mb3JFYWNoKChbaywgdl0pID0+IHsKICAgICAgICAgICAgICAgIGlmICh2ID4gMCkgdG90YWxzW2tdID0gKHRvdGFsc1trXSB8fCAwKSArIHY7CiAgICAgICAgICAgIH0pOwogICAgICAgIH0pOwoKICAgICAgICBjb25zdCBlbnRyaWVzID0gT2JqZWN0LmVudHJpZXModG90YWxzKS5maWx0ZXIoKFtfLCB2XSkgPT4gdiA+IDApLnNvcnQoKGEsIGIpID0+IGJbMV0gLSBhWzFdKTsKICAgICAgICBpZiAoZW50cmllcy5sZW5ndGggPT09IDApIHsKICAgICAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICcjOGI5NDllJzsKICAgICAgICAgICAgY3R4LmZvbnQgPSAnMTNweCAtYXBwbGUtc3lzdGVtLCBzYW5zLXNlcmlmJzsKICAgICAgICAgICAgY3R4LnRleHRBbGlnbiA9ICdjZW50ZXInOwogICAgICAgICAgICBjdHguZmlsbFRleHQoJ05vIHdpbiBkYXRhJywgdyAvIDIsIGggLyAyKTsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgY29uc3QgdG90YWwgPSBlbnRyaWVzLnJlZHVjZSgocywgW18sIHZdKSA9PiBzICsgdiwgMCk7CiAgICAgICAgY29uc3QgcGllQ29sb3JzID0gWycjNThhNmZmJywgJyNmODUxNDknLCAnIzNmYjk1MCcsICcjZDI5OTIyJywgJyNhMzcxZjcnLCAnI2YwODgzZScsICcjOGI5NDllJywgJyM1NmQzNjQnXTsKICAgICAgICBjb25zdCBjeCA9IHcgKiAwLjM1LCBjeSA9IGggLyAyLCByYWRpdXMgPSBNYXRoLm1pbihjeCAtIDIwLCBjeSAtIDIwKTsKCiAgICAgICAgbGV0IHN0YXJ0QW5nbGUgPSAtTWF0aC5QSSAvIDI7CiAgICAgICAgY29uc3Qgc2xpY2VzID0gW107CiAgICAgICAgZW50cmllcy5mb3JFYWNoKChbaywgdl0sIGkpID0+IHsKICAgICAgICAgICAgY29uc3Qgc2xpY2VBbmdsZSA9ICh2IC8gdG90YWwpICogTWF0aC5QSSAqIDI7CiAgICAgICAgICAgIGN0eC5maWxsU3R5bGUgPSBwaWVDb2xvcnNbaSAlIHBpZUNvbG9ycy5sZW5ndGhdOwogICAgICAgICAgICBjdHguYmVnaW5QYXRoKCk7CiAgICAgICAgICAgIGN0eC5tb3ZlVG8oY3gsIGN5KTsKICAgICAgICAgICAgY3R4LmFyYyhjeCwgY3ksIHJhZGl1cywgc3RhcnRBbmdsZSwgc3RhcnRBbmdsZSArIHNsaWNlQW5nbGUpOwogICAgICAgICAgICBjdHguY2xvc2VQYXRoKCk7CiAgICAgICAgICAgIGN0eC5maWxsKCk7CiAgICAgICAgICAgIHNsaWNlcy5wdXNoKHsga2V5OiBrLCB2YWx1ZTogdiwgY29sb3I6IHBpZUNvbG9yc1tpICUgcGllQ29sb3JzLmxlbmd0aF0sIHN0YXJ0QW5nbGUsIGVuZEFuZ2xlOiBzdGFydEFuZ2xlICsgc2xpY2VBbmdsZSB9KTsKICAgICAgICAgICAgc3RhcnRBbmdsZSArPSBzbGljZUFuZ2xlOwogICAgICAgIH0pOwoKICAgICAgICAvLyBMZWdlbmQKICAgICAgICBjb25zdCBsZWdlbmRYID0gdyAqIDAuNjU7CiAgICAgICAgbGV0IGxlZ2VuZFkgPSAzMDsKICAgICAgICBjdHguZm9udCA9ICcxMXB4IC1hcHBsZS1zeXN0ZW0sIHNhbnMtc2VyaWYnOwogICAgICAgIGN0eC50ZXh0QWxpZ24gPSAnbGVmdCc7CiAgICAgICAgc2xpY2VzLmZvckVhY2gocyA9PiB7CiAgICAgICAgICAgIGN0eC5maWxsU3R5bGUgPSBzLmNvbG9yOwogICAgICAgICAgICBjdHguZmlsbFJlY3QobGVnZW5kWCwgbGVnZW5kWSAtIDgsIDEwLCAxMCk7CiAgICAgICAgICAgIGN0eC5maWxsU3R5bGUgPSAnI2M5ZDFkOSc7CiAgICAgICAgICAgIGNvbnN0IHBjdCA9ICgocy52YWx1ZSAvIHRvdGFsKSAqIDEwMCkudG9GaXhlZCgwKTsKICAgICAgICAgICAgY3R4LmZpbGxUZXh0KGAke2Zvcm1hdFdpbkNvbmRpdGlvbihzLmtleSl9ICgke3BjdH0lKWAsIGxlZ2VuZFggKyAxNiwgbGVnZW5kWSk7CiAgICAgICAgICAgIGxlZ2VuZFkgKz0gMTg7CiAgICAgICAgfSk7CiAgICB9CgogICAgZnVuY3Rpb24gZHJhd1R1cm5EaXN0cmlidXRpb24oZ2FtZXMsIGRlY2tzKSB7CiAgICAgICAgY29uc3QgY2FudmFzID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1jaGFydC10dXJucycpOwogICAgICAgIGlmICghY2FudmFzKSByZXR1cm47CiAgICAgICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJyk7CiAgICAgICAgY29uc3QgdyA9IGNhbnZhcy53aWR0aCwgaCA9IGNhbnZhcy5oZWlnaHQ7CiAgICAgICAgY3R4LmNsZWFyUmVjdCgwLCAwLCB3LCBoKTsKCiAgICAgICAgaWYgKGdhbWVzLmxlbmd0aCA9PT0gMCkgcmV0dXJuOwoKICAgICAgICBjb25zdCBwYWQgPSB7IHRvcDogMjAsIHJpZ2h0OiAyMCwgYm90dG9tOiA1MCwgbGVmdDogNTAgfTsKICAgICAgICBjb25zdCBjaGFydFcgPSB3IC0gcGFkLmxlZnQgLSBwYWQucmlnaHQ7CiAgICAgICAgY29uc3QgY2hhcnRIID0gaCAtIHBhZC50b3AgLSBwYWQuYm90dG9tOwoKICAgICAgICAvLyBCdWlsZCBoaXN0b2dyYW0gYnVja2V0cwogICAgICAgIGNvbnN0IHR1cm5zID0gZ2FtZXMubWFwKGcgPT4gZy50b3RhbFR1cm5zKTsKICAgICAgICBjb25zdCBtaW5UID0gTWF0aC5taW4oLi4udHVybnMpOwogICAgICAgIGNvbnN0IG1heFQgPSBNYXRoLm1heCguLi50dXJucyk7CiAgICAgICAgY29uc3QgYnVja2V0U2l6ZSA9IE1hdGgubWF4KDEsIE1hdGguY2VpbCgobWF4VCAtIG1pblQgKyAxKSAvIDE1KSk7CiAgICAgICAgY29uc3QgYnVja2V0cyA9IFtdOwogICAgICAgIGZvciAobGV0IHQgPSBtaW5UOyB0IDw9IG1heFQ7IHQgKz0gYnVja2V0U2l6ZSkgewogICAgICAgICAgICBjb25zdCBjb3VudCA9IHR1cm5zLmZpbHRlcih2ID0+IHYgPj0gdCAmJiB2IDwgdCArIGJ1Y2tldFNpemUpLmxlbmd0aDsKICAgICAgICAgICAgYnVja2V0cy5wdXNoKHsgbGFiZWw6IGJ1Y2tldFNpemUgPT09IDEgPyAnJyArIHQgOiB0ICsgJy0nICsgKHQgKyBidWNrZXRTaXplIC0gMSksIGNvdW50IH0pOwogICAgICAgIH0KCiAgICAgICAgY29uc3QgbWF4Q291bnQgPSBNYXRoLm1heCguLi5idWNrZXRzLm1hcChiID0+IGIuY291bnQpKTsKICAgICAgICBjb25zdCBiYXJXID0gTWF0aC5tYXgoNCwgY2hhcnRXIC8gYnVja2V0cy5sZW5ndGggLSAyKTsKCiAgICAgICAgLy8gQXhlcwogICAgICAgIGN0eC5zdHJva2VTdHlsZSA9ICcjMzAzNjNkJzsKICAgICAgICBjdHguYmVnaW5QYXRoKCk7CiAgICAgICAgY3R4Lm1vdmVUbyhwYWQubGVmdCwgcGFkLnRvcCk7CiAgICAgICAgY3R4LmxpbmVUbyhwYWQubGVmdCwgaCAtIHBhZC5ib3R0b20pOwogICAgICAgIGN0eC5saW5lVG8odyAtIHBhZC5yaWdodCwgaCAtIHBhZC5ib3R0b20pOwogICAgICAgIGN0eC5zdHJva2UoKTsKCiAgICAgICAgLy8gWS1heGlzCiAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICcjOGI5NDllJzsKICAgICAgICBjdHguZm9udCA9ICcxMHB4IC1hcHBsZS1zeXN0ZW0sIHNhbnMtc2VyaWYnOwogICAgICAgIGN0eC50ZXh0QWxpZ24gPSAncmlnaHQnOwogICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDw9IDQ7IGkrKykgewogICAgICAgICAgICBjb25zdCB2YWwgPSBNYXRoLnJvdW5kKG1heENvdW50ICogaSAvIDQpOwogICAgICAgICAgICBjb25zdCB5ID0gaCAtIHBhZC5ib3R0b20gLSAoaSAvIDQpICogY2hhcnRIOwogICAgICAgICAgICBjdHguZmlsbFRleHQodmFsLCBwYWQubGVmdCAtIDgsIHkgKyAzKTsKICAgICAgICB9CgogICAgICAgIC8vIEJhcnMKICAgICAgICBidWNrZXRzLmZvckVhY2goKGIsIGkpID0+IHsKICAgICAgICAgICAgY29uc3QgeCA9IHBhZC5sZWZ0ICsgaSAqIChjaGFydFcgLyBidWNrZXRzLmxlbmd0aCkgKyAxOwogICAgICAgICAgICBjb25zdCBiYXJIID0gbWF4Q291bnQgPiAwID8gKGIuY291bnQgLyBtYXhDb3VudCkgKiBjaGFydEggOiAwOwogICAgICAgICAgICBjb25zdCB5ID0gaCAtIHBhZC5ib3R0b20gLSBiYXJIOwoKICAgICAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICcjNThhNmZmJzsKICAgICAgICAgICAgY3R4Lmdsb2JhbEFscGhhID0gMC44OwogICAgICAgICAgICBjdHguYmVnaW5QYXRoKCk7CiAgICAgICAgICAgIHJvdW5kUmVjdChjdHgsIHgsIHksIGJhclcsIGJhckgsIDIpOwogICAgICAgICAgICBjdHguZmlsbCgpOwogICAgICAgICAgICBjdHguZ2xvYmFsQWxwaGEgPSAxOwoKICAgICAgICAgICAgLy8gTGFiZWwKICAgICAgICAgICAgaWYgKGJ1Y2tldHMubGVuZ3RoIDw9IDIwKSB7CiAgICAgICAgICAgICAgICBjdHguZmlsbFN0eWxlID0gJyM4Yjk0OWUnOwogICAgICAgICAgICAgICAgY3R4LmZvbnQgPSAnOXB4IC1hcHBsZS1zeXN0ZW0sIHNhbnMtc2VyaWYnOwogICAgICAgICAgICAgICAgY3R4LnRleHRBbGlnbiA9ICdjZW50ZXInOwogICAgICAgICAgICAgICAgY3R4LmZpbGxUZXh0KGIubGFiZWwsIHggKyBiYXJXIC8gMiwgaCAtIHBhZC5ib3R0b20gKyAxNCk7CiAgICAgICAgICAgIH0KICAgICAgICB9KTsKCiAgICAgICAgLy8gWC1heGlzIGxhYmVsCiAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICcjOGI5NDllJzsKICAgICAgICBjdHguZm9udCA9ICcxMXB4IC1hcHBsZS1zeXN0ZW0sIHNhbnMtc2VyaWYnOwogICAgICAgIGN0eC50ZXh0QWxpZ24gPSAnY2VudGVyJzsKICAgICAgICBjdHguZmlsbFRleHQoJ1R1cm5zJywgdyAvIDIsIGggLSA2KTsKICAgIH0KCiAgICBmdW5jdGlvbiBkcmF3V2luUmF0ZVRyZW5kKGdhbWVzLCBkZWNrcykgewogICAgICAgIGNvbnN0IGNhbnZhcyA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItY2hhcnQtd2ludHJlbmQnKTsKICAgICAgICBpZiAoIWNhbnZhcykgcmV0dXJuOwogICAgICAgIGNvbnN0IGN0eCA9IGNhbnZhcy5nZXRDb250ZXh0KCcyZCcpOwogICAgICAgIGNvbnN0IHcgPSBjYW52YXMud2lkdGgsIGggPSBjYW52YXMuaGVpZ2h0OwogICAgICAgIGN0eC5jbGVhclJlY3QoMCwgMCwgdywgaCk7CgogICAgICAgIGlmIChnYW1lcy5sZW5ndGggPCAyKSByZXR1cm47CgogICAgICAgIGNvbnN0IHBhZCA9IHsgdG9wOiAyMCwgcmlnaHQ6IDIwLCBib3R0b206IDUwLCBsZWZ0OiA1MCB9OwogICAgICAgIGNvbnN0IGNoYXJ0VyA9IHcgLSBwYWQubGVmdCAtIHBhZC5yaWdodDsKICAgICAgICBjb25zdCBjaGFydEggPSBoIC0gcGFkLnRvcCAtIHBhZC5ib3R0b207CgogICAgICAgIC8vIENvbXB1dGUgY3VtdWxhdGl2ZSB3aW4gcmF0ZSBmb3IgZWFjaCBzZWF0CiAgICAgICAgY29uc3Qgc2VhdFdpbnMgPSBbMCwgMCwgMCwgMF07CiAgICAgICAgY29uc3Qgc2VhdFJhdGVzID0gW1tdLCBbXSwgW10sIFtdXTsKCiAgICAgICAgLy8gU2FtcGxlIHBvaW50cyAoZG9uJ3QgcGxvdCBldmVyeSBnYW1lIGZvciBsYXJnZSBiYXRjaGVzKQogICAgICAgIGNvbnN0IHN0ZXAgPSBNYXRoLm1heCgxLCBNYXRoLmZsb29yKGdhbWVzLmxlbmd0aCAvIDEwMCkpOwoKICAgICAgICBnYW1lcy5mb3JFYWNoKChnLCBnaSkgPT4gewogICAgICAgICAgICBpZiAoZy53aW5uaW5nU2VhdCAhPT0gbnVsbCAmJiBnLndpbm5pbmdTZWF0ICE9PSB1bmRlZmluZWQpIHsKICAgICAgICAgICAgICAgIHNlYXRXaW5zW2cud2lubmluZ1NlYXRdKys7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKGdpICUgc3RlcCA9PT0gMCB8fCBnaSA9PT0gZ2FtZXMubGVuZ3RoIC0gMSkgewogICAgICAgICAgICAgICAgY29uc3QgbiA9IGdpICsgMTsKICAgICAgICAgICAgICAgIGZvciAobGV0IHMgPSAwOyBzIDwgNDsgcysrKSB7CiAgICAgICAgICAgICAgICAgICAgc2VhdFJhdGVzW3NdLnB1c2goeyB4OiBnaSwgeTogc2VhdFdpbnNbc10gLyBuIH0pOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSk7CgogICAgICAgIC8vIEF4ZXMKICAgICAgICBjdHguc3Ryb2tlU3R5bGUgPSAnIzMwMzYzZCc7CiAgICAgICAgY3R4LmJlZ2luUGF0aCgpOwogICAgICAgIGN0eC5tb3ZlVG8ocGFkLmxlZnQsIHBhZC50b3ApOwogICAgICAgIGN0eC5saW5lVG8ocGFkLmxlZnQsIGggLSBwYWQuYm90dG9tKTsKICAgICAgICBjdHgubGluZVRvKHcgLSBwYWQucmlnaHQsIGggLSBwYWQuYm90dG9tKTsKICAgICAgICBjdHguc3Ryb2tlKCk7CgogICAgICAgIC8vIFktYXhpcyBsYWJlbHMKICAgICAgICBjdHguZmlsbFN0eWxlID0gJyM4Yjk0OWUnOwogICAgICAgIGN0eC5mb250ID0gJzEwcHggLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZic7CiAgICAgICAgY3R4LnRleHRBbGlnbiA9ICdyaWdodCc7CiAgICAgICAgZm9yIChsZXQgcGN0ID0gMDsgcGN0IDw9IDEwMDsgcGN0ICs9IDI1KSB7CiAgICAgICAgICAgIGNvbnN0IHkgPSBoIC0gcGFkLmJvdHRvbSAtIChwY3QgLyAxMDApICogY2hhcnRIOwogICAgICAgICAgICBjdHguZmlsbFRleHQocGN0ICsgJyUnLCBwYWQubGVmdCAtIDgsIHkgKyAzKTsKICAgICAgICAgICAgY3R4LnN0cm9rZVN0eWxlID0gJ3JnYmEoNDgsNTQsNjEsMC4zKSc7CiAgICAgICAgICAgIGN0eC5iZWdpblBhdGgoKTsKICAgICAgICAgICAgY3R4Lm1vdmVUbyhwYWQubGVmdCwgeSk7CiAgICAgICAgICAgIGN0eC5saW5lVG8odyAtIHBhZC5yaWdodCwgeSk7CiAgICAgICAgICAgIGN0eC5zdHJva2UoKTsKICAgICAgICB9CgogICAgICAgIC8vIExpbmVzCiAgICAgICAgY29uc3QgbWF4WCA9IGdhbWVzLmxlbmd0aCAtIDE7CiAgICAgICAgZm9yIChsZXQgcyA9IDA7IHMgPCA0OyBzKyspIHsKICAgICAgICAgICAgY29uc3QgcG9pbnRzID0gc2VhdFJhdGVzW3NdOwogICAgICAgICAgICBpZiAocG9pbnRzLmxlbmd0aCA8IDIpIGNvbnRpbnVlOwoKICAgICAgICAgICAgY3R4LnN0cm9rZVN0eWxlID0gU0VBVF9DT0xPUlNbc107CiAgICAgICAgICAgIGN0eC5saW5lV2lkdGggPSAyOwogICAgICAgICAgICBjdHguYmVnaW5QYXRoKCk7CiAgICAgICAgICAgIHBvaW50cy5mb3JFYWNoKChwLCBwaSkgPT4gewogICAgICAgICAgICAgICAgY29uc3QgcHggPSBwYWQubGVmdCArIChwLnggLyBtYXhYKSAqIGNoYXJ0VzsKICAgICAgICAgICAgICAgIGNvbnN0IHB5ID0gaCAtIHBhZC5ib3R0b20gLSBwLnkgKiBjaGFydEg7CiAgICAgICAgICAgICAgICBpZiAocGkgPT09IDApIGN0eC5tb3ZlVG8ocHgsIHB5KTsKICAgICAgICAgICAgICAgIGVsc2UgY3R4LmxpbmVUbyhweCwgcHkpOwogICAgICAgICAgICB9KTsKICAgICAgICAgICAgY3R4LnN0cm9rZSgpOwogICAgICAgICAgICBjdHgubGluZVdpZHRoID0gMTsKICAgICAgICB9CgogICAgICAgIC8vIExlZ2VuZAogICAgICAgIGN0eC5mb250ID0gJzEwcHggLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZic7CiAgICAgICAgY3R4LnRleHRBbGlnbiA9ICdsZWZ0JzsKICAgICAgICBmb3IgKGxldCBzID0gMDsgcyA8IDQ7IHMrKykgewogICAgICAgICAgICBjb25zdCBseCA9IHBhZC5sZWZ0ICsgMTAgKyBzICogMTIwOwogICAgICAgICAgICBjdHguZmlsbFN0eWxlID0gU0VBVF9DT0xPUlNbc107CiAgICAgICAgICAgIGN0eC5maWxsUmVjdChseCwgcGFkLnRvcCArIDIsIDEyLCAzKTsKICAgICAgICAgICAgY3R4LmZpbGxTdHlsZSA9ICcjOGI5NDllJzsKICAgICAgICAgICAgY29uc3QgbGFiZWwgPSAoZGVja3Nbc10/LmNvbW1hbmRlck5hbWUgfHwgZGVja3Nbc10/LmRlY2tOYW1lIHx8ICdEZWNrICcgKyAocyArIDEpKS5zdWJzdHJpbmcoMCwgMTQpOwogICAgICAgICAgICBjdHguZmlsbFRleHQobGFiZWwsIGx4ICsgMTYsIHBhZC50b3AgKyA3KTsKICAgICAgICB9CgogICAgICAgIC8vIFgtYXhpcyBsYWJlbAogICAgICAgIGN0eC5maWxsU3R5bGUgPSAnIzhiOTQ5ZSc7CiAgICAgICAgY3R4LmZvbnQgPSAnMTFweCAtYXBwbGUtc3lzdGVtLCBzYW5zLXNlcmlmJzsKICAgICAgICBjdHgudGV4dEFsaWduID0gJ2NlbnRlcic7CiAgICAgICAgY3R4LmZpbGxUZXh0KCdHYW1lcyBQbGF5ZWQnLCB3IC8gMiwgaCAtIDYpOwogICAgfQoKICAgIGZ1bmN0aW9uIHJvdW5kUmVjdChjdHgsIHgsIHksIHcsIGgsIHIpIHsKICAgICAgICBpZiAoaCA8PSAwKSB7IGN0eC5yZWN0KHgsIHksIHcsIDApOyByZXR1cm47IH0KICAgICAgICByID0gTWF0aC5taW4ociwgaCAvIDIsIHcgLyAyKTsKICAgICAgICBjdHgubW92ZVRvKHggKyByLCB5KTsKICAgICAgICBjdHguYXJjVG8oeCArIHcsIHksIHggKyB3LCB5ICsgaCwgcik7CiAgICAgICAgY3R4LmFyY1RvKHggKyB3LCB5ICsgaCwgeCwgeSArIGgsIHIpOwogICAgICAgIGN0eC5hcmNUbyh4LCB5ICsgaCwgeCwgeSwgcik7CiAgICAgICAgY3R4LmFyY1RvKHgsIHksIHggKyB3LCB5LCByKTsKICAgIH0KCiAgICAvLyDilIDilIAgSGVhZC10by1IZWFkIE1hdHJpeCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBmdW5jdGlvbiByZW5kZXJIZWFkVG9IZWFkKHJlc3VsdCkgewogICAgICAgIGNvbnN0IGVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1oMmgtbWF0cml4Jyk7CiAgICAgICAgaWYgKCFlbCkgcmV0dXJuOwoKICAgICAgICBjb25zdCBnYW1lcyA9IHJlc3VsdC5nYW1lcyB8fCBbXTsKICAgICAgICBjb25zdCBkZWNrcyA9IHJlc3VsdC5kZWNrcyB8fCBbXTsKICAgICAgICBpZiAoZ2FtZXMubGVuZ3RoID09PSAwKSB7IGVsLmlubmVySFRNTCA9ICc8cCBjbGFzcz0ibGFiLW5vLWRhdGEiPk5vIGdhbWUgZGF0YTwvcD4nOyByZXR1cm47IH0KCiAgICAgICAgLy8gQ29tcHV0ZSBwYWlyd2lzZTogZm9yIGVhY2ggcGFpciAoQSwgQiksIGhvdyBvZnRlbiBkaWQgQSB3aW4gd2hlbiBib3RoIHdlcmUgaW4gdGhlIGdhbWUgKGFsd2F5cyDigJQgNC1wbGF5ZXIgcG9kKQogICAgICAgIC8vIFNob3c6IHdoZW4gZGVjayBBIHdpbnMsIHdoYXQgJSBvZiB0aG9zZSBnYW1lcyB2cyBlYWNoIG9wcG9uZW50CiAgICAgICAgLy8gQmV0dGVyIG1ldHJpYzogRm9yIGVhY2ggcGFpciwgY291bnQgZ2FtZXMgd2hlcmUgQSB3aW5zIGFuZCBCIGRvZXNuJ3QgKGFuZCB2aWNlIHZlcnNhKQogICAgICAgIGNvbnN0IG4gPSBkZWNrcy5sZW5ndGg7CiAgICAgICAgY29uc3QgaDJoID0gQXJyYXkuZnJvbSh7IGxlbmd0aDogbiB9LCAoKSA9PiBBcnJheShuKS5maWxsKDApKTsKICAgICAgICBjb25zdCBwYWlyR2FtZXMgPSBBcnJheS5mcm9tKHsgbGVuZ3RoOiBuIH0sICgpID0+IEFycmF5KG4pLmZpbGwoMCkpOwoKICAgICAgICBnYW1lcy5mb3JFYWNoKGcgPT4gewogICAgICAgICAgICBmb3IgKGxldCBhID0gMDsgYSA8IG47IGErKykgewogICAgICAgICAgICAgICAgZm9yIChsZXQgYiA9IDA7IGIgPCBuOyBiKyspIHsKICAgICAgICAgICAgICAgICAgICBpZiAoYSA9PT0gYikgY29udGludWU7CiAgICAgICAgICAgICAgICAgICAgcGFpckdhbWVzW2FdW2JdKys7CiAgICAgICAgICAgICAgICAgICAgaWYgKGcud2lubmluZ1NlYXQgPT09IGEpIGgyaFthXVtiXSsrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSk7CgogICAgICAgIGxldCBodG1sID0gJzx0YWJsZSBjbGFzcz0ibGFiLWgyaC10YWJsZSI+PHRoZWFkPjx0cj48dGggY2xhc3M9ImxhYi1oMmgtY29ybmVyIj52czwvdGg+JzsKICAgICAgICBkZWNrcy5mb3JFYWNoKChkLCBpKSA9PiB7CiAgICAgICAgICAgIGNvbnN0IG5hbWUgPSAoZC5jb21tYW5kZXJOYW1lIHx8IGQuZGVja05hbWUgfHwgJ0RlY2sgJyArIChpICsgMSkpLnN1YnN0cmluZygwLCAxOCk7CiAgICAgICAgICAgIGh0bWwgKz0gYDx0aCBzdHlsZT0iY29sb3I6JHtTRUFUX0NPTE9SU1tpXX0iPiR7ZXNjYXBlSHRtbChuYW1lKX08L3RoPmA7CiAgICAgICAgfSk7CiAgICAgICAgaHRtbCArPSAnPC90cj48L3RoZWFkPjx0Ym9keT4nOwoKICAgICAgICBmb3IgKGxldCBhID0gMDsgYSA8IG47IGErKykgewogICAgICAgICAgICBjb25zdCBuYW1lID0gKGRlY2tzW2FdLmNvbW1hbmRlck5hbWUgfHwgZGVja3NbYV0uZGVja05hbWUgfHwgJ0RlY2sgJyArIChhICsgMSkpLnN1YnN0cmluZygwLCAxOCk7CiAgICAgICAgICAgIGh0bWwgKz0gYDx0cj48dGQgY2xhc3M9ImxhYi1oMmgtcm93LWxhYmVsIiBzdHlsZT0iY29sb3I6JHtTRUFUX0NPTE9SU1thXX0iPiR7ZXNjYXBlSHRtbChuYW1lKX08L3RkPmA7CiAgICAgICAgICAgIGZvciAobGV0IGIgPSAwOyBiIDwgbjsgYisrKSB7CiAgICAgICAgICAgICAgICBpZiAoYSA9PT0gYikgewogICAgICAgICAgICAgICAgICAgIGh0bWwgKz0gJzx0ZCBjbGFzcz0ibGFiLWgyaC1zZWxmIj7igJQ8L3RkPic7CiAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgIGNvbnN0IHdpbnMgPSBoMmhbYV1bYl07CiAgICAgICAgICAgICAgICAgICAgY29uc3QgdG90YWwgPSBwYWlyR2FtZXNbYV1bYl07CiAgICAgICAgICAgICAgICAgICAgY29uc3QgcmF0ZSA9IHRvdGFsID4gMCA/ICh3aW5zIC8gdG90YWwgKiAxMDApLnRvRml4ZWQoMSkgOiAnMC4wJzsKICAgICAgICAgICAgICAgICAgICBjb25zdCByYXRlTnVtID0gdG90YWwgPiAwID8gd2lucyAvIHRvdGFsIDogMDsKICAgICAgICAgICAgICAgICAgICBjb25zdCBjbHMgPSByYXRlTnVtID4gMC41ID8gJ2xhYi1oMmgtd2lubmluZycgOiByYXRlTnVtIDwgMC4zID8gJ2xhYi1oMmgtbG9zaW5nJyA6ICcnOwogICAgICAgICAgICAgICAgICAgIGh0bWwgKz0gYDx0ZCBjbGFzcz0ibGFiLWgyaC1jZWxsICR7Y2xzfSI+JHtyYXRlfSU8c3BhbiBjbGFzcz0ibGFiLWgyaC1zdWIiPiR7d2luc30vJHt0b3RhbH08L3NwYW4+PC90ZD5gOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGh0bWwgKz0gJzwvdHI+JzsKICAgICAgICB9CiAgICAgICAgaHRtbCArPSAnPC90Ym9keT48L3RhYmxlPic7CgogICAgICAgIGh0bWwgKz0gJzxwIGNsYXNzPSJsYWItaDJoLW5vdGUiPlJlYWRzIGFzOiByb3cgZGVja1wncyB3aW4gcmF0ZSBpbiBnYW1lcyB3aGVyZSBjb2x1bW4gZGVjayBhbHNvIHBsYXllZC48L3A+JzsKICAgICAgICBlbC5pbm5lckhUTUwgPSBodG1sOwogICAgfQoKICAgIC8vIOKUgOKUgCBDb21iYXQgU3RhdHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgZnVuY3Rpb24gcmVuZGVyQ29tYmF0U3RhdHMocmVzdWx0KSB7CiAgICAgICAgY29uc3QgZWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWNvbWJhdC1zdGF0cycpOwogICAgICAgIGlmICghZWwpIHJldHVybjsKCiAgICAgICAgY29uc3QgZ2FtZXMgPSByZXN1bHQuZ2FtZXMgfHwgW107CiAgICAgICAgY29uc3QgZGVja3MgPSByZXN1bHQuZGVja3MgfHwgW107CiAgICAgICAgaWYgKGdhbWVzLmxlbmd0aCA9PT0gMCkgeyBlbC5pbm5lckhUTUwgPSAnPHAgY2xhc3M9ImxhYi1uby1kYXRhIj5ObyBnYW1lIGRhdGE8L3A+JzsgcmV0dXJuOyB9CgogICAgICAgIC8vIEFnZ3JlZ2F0ZSBwZXItc2VhdCBjb21iYXQgc3RhdHMgZnJvbSBnYW1lLWxldmVsIHBsYXllclJlc3VsdHMKICAgICAgICBjb25zdCBzdGF0cyA9IGRlY2tzLm1hcCgoZCwgc2VhdCkgPT4gewogICAgICAgICAgICBjb25zdCBwciA9IGdhbWVzLm1hcChnID0+IGcucGxheWVyUmVzdWx0c1tzZWF0XSkuZmlsdGVyKEJvb2xlYW4pOwogICAgICAgICAgICBjb25zdCBuID0gcHIubGVuZ3RoOwogICAgICAgICAgICBpZiAobiA9PT0gMCkgcmV0dXJuIG51bGw7CgogICAgICAgICAgICBjb25zdCBzdW0gPSAoZm4pID0+IHByLnJlZHVjZSgocywgcCkgPT4gcyArIGZuKHApLCAwKTsKICAgICAgICAgICAgY29uc3QgYXZnID0gKGZuKSA9PiBzdW0oZm4pIC8gbjsKICAgICAgICAgICAgY29uc3QgbWF4ID0gKGZuKSA9PiBNYXRoLm1heCguLi5wci5tYXAoZm4pKTsKCiAgICAgICAgICAgIHJldHVybiB7CiAgICAgICAgICAgICAgICBzZWF0LAogICAgICAgICAgICAgICAgbmFtZTogZC5jb21tYW5kZXJOYW1lIHx8IGQuZGVja05hbWUgfHwgJ0RlY2sgJyArIChzZWF0ICsgMSksCiAgICAgICAgICAgICAgICBhdmdDbWRyRG1nOiBhdmcocCA9PiBwLmNvbW1hbmRlckRhbWFnZURlYWx0IHx8IDApLAogICAgICAgICAgICAgICAgbWF4Q21kckRtZzogbWF4KHAgPT4gcC5jb21tYW5kZXJEYW1hZ2VEZWFsdCB8fCAwKSwKICAgICAgICAgICAgICAgIGF2Z0NtZHJDYXN0czogYXZnKHAgPT4gcC5jb21tYW5kZXJDYXN0cyB8fCAwKSwKICAgICAgICAgICAgICAgIG1heENtZHJDYXN0czogbWF4KHAgPT4gcC5jb21tYW5kZXJDYXN0cyB8fCAwKSwKICAgICAgICAgICAgICAgIGF2Z0xhbmRzOiBhdmcocCA9PiBwLmxhbmRzUGxheWVkIHx8IDApLAogICAgICAgICAgICAgICAgYXZnU3BlbGxzOiBhdmcocCA9PiBwLnNwZWxsc0Nhc3QgfHwgMCksCiAgICAgICAgICAgICAgICBtYXhTcGVsbHM6IG1heChwID0+IHAuc3BlbGxzQ2FzdCB8fCAwKSwKICAgICAgICAgICAgICAgIGF2Z0NyZWF0dXJlc0Rlc3Ryb3llZDogYXZnKHAgPT4gcC5jcmVhdHVyZXNEZXN0cm95ZWQgfHwgMCksCiAgICAgICAgICAgICAgICB0b3RhbENyZWF0dXJlc0Rlc3Ryb3llZDogc3VtKHAgPT4gcC5jcmVhdHVyZXNEZXN0cm95ZWQgfHwgMCksCiAgICAgICAgICAgICAgICBhdmdGaW5hbExpZmU6IGF2ZyhwID0+IHAuZmluYWxMaWZlIHx8IDApLAogICAgICAgICAgICB9OwogICAgICAgIH0pLmZpbHRlcihCb29sZWFuKTsKCiAgICAgICAgbGV0IGh0bWwgPSAnPGRpdiBjbGFzcz0ibGFiLWNvbWJhdC1ncmlkIj4nOwogICAgICAgIHN0YXRzLmZvckVhY2gocyA9PiB7CiAgICAgICAgICAgIGh0bWwgKz0gYAogICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY29tYmF0LWNhcmQiPgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWNvbWJhdC1oZWFkZXIiIHN0eWxlPSJib3JkZXItbGVmdDogM3B4IHNvbGlkICR7U0VBVF9DT0xPUlNbcy5zZWF0XX0iPgogICAgICAgICAgICAgICAgICAgICR7ZXNjYXBlSHRtbChzLm5hbWUpfQogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY29tYmF0LXJvd3MiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1jb21iYXQtcm93Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1jb21iYXQtaWNvbiI+4pqU77iPPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1sYWJlbCI+Q21kciBEYW1hZ2UgLyBHYW1lPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC12YWwiPiR7cy5hdmdDbWRyRG1nLnRvRml4ZWQoMSl9PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1tYXgiPm1heCAke3MubWF4Q21kckRtZ308L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWNvbWJhdC1yb3ciPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1pY29uIj7wn5GRPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1sYWJlbCI+Q21kciBDYXN0cyAvIEdhbWU8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItY29tYmF0LXZhbCI+JHtzLmF2Z0NtZHJDYXN0cy50b0ZpeGVkKDIpfTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1jb21iYXQtbWF4Ij5tYXggJHtzLm1heENtZHJDYXN0c308L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWNvbWJhdC1yb3ciPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1pY29uIj7wn4+U77iPPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1sYWJlbCI+TGFuZHMgLyBHYW1lPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC12YWwiPiR7cy5hdmdMYW5kcy50b0ZpeGVkKDEpfTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItY29tYmF0LXJvdyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItY29tYmF0LWljb24iPuKcqDwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1jb21iYXQtbGFiZWwiPlNwZWxscyAvIEdhbWU8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItY29tYmF0LXZhbCI+JHtzLmF2Z1NwZWxscy50b0ZpeGVkKDEpfTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1jb21iYXQtbWF4Ij5tYXggJHtzLm1heFNwZWxsc308L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGFiLWNvbWJhdC1yb3ciPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1pY29uIj7wn5KAPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1sYWJlbCI+Q3JlYXR1cmVzIERlc3Ryb3llZCAvIEdhbWU8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItY29tYmF0LXZhbCI+JHtzLmF2Z0NyZWF0dXJlc0Rlc3Ryb3llZC50b0ZpeGVkKDEpfTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1jb21iYXQtbWF4Ij50b3RhbCAke3MudG90YWxDcmVhdHVyZXNEZXN0cm95ZWR9PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImxhYi1jb21iYXQtcm93Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImxhYi1jb21iYXQtaWNvbiI+4p2k77iPPC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0ibGFiLWNvbWJhdC1sYWJlbCI+QXZnIEZpbmFsIExpZmU8L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJsYWItY29tYmF0LXZhbCI+JHtzLmF2Z0ZpbmFsTGlmZS50b0ZpeGVkKDEpfTwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Rpdj5gOwogICAgICAgIH0pOwogICAgICAgIGh0bWwgKz0gJzwvZGl2Pic7CiAgICAgICAgZWwuaW5uZXJIVE1MID0gaHRtbDsKICAgIH0KCiAgICAvLyDilIDilIAgR2FtZS1ieS1HYW1lIExvZyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBsZXQgZ2FtZUxvZ0RhdGEgPSBbXTsKCiAgICBmdW5jdGlvbiByZW5kZXJHYW1lTG9nKHJlc3VsdCkgewogICAgICAgIGNvbnN0IHdyYXBwZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWdhbWVsb2ctd3JhcHBlcicpOwogICAgICAgIGNvbnN0IGNvdW50RWwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWdhbWVsb2ctY291bnQnKTsKICAgICAgICBjb25zdCBmaWx0ZXJJbnB1dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItZ2FtZWxvZy1maWx0ZXInKTsKICAgICAgICBpZiAoIXdyYXBwZXIpIHJldHVybjsKCiAgICAgICAgY29uc3QgZ2FtZXMgPSByZXN1bHQuZ2FtZXMgfHwgW107CiAgICAgICAgY29uc3QgZGVja3MgPSByZXN1bHQuZGVja3MgfHwgW107CgogICAgICAgIGdhbWVMb2dEYXRhID0gZ2FtZXMubWFwKGcgPT4gewogICAgICAgICAgICBjb25zdCB3aW5uZXJOYW1lID0gZy53aW5uaW5nU2VhdCAhPT0gbnVsbCAmJiBnLndpbm5pbmdTZWF0ICE9PSB1bmRlZmluZWQKICAgICAgICAgICAgICAgID8gKGRlY2tzW2cud2lubmluZ1NlYXRdPy5jb21tYW5kZXJOYW1lIHx8IGRlY2tzW2cud2lubmluZ1NlYXRdPy5kZWNrTmFtZSB8fCAnRGVjayAnICsgKGcud2lubmluZ1NlYXQgKyAxKSkKICAgICAgICAgICAgICAgIDogJ0RyYXcnOwogICAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICAgICAgaW5kZXg6IGcuZ2FtZUluZGV4ICsgMSwKICAgICAgICAgICAgICAgIHdpbm5lcjogd2lubmVyTmFtZSwKICAgICAgICAgICAgICAgIHdpbm5pbmdTZWF0OiBnLndpbm5pbmdTZWF0LAogICAgICAgICAgICAgICAgdHVybnM6IGcudG90YWxUdXJucywKICAgICAgICAgICAgICAgIHdpbkNvbmRpdGlvbjogZy53aW5Db25kaXRpb24sCiAgICAgICAgICAgICAgICBlbGFwc2VkOiBnLmVsYXBzZWRNcywKICAgICAgICAgICAgICAgIHBsYXllcnM6IGcucGxheWVyUmVzdWx0cy5tYXAoKHByLCBpKSA9PiAoewogICAgICAgICAgICAgICAgICAgIG5hbWU6IChkZWNrc1tpXT8uY29tbWFuZGVyTmFtZSB8fCBkZWNrc1tpXT8uZGVja05hbWUgfHwgJ0RlY2sgJyArIChpICsgMSkpLnN1YnN0cmluZygwLCAxNiksCiAgICAgICAgICAgICAgICAgICAgbGlmZTogcHIuZmluYWxMaWZlLAogICAgICAgICAgICAgICAgICAgIHNlYXQ6IGksCiAgICAgICAgICAgICAgICAgICAgaXNXaW5uZXI6IHByLmlzV2lubmVyLAogICAgICAgICAgICAgICAgfSkpLAogICAgICAgICAgICB9OwogICAgICAgIH0pOwoKICAgICAgICAvLyBXaXJlIHVwIGZpbHRlcgogICAgICAgIGlmIChmaWx0ZXJJbnB1dCkgewogICAgICAgICAgICBmaWx0ZXJJbnB1dC5vbmlucHV0ID0gKCkgPT4gZmlsdGVyR2FtZUxvZyhmaWx0ZXJJbnB1dC52YWx1ZSk7CiAgICAgICAgfQoKICAgICAgICByZW5kZXJHYW1lTG9nVGFibGUoZ2FtZUxvZ0RhdGEpOwogICAgfQoKICAgIGZ1bmN0aW9uIGZpbHRlckdhbWVMb2cocXVlcnkpIHsKICAgICAgICBjb25zdCBxID0gcXVlcnkudG9Mb3dlckNhc2UoKS50cmltKCk7CiAgICAgICAgaWYgKCFxKSB7CiAgICAgICAgICAgIHJlbmRlckdhbWVMb2dUYWJsZShnYW1lTG9nRGF0YSk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CiAgICAgICAgY29uc3QgZmlsdGVyZWQgPSBnYW1lTG9nRGF0YS5maWx0ZXIoZyA9PgogICAgICAgICAgICBnLndpbm5lci50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKHEpIHx8CiAgICAgICAgICAgIChnLndpbkNvbmRpdGlvbiB8fCAnJykudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKSB8fAogICAgICAgICAgICBmb3JtYXRXaW5Db25kaXRpb24oZy53aW5Db25kaXRpb24pLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMocSkgfHwKICAgICAgICAgICAgZy5wbGF5ZXJzLnNvbWUocCA9PiBwLm5hbWUudG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhxKSkKICAgICAgICApOwogICAgICAgIHJlbmRlckdhbWVMb2dUYWJsZShmaWx0ZXJlZCk7CiAgICB9CgogICAgZnVuY3Rpb24gcmVuZGVyR2FtZUxvZ1RhYmxlKGRhdGEpIHsKICAgICAgICBjb25zdCB3cmFwcGVyID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1nYW1lbG9nLXdyYXBwZXInKTsKICAgICAgICBjb25zdCBjb3VudEVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1nYW1lbG9nLWNvdW50Jyk7CiAgICAgICAgaWYgKCF3cmFwcGVyKSByZXR1cm47CgogICAgICAgIGlmIChjb3VudEVsKSB7CiAgICAgICAgICAgIGNvdW50RWwudGV4dENvbnRlbnQgPSBkYXRhLmxlbmd0aCArICcgZ2FtZScgKyAoZGF0YS5sZW5ndGggIT09IDEgPyAncycgOiAnJyk7CiAgICAgICAgfQoKICAgICAgICAvLyBTaG93IG1heCAyMDAgcm93cywgcGFnaW5hdGVkCiAgICAgICAgY29uc3QgcGFnZVNpemUgPSAyMDA7CiAgICAgICAgY29uc3QgZGlzcGxheSA9IGRhdGEuc2xpY2UoMCwgcGFnZVNpemUpOwoKICAgICAgICBsZXQgaHRtbCA9IGA8dGFibGUgY2xhc3M9ImxhYi1nYW1lbG9nLXRhYmxlIj4KICAgICAgICAgICAgPHRoZWFkPjx0cj4KICAgICAgICAgICAgICAgIDx0aD4jPC90aD4KICAgICAgICAgICAgICAgIDx0aD5XaW5uZXI8L3RoPgogICAgICAgICAgICAgICAgPHRoPlR1cm5zPC90aD4KICAgICAgICAgICAgICAgIDx0aD5XaW4gQ29uZGl0aW9uPC90aD5gOwogICAgICAgIC8vIExpZmUgY29sdW1ucyBmb3IgZWFjaCBzZWF0CiAgICAgICAgaWYgKGRpc3BsYXkubGVuZ3RoID4gMCAmJiBkaXNwbGF5WzBdLnBsYXllcnMpIHsKICAgICAgICAgICAgZGlzcGxheVswXS5wbGF5ZXJzLmZvckVhY2goKHAsIGkpID0+IHsKICAgICAgICAgICAgICAgIGh0bWwgKz0gYDx0aCBzdHlsZT0iY29sb3I6JHtTRUFUX0NPTE9SU1tpXX0iPiR7ZXNjYXBlSHRtbChwLm5hbWUpfTwvdGg+YDsKICAgICAgICAgICAgfSk7CiAgICAgICAgfQogICAgICAgIGh0bWwgKz0gJzwvdHI+PC90aGVhZD48dGJvZHk+JzsKCiAgICAgICAgZGlzcGxheS5mb3JFYWNoKGcgPT4gewogICAgICAgICAgICBjb25zdCB3aW5Db2xvciA9IGcud2lubmluZ1NlYXQgIT09IG51bGwgJiYgZy53aW5uaW5nU2VhdCAhPT0gdW5kZWZpbmVkCiAgICAgICAgICAgICAgICA/IFNFQVRfQ09MT1JTW2cud2lubmluZ1NlYXRdIDogJyM4Yjk0OWUnOwogICAgICAgICAgICBodG1sICs9IGA8dHI+CiAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImxhYi1nbC1udW0iPiR7Zy5pbmRleH08L3RkPgogICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJjb2xvcjoke3dpbkNvbG9yfSI+JHtlc2NhcGVIdG1sKGcud2lubmVyKX08L3RkPgogICAgICAgICAgICAgICAgPHRkPiR7Zy50dXJuc308L3RkPgogICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJsYWItZ2wtd2MiPiR7Zm9ybWF0V2luQ29uZGl0aW9uKGcud2luQ29uZGl0aW9uKX08L3RkPmA7CiAgICAgICAgICAgIGcucGxheWVycy5mb3JFYWNoKHAgPT4gewogICAgICAgICAgICAgICAgY29uc3QgbGlmZUNsYXNzID0gcC5saWZlIDw9IDAgPyAnbGFiLWdsLWRlYWQnIDogcC5pc1dpbm5lciA/ICdsYWItZ2wtYWxpdmUnIDogJyc7CiAgICAgICAgICAgICAgICBodG1sICs9IGA8dGQgY2xhc3M9IiR7bGlmZUNsYXNzfSI+JHtwLmxpZmV9PC90ZD5gOwogICAgICAgICAgICB9KTsKICAgICAgICAgICAgaHRtbCArPSAnPC90cj4nOwogICAgICAgIH0pOwogICAgICAgIGh0bWwgKz0gJzwvdGJvZHk+PC90YWJsZT4nOwoKICAgICAgICBpZiAoZGF0YS5sZW5ndGggPiBwYWdlU2l6ZSkgewogICAgICAgICAgICBodG1sICs9IGA8cCBjbGFzcz0ibGFiLWdsLW1vcmUiPlNob3dpbmcgJHtwYWdlU2l6ZX0gb2YgJHtkYXRhLmxlbmd0aH0gZ2FtZXM8L3A+YDsKICAgICAgICB9CgogICAgICAgIHdyYXBwZXIuaW5uZXJIVE1MID0gaHRtbDsKICAgIH0KCiAgICAvLyDilIDilIAgSGlzdG9yeSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCiAgICBhc3luYyBmdW5jdGlvbiBzaG93SGlzdG9yeSgpIHsKICAgICAgICBjb25zdCBtb2RhbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItaGlzdG9yeS1tb2RhbCcpOwogICAgICAgIGNvbnN0IGxpc3RFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItaGlzdG9yeS1saXN0Jyk7CiAgICAgICAgaWYgKCFtb2RhbCB8fCAhbGlzdEVsKSByZXR1cm47CgogICAgICAgIG1vZGFsLmNsYXNzTGlzdC5hZGQoJ2FjdGl2ZScpOwogICAgICAgIGxpc3RFbC5pbm5lckhUTUwgPSAnTG9hZGluZy4uLic7CgogICAgICAgIGlmICghYmFja2VuZEF2YWlsYWJsZSkgewogICAgICAgICAgICBsaXN0RWwuaW5uZXJIVE1MID0gJzxwIGNsYXNzPSJsYWItaGlzdG9yeS1lbXB0eSI+SGlzdG9yeSByZXF1aXJlcyBhIHJ1bm5pbmcgYmFja2VuZC48L3A+JzsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KCiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7QVBJX0JBU0V9L2FwaS9sYWIvaGlzdG9yeWApOwogICAgICAgICAgICBpZiAoIXJlcy5vaykgdGhyb3cgbmV3IEVycm9yKCdGYWlsZWQgdG8gbG9hZCBoaXN0b3J5Jyk7CiAgICAgICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpOwoKICAgICAgICAgICAgaWYgKCFkYXRhLnJlc3VsdHMgfHwgZGF0YS5yZXN1bHRzLmxlbmd0aCA9PT0gMCkgewogICAgICAgICAgICAgICAgbGlzdEVsLmlubmVySFRNTCA9ICc8cCBjbGFzcz0ibGFiLWhpc3RvcnktZW1wdHkiPk5vIHBhc3QgcnVucyBmb3VuZC48L3A+JzsKICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgbGlzdEVsLmlubmVySFRNTCA9IGRhdGEucmVzdWx0cy5tYXAociA9PiB7CiAgICAgICAgICAgICAgICBjb25zdCBkZWNrTGFiZWxzID0gKHIuZGVja3MgfHwgW10pLm1hcChkID0+IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBzcmNUYWcgPSBkLnNvdXJjZSA/IGAgPHNwYW4gY2xhc3M9Imhpc3Qtc3JjIj5bJHtkLnNvdXJjZX1dPC9zcGFuPmAgOiAnJzsKICAgICAgICAgICAgICAgICAgICByZXR1cm4gYCR7ZXNjYXBlSHRtbChkLm5hbWUpfSR7c3JjVGFnfWA7CiAgICAgICAgICAgICAgICB9KS5qb2luKCcsICcpOwoKICAgICAgICAgICAgICAgIHJldHVybiBgCiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJsYWItaGlzdG9yeS1pdGVtIiBvbmNsaWNrPSJBaUxhYi5sb2FkSGlzdG9yeVJlc3VsdCgnJHtyLmZpbGVuYW1lfScpIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJoaXN0b3J5LWJhdGNoLWlkIj4ke3IuYmF0Y2hJZH08L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJoaXN0b3J5LW1ldGEiPgogICAgICAgICAgICAgICAgICAgICAgICAke3IudG90YWxHYW1lc30gZ2FtZXMgwrcgJHtyLnRocmVhZHN9IHRocmVhZHMgwrcgJHsoci5lbGFwc2VkTXMvMTAwMCkudG9GaXhlZCgxKX1zCiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iaGlzdG9yeS1kZWNrcyI+JHtkZWNrTGFiZWxzfTwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Imhpc3RvcnktdGltZSI+JHtuZXcgRGF0ZShyLnRpbWVzdGFtcCkudG9Mb2NhbGVTdHJpbmcoKX08L2Rpdj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgYDsKICAgICAgICAgICAgfSkuam9pbignJyk7CiAgICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgICAgIGxpc3RFbC5pbm5lckhUTUwgPSBgPHAgY2xhc3M9ImxhYi1oaXN0b3J5LWVtcHR5Ij5FcnJvcjogJHtlcnIubWVzc2FnZX08L3A+YDsKICAgICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gY2xvc2VIaXN0b3J5KCkgewogICAgICAgIGNvbnN0IG1vZGFsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhYi1oaXN0b3J5LW1vZGFsJyk7CiAgICAgICAgaWYgKG1vZGFsKSBtb2RhbC5jbGFzc0xpc3QucmVtb3ZlKCdhY3RpdmUnKTsKICAgIH0KCiAgICBhc3luYyBmdW5jdGlvbiBsb2FkSGlzdG9yeVJlc3VsdChmaWxlbmFtZSkgewogICAgICAgIGNsb3NlSGlzdG9yeSgpOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIC8vIEZldGNoIGZyb20gcmVzdWx0cyBkaXJlY3RvcnkgdmlhIEFQSQogICAgICAgICAgICBjb25zdCBiYXRjaElkID0gZmlsZW5hbWUucmVwbGFjZSgnYmF0Y2gtJywgJycpLnJlcGxhY2UoJy5qc29uJywgJycpOwogICAgICAgICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaChgJHtBUElfQkFTRX0vYXBpL2xhYi9yZXN1bHQ/YmF0Y2hJZD0ke2JhdGNoSWR9YCk7CiAgICAgICAgICAgIGlmIChyZXMub2spIHsKICAgICAgICAgICAgICAgIGxhc3RSZXN1bHQgPSBhd2FpdCByZXMuanNvbigpOwogICAgICAgICAgICAgICAgZGlzcGxheVJlc3VsdHMobGFzdFJlc3VsdCk7CiAgICAgICAgICAgIH0KICAgICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAgICAgICAgY29uc29sZS5lcnJvcignW0FJIExhYl0gRmFpbGVkIHRvIGxvYWQgaGlzdG9yeSByZXN1bHQ6JywgZXJyKTsKICAgICAgICB9CiAgICB9CgogICAgLy8g4pSA4pSAIEhlbHBlcnMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgZnVuY3Rpb24gZm9ybWF0V2luQ29uZGl0aW9uKGtleSkgewogICAgICAgIGNvbnN0IG1hcCA9IHsKICAgICAgICAgICAgY29tYmF0X2RhbWFnZTogJ0NvbWJhdCcsCiAgICAgICAgICAgIGNvbW1hbmRlcl9kYW1hZ2U6ICdDbWRyIERtZycsCiAgICAgICAgICAgIGNvbWJvX2FsdF93aW46ICdDb21ibycsCiAgICAgICAgICAgIGxpZmVfZHJhaW46ICdEcmFpbicsCiAgICAgICAgICAgIG1pbGw6ICdNaWxsJywKICAgICAgICAgICAgY29uY2Vzc2lvbjogJ0NvbmNlZGUnLAogICAgICAgICAgICB0aW1lb3V0OiAnVGltZW91dCcsCiAgICAgICAgICAgIHVua25vd246ICc/Pz8nLAogICAgICAgIH07CiAgICAgICAgcmV0dXJuIG1hcFtrZXldIHx8IGtleTsKICAgIH0KCiAgICBmdW5jdGlvbiBzZXRSdW5uaW5nKHJ1bm5pbmcpIHsKICAgICAgICBpc1J1bm5pbmcgPSBydW5uaW5nOwogICAgICAgIGNvbnN0IGJ0biA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItcnVuLWJ0bicpOwogICAgICAgIGlmIChidG4pIHsKICAgICAgICAgICAgYnRuLmRpc2FibGVkID0gcnVubmluZzsKICAgICAgICAgICAgYnRuLmNsYXNzTGlzdC50b2dnbGUoJ3J1bm5pbmcnLCBydW5uaW5nKTsKICAgICAgICAgICAgYnRuLnF1ZXJ5U2VsZWN0b3IoJy5ydW4tdGV4dCcpLnRleHRDb250ZW50ID0gcnVubmluZyA/ICdTaW11bGF0aW5nLi4uJyA6ICdSdW4gQUkgU2ltcyc7CiAgICAgICAgfQogICAgICAgIGlmICghcnVubmluZykgewogICAgICAgICAgICBzdG9wUG9sbGluZygpOwogICAgICAgICAgICAvLyBLZWVwIHByb2dyZXNzIHZpc2libGUgYnJpZWZseSwgdGhlbiBmYWRlCiAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4gewogICAgICAgICAgICAgICAgY29uc3QgcHJvZ3Jlc3NFbCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItcHJvZ3Jlc3MnKTsKICAgICAgICAgICAgICAgIGlmIChwcm9ncmVzc0VsKSBwcm9ncmVzc0VsLmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpOwogICAgICAgICAgICB9LCAzMDAwKTsKICAgICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gc2VlZGVkUmFuZG9tKHNlZWQpIHsKICAgICAgICBsZXQgcyA9IHNlZWQgfCAwOwogICAgICAgIHJldHVybiBmdW5jdGlvbigpIHsKICAgICAgICAgICAgcyB8PSAwOyBzID0gcyArIDB4NkQyQjc5RjUgfCAwOwogICAgICAgICAgICBsZXQgdCA9IE1hdGguaW11bChzIF4gcyA+Pj4gMTUsIDEgfCBzKTsKICAgICAgICAgICAgdCA9IHQgKyBNYXRoLmltdWwodCBeIHQgPj4+IDcsIDYxIHwgdCkgXiB0OwogICAgICAgICAgICByZXR1cm4gKCh0IF4gdCA+Pj4gMTQpID4+PiAwKSAvIDQyOTQ5NjcyOTY7CiAgICAgICAgfTsKICAgIH0KCiAgICBmdW5jdGlvbiBleHBvcnRKc29uKCkgewogICAgICAgIGlmICghbGFzdFJlc3VsdCkgcmV0dXJuOwogICAgICAgIGNvbnN0IGpzb24gPSBKU09OLnN0cmluZ2lmeShsYXN0UmVzdWx0LCBudWxsLCAyKTsKICAgICAgICBjb25zdCBibG9iID0gbmV3IEJsb2IoW2pzb25dLCB7IHR5cGU6ICdhcHBsaWNhdGlvbi9qc29uJyB9KTsKICAgICAgICBjb25zdCB1cmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpOwogICAgICAgIGNvbnN0IGEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7CiAgICAgICAgYS5ocmVmID0gdXJsOwogICAgICAgIGEuZG93bmxvYWQgPSBgY29tbWFuZGVyLWFpLWxhYi0ke2xhc3RSZXN1bHQubWV0YWRhdGEuYmF0Y2hJZH0uanNvbmA7CiAgICAgICAgYS5jbGljaygpOwogICAgICAgIFVSTC5yZXZva2VPYmplY3RVUkwodXJsKTsKICAgIH0KCiAgICBmdW5jdGlvbiBlc2NhcGVIdG1sKHN0cikgewogICAgICAgIGlmICghc3RyKSByZXR1cm4gJyc7CiAgICAgICAgcmV0dXJuIHN0ci5yZXBsYWNlKC8mL2csICcmYW1wOycpLnJlcGxhY2UoLzwvZywgJyZsdDsnKS5yZXBsYWNlKC8+L2csICcmZ3Q7JykucmVwbGFjZSgvIi9nLCAnJnF1b3Q7Jyk7CiAgICB9CgogICAgZnVuY3Rpb24gZXNjYXBlQXR0cihzdHIpIHsKICAgICAgICByZXR1cm4gc3RyLnJlcGxhY2UoLycvZywgIlxcJyIpLnJlcGxhY2UoLyIvZywgJ1xcIicpOwogICAgfQoKICAgIGZ1bmN0aW9uIGJpbmRFdmVudHMoKSB7CiAgICAgICAgY29uc3QgaW5wdXQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFiLWdhbWUtY291bnQnKTsKICAgICAgICBpZiAoaW5wdXQpIHsKICAgICAgICAgICAgaW5wdXQuYWRkRXZlbnRMaXN0ZW5lcignY2hhbmdlJywgKCkgPT4gewogICAgICAgICAgICAgICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLmxhYi1wcmVzZXQtYnRuJykuZm9yRWFjaChidG4gPT4gewogICAgICAgICAgICAgICAgICAgIGJ0bi5jbGFzc0xpc3QudG9nZ2xlKCdhY3RpdmUnLCBwYXJzZUludChidG4udGV4dENvbnRlbnQpID09PSBwYXJzZUludChpbnB1dC52YWx1ZSkpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIH0pOwogICAgICAgIH0KCiAgICAgICAgLy8gQ2xvc2UgbW9kYWxzIG9uIG92ZXJsYXkgY2xpY2sKICAgICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcubGFiLW1vZGFsLW92ZXJsYXknKS5mb3JFYWNoKG1vZGFsID0+IHsKICAgICAgICAgICAgbW9kYWwuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4gewogICAgICAgICAgICAgICAgaWYgKGUudGFyZ2V0ID09PSBtb2RhbCkgewogICAgICAgICAgICAgICAgICAgIG1vZGFsLmNsYXNzTGlzdC5yZW1vdmUoJ2FjdGl2ZScpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9KTsKICAgICAgICB9KTsKCiAgICAgICAgLy8gRW50ZXIga2V5IGluIFVSTCBpbXBvcnQKICAgICAgICBjb25zdCB1cmxJbnB1dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsYWItaW1wb3J0LXVybC1pbnB1dCcpOwogICAgICAgIGlmICh1cmxJbnB1dCkgewogICAgICAgICAgICB1cmxJbnB1dC5hZGRFdmVudExpc3RlbmVyKCdrZXlkb3duJywgKGUpID0+IHsKICAgICAgICAgICAgICAgIGlmIChlLmtleSA9PT0gJ0VudGVyJykgaW1wb3J0RnJvbVVybCgpOwogICAgICAgICAgICB9KTsKICAgICAgICB9CiAgICB9CgogICAgLy8g4pSA4pSAIFB1YmxpYyBBUEkg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgogICAgcmV0dXJuIHsKICAgICAgICBpbml0LAogICAgICAgIHNlbGVjdERlY2ssCiAgICAgICAgc2V0R2FtZUNvdW50LAogICAgICAgIHN0YXJ0QmF0Y2gsCiAgICAgICAgZXhwb3J0SnNvbiwKICAgICAgICBzaG93SGlzdG9yeSwKICAgICAgICBjbG9zZUhpc3RvcnksCiAgICAgICAgbG9hZEhpc3RvcnlSZXN1bHQsCiAgICAgICAgdG9nZ2xlTG9nLAogICAgICAgIGdldExhc3RSZXN1bHQ6ICgpID0+IGxhc3RSZXN1bHQsCiAgICAgICAgLy8gdjMKICAgICAgICBzaG93VXJsSW1wb3J0LAogICAgICAgIGhpZGVVcmxJbXBvcnQsCiAgICAgICAgaW1wb3J0RnJvbVVybCwKICAgICAgICBzaG93TWV0YVBpY2tlciwKICAgICAgICBjbG9zZU1ldGFQaWNrZXIsCiAgICAgICAgZmlsdGVyTWV0YUNvbW1hbmRlcnMsCiAgICAgICAgZmV0Y2hNZXRhRGVjaywKICAgICAgICBzaG93VGV4dEltcG9ydCwKICAgICAgICBjbG9zZVRleHRJbXBvcnQsCiAgICAgICAgaW1wb3J0RnJvbVRleHQsCiAgICAgICAgLy8gUHJlY29uIGRlY2tzCiAgICAgICAgc2hvd1ByZWNvblBpY2tlciwKICAgICAgICBjbG9zZVByZWNvblBpY2tlciwKICAgICAgICBmaWx0ZXJQcmVjb25zLAogICAgICAgIGluc3RhbGxQcmVjb24sCiAgICAgICAgaW5zdGFsbEFsbFByZWNvbnMsCiAgICAgICAgLy8gdjQg4oCUIGVuaGFuY2VkIHJlc3VsdHMKICAgICAgICBzd2l0Y2hUYWIsCiAgICAgICAgLy8gdjUg4oCUIGFuYWx5dGljcywgdHJlbmRzLCBwcmVjb24gcmVmcmVzaAogICAgICAgIGxvYWRBbmFseXRpY3MsCiAgICAgICAgbG9hZFRyZW5kcywKICAgICAgICByZWZyZXNoUHJlY29ucywKICAgIH07Cgp9KSgpOwoKLy8gQXV0by1pbml0aWFsaXplIHdoZW4gRE9NIGlzIHJlYWR5CmlmIChkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnbG9hZGluZycpIHsKICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCBBaUxhYi5pbml0KTsKfSBlbHNlIHsKICAgIEFpTGFiLmluaXQoKTsKfQo= \ No newline at end of file