// 

import { navigateDisklet } from 'disklet'



import { FixCurrencyCode, InfoServer } from '../info/constants'
import { ServerCache } from './serverCache.js'


















export class PluginState extends ServerCache {
  // On-disk header information:
  
  





  // True if somebody is currently fetching a header:
  



  // On-disk server information:
  

  /**
   * Begins notifying the engine of state changes. Used at connection time.
   */
  addEngine(engineState) {
    this.engines.push(engineState)
  }

  /**
   * Stops notifying the engine of state changes. Used at disconnection time.
   */
  removeEngine(engineState) {
    this.engines = this.engines.filter(engine => engine !== engineState)
  }

  dumpData() {
    return {
      'pluginState.headerCache': this.headerCache,
      'pluginState.servers_': this.servers_
    }
  }

  // ------------------------------------------------------------------------
  // Private stuff
  // ------------------------------------------------------------------------
  
  
  
  

  
  

  
  
  

  constructor({
    io,
    defaultSettings,
    currencyCode,
    pluginId,
    log
  }) {
    super(log)
    this.height = 0
    this.headerCache = {}
    this.io = io
    this.defaultServers = defaultSettings.electrumServers
    this.disableFetchingServers = !!defaultSettings.disableFetchingServers
    // Rename the bitcoin currencyCode to get the new version of the server list
    const fixedCode = FixCurrencyCode(currencyCode)
    this.infoServerUris = `${InfoServer}/electrumServers/${fixedCode}`
    this.engines = []
    this.disklet = navigateDisklet(io.disklet, 'plugins/' + pluginId)

    this.pluginId = pluginId
    this.headerCacheDirty = false
    this.serverCacheJson = {}
  }

  async load() {
    try {
      const headerCacheText = await this.disklet.getText('headers.json')
      const headerCacheJson = JSON.parse(headerCacheText)
      // TODO: Validate JSON

      this.height = headerCacheJson.height
      this.headerCache = headerCacheJson.headers
    } catch (e) {
      this.headerCache = {}
      this.log.error(`${this.pluginId}: Failed to load header cache: ${e}`)
    }

    try {
      const serverCacheText = await this.disklet.getText('serverCache.json')
      const serverCacheJson = JSON.parse(serverCacheText)
      // TODO: Validate JSON

      this.serverCacheJson = serverCacheJson
    } catch (e) {
      this.log.error(`${this.pluginId}: Failed to load server cache: ${e}`)
    }

    // Fetch stratum servers in the background:
    this.fetchStratumServers()

    return this
  }

  async clearCache() {
    this.clearServerCache()
    this.headerCache = {}
    this.headerCacheDirty = true
    this.serverCacheDirty = true
    await this.saveHeaderCache()
    await this.saveServerCache()
    await this.fetchStratumServers()
  }

  saveHeaderCache() {
    if (this.headerCacheDirty) {
      return this.disklet
        .setText(
          'headers.json',
          JSON.stringify({
            height: this.height,
            headers: this.headerCache
          })
        )
        .then(() => {
          this.log(`${this.pluginId} - Saved header cache`)
          this.headerCacheDirty = false
        })
        .catch(e => this.log.error(`${this.pluginId} - ${e.toString()}`))
    }
    return Promise.resolve()
  }

  async saveServerCache() {
    // this.printServerCache()
    if (this.serverCacheDirty) {
      try {
        await this.disklet.setText(
          'serverCache.json',
          JSON.stringify(this.servers_)
        )
        this.serverCacheDirty = false
        this.cacheLastSave_ = Date.now()
        this.log(`${this.pluginId} - Saved server cache`)
      } catch (e) {
        this.log.error(`${this.pluginId} - ${e.toString()}`)
      }
    }
  }

  dirtyServerCache(serverUrl) {
    this.serverCacheDirty = true
    for (const engine of this.engines) {
      if (engine.progressRatio === 1) {
        for (const uri in engine.serverStates) {
          if (uri === serverUrl) {
            this.saveServerCache()
            return
          }
        }
      }
    }
  }

  dirtyHeaderCache() {
    this.headerCacheDirty = true
    for (const engine of this.engines) {
      if (engine.progressRatio === 1) {
        this.saveHeaderCache()
        return
      }
    }
  }

  async fetchStratumServers() {
    const { io } = this
    let serverList = this.defaultServers
    if (!this.disableFetchingServers) {
      try {
        this.log(`${this.pluginId} - GET ${this.infoServerUris}`)
        const result = await io.fetch(this.infoServerUris)
        if (!result.ok) {
          this.log.error(
            `${this.pluginId} - Fetching ${this.infoServerUris} failed with ${result.status}`
          )
        } else {
          serverList = await result.json()
        }
      } catch (e) {
        this.log.error(e)
      }
    }
    if (!Array.isArray(serverList)) {
      serverList = this.defaultServers
    }
    this.serverCacheLoad(this.serverCacheJson, serverList)
    await this.saveServerCache()

    // Tell the engines about the new servers:
    for (const engine of this.engines) {
      engine.refillServers()
    }
  }

  updateHeight(height) {
    if (this.height < height) {
      this.height = height
      this.dirtyHeaderCache()

      // Tell the engines about our new height:
      for (const engine of this.engines) {
        engine.onHeightUpdated(height)
      }
    }
  }

  async updateServers(settings) {
    const { electrumServers, disableFetchingServers } = settings || {}
    if (typeof disableFetchingServers === 'boolean') {
      this.disableFetchingServers = disableFetchingServers
    }
    if (Array.isArray(electrumServers)) {
      this.defaultServers = electrumServers
    }
    const engines = []
    const disconnects = []
    for (const engine of this.engines) {
      engines.push(engine)
      engine.serverList = []
      disconnects.push(engine.disconnect())
    }
    await Promise.all(disconnects)
    this.clearServerCache()
    this.serverCacheJson = {}
    this.serverCacheDirty = true
    await this.saveServerCache()
    await this.fetchStratumServers()
    for (const engine of engines) {
      engine.connect()
    }
  }
}
