text: const XMLWriter = require('xml-writer')
  const fs = require('fs')
  
  module.exports = { WriteXMLTV: WriteXMLTV, shutdown: shutdown }
  
  let isShutdown = false;
  let isWorking = false;
  
  async function WriteXMLTV(json, xmlSettings, throttle, cacheImageService) {
      if (isShutdown) {
          return;
      }
      if (isWorking) {
          console.log("Concurrent xmltv write attempt detected, skipping");
          return;
      }
      isWorking = true;
      try {
          await writePromise(json, xmlSettings, throttle, cacheImageService);
      } catch (err) {
          console.error("Error writing xmltv", err);
      }
      isWorking = false;
  }
  
  function writePromise(json, xmlSettings, throttle, cacheImageService) {
      return new Promise((resolve, reject) => {
          let ws = fs.createWriteStream(xmlSettings.file)
          let xw = new XMLWriter(true, (str, enc) => ws.write(str, enc))
          ws.on('close', () => { resolve() })
          ws.on('error', (err) => { reject(err) })
          _writeDocStart(xw)
          async function middle() {
              let channelNumbers = [];
              Object.keys(json).forEach( (key, index) => channelNumbers.push(key) );
              let channels = channelNumbers.map( (number) => json[number].channel );
              _writeChannels( xw, channels );
              for (let i = 0; i < channelNumbers.length; i++) {
                  let number = channelNumbers[i];
                  await _writePrograms(xw, json[number].channel, json[number].programs, throttle, xmlSettings, cacheImageService);
              }
          }
        middle().then( () => {
          _writeDocEnd(xw, ws)
        }).catch( (err) => {
            console.error("Error", err);
        }).then( () => ws.end() );
      })
  }
  
  function _writeDocStart(xw) {
      xw.startDocument()
      xw.startElement('tv')
      xw.writeAttribute('generator-info-name', 'psuedotv-plex')
  }
  function _writeDocEnd(xw, ws) {
      xw.endElement()
      xw.endDocument()
  }
  
  function _writeChannels(xw, channels) {
      for (let i = 0; i < channels.length; i++) {
          xw.startElement('channel')
          xw.writeAttribute('id', channels[i].number)
          xw.startElement('display-name')
          xw.writeAttribute('lang', 'en')
          xw.text(channels[i].name)
          xw.endElement()
          if (channels[i].icon) {
              xw.startElement('icon')
              xw.writeAttribute('src', channels[i].icon)
              xw.endElement()
          }
          xw.endElement()
      }
  }
  
  async function _writePrograms(xw, channel, programs, throttle, xmlSettings, cacheImageService) {
      for (let i = 0; i < programs.length; i++) {
          if (! isShutdown) {
              await throttle();
          }
          await _writeProgramme(channel, programs[i], xw, xmlSettings, cacheImageService);
      }
  }
  
  async function _writeProgramme(channel, program, xw, xmlSettings, cacheImageService) {
      // Programme
      xw.startElement('programme')
      xw.writeAttribute('start', _createXMLTVDate(program.start))
      xw.writeAttribute('stop', _createXMLTVDate(program.stop ))
      xw.writeAttribute('channel', channel.number)
      // Title
      xw.startElement('title')
      xw.writeAttribute('lang', 'en')
      xw.text(program.title);
      xw.endElement();
      xw.writeRaw('\n        <previously-shown/>')
  
      //sub-title
      if ( typeof(program.sub) !== 'undefined') {
          xw.startElement('sub-title')
          xw.writeAttribute('lang', 'en')
          xw.text(program.sub.title)
          xw.endElement()
  
          xw.startElement('episode-num')
          xw.writeAttribute('system', 'onscreen')
          xw.text( "S" + (program.sub.season) + ' E' + (program.sub.episode) )
          xw.endElement()
  
          xw.startElement('episode-num')
          xw.writeAttribute('system', 'xmltv_ns')
          xw.text((program.sub.season - 1) + '.' + (program.sub.episode - 1) + '.0/1')
          xw.endElement()
  
      }
      // Icon
      if (typeof program.icon !== 'undefined') {
          xw.startElement('icon');
          let icon = program.icon;
          if (xmlSettings.enableImageCache === true) {
              const imgUrl = cacheImageService.registerImageOnDatabase(icon);
              icon = `{{host}}/cache/images/${imgUrl}`;
          }
          xw.writeAttribute('src', icon);
          xw.endElement();
      }
      // Desc
      xw.startElement('desc')
      xw.writeAttribute('lang', 'en')
      if ( (typeof(program.summary) !== 'undefined') && (program.summary.length > 0) ) {
          xw.text(program.summary)
      } else {
          xw.text(channel.name)
      }
      xw.endElement()
      // Rating
      if ( (program.rating != null) && (typeof program.rating !== 'undefined') ) {
          xw.startElement('rating')
          xw.writeAttribute('system', 'MPAA')
          xw.writeElement('value', program.rating)
          xw.endElement()
      }
      // End of Programme
      xw.endElement()
  }
  function _createXMLTVDate(d) {
      return d.substring(0,19).replace(/[-T:]/g,"") + " +0000";
  }
  function wait(x) {
      return new Promise((resolve) => {
        setTimeout(resolve, x);
      });
  }
  
  async function shutdown() {
      isShutdown = true;
      console.log("Shutting down xmltv writer.");
      if (isWorking) {
          let s = "Wait for xmltv writer...";
          while (isWorking) {
              console.log(s);
              await wait(100);
              s = "Still waiting for xmltv writer...";
          }
          console.log("Write finished.");
      } else {
          console.log("xmltv writer had no pending jobs.");
      }
  }
  
  