text: var Navigation = require('./slideshow/navigation')
    , Events = require('./slideshow/events')
    , utils = require('../utils')
    , Slide = require('./slide')
    , Parser = require('../parser')
    , macros = require('../macros')
    ;
  
  module.exports = Slideshow;
  
  function Slideshow (events, dom, options, callback) {
    var self = this
      , slides = []
      , links = {}
      ;
  
    slides.byName = {};
    options = options || {};
  
    // Extend slideshow functionality
    Events.call(self, events);
    Navigation.call(self, events);
  
    self.loadFromString = loadFromString;
    self.loadFromUrl = loadFromUrl;
    self.update = update;
    self.getLinks = getLinks;
    self.getSlides = getSlides;
    self.getSlideCount = getSlideCount;
    self.getSlideByName = getSlideByName;
  
    self.togglePresenterMode = togglePresenterMode;
    self.toggleHelp = toggleHelp;
    self.toggleBlackout = toggleBlackout;
    self.toggleMirrored = toggleMirrored;
    self.toggleFullscreen = toggleFullscreen;
    self.createClone = createClone;
  
    self.resetTimer = resetTimer;
  
    self.getRatio = getOrDefault('ratio', '4:3');
    self.getHighlightStyle = getOrDefault('highlightStyle', 'default');
    self.getHighlightLines = getOrDefault('highlightLines', false);
    self.getHighlightSpans = getOrDefault('highlightSpans', false);
    self.getHighlightInlineCode = getOrDefault('highlightInlineCode', false);
    self.getHighlightLanguage = getOrDefault('highlightLanguage', '');
    self.getSlideNumberFormat = getOrDefault('slideNumberFormat', '%current% / %total%');
    self.getCloneTarget = getOrDefault('cloneTarget', '_blank');
  
    events.on('toggleBlackout', function () {
      if (self.clone && !self.clone.closed) {
        self.clone.postMessage('toggleBlackout', '*');
      }
    });
  
    if (options.sourceUrl) {
      loadFromUrl(options.sourceUrl, callback);
    }
    else {
      loadFromString(options.source);
      if (typeof callback === 'function') {
        callback(self);
      }
    }
  
    function loadFromString (source) {
      source = source || '';
  
      slides = createSlides(source, options);
      expandVariables(slides);
  
      links = {};
      slides.forEach(function (slide) {
        for (var id in slide.links) {
          if (slide.links.hasOwnProperty(id)) {
            links[id] = slide.links[id];
          }
        }
      });
  
      events.emit('slidesChanged');
    }
    
    function loadFromUrl (url, callback) {
      var xhr = new dom.XMLHttpRequest();
      xhr.open('GET', options.sourceUrl, true);
      xhr.onload = function (e) {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            options.source = xhr.responseText.replace(/\r\n/g, '\n');
            loadFromString(options.source);
            if (typeof callback === 'function') {
              callback(self);
            }
          } else {
            throw Error(xhr.statusText);
          }
        }
      };
      xhr.onerror = function (e) {
        throw Error(xhr.statusText);
      };
      xhr.send(null);
      return xhr;
    }
  
    function update () {
      events.emit('resize');
    }
  
    function getLinks () {
      return links;
    }
  
    function getSlides () {
      return slides.map(function (slide) { return slide; });
    }
  
    function getSlideCount () {
      return slides.length;
    }
  
    function getSlideByName (name) {
      return slides.byName[name];
    }
  
    function togglePresenterMode () {
      events.emit('togglePresenterMode');
    }
  
    function toggleHelp () {
      events.emit('toggleHelp');
    }
  
    function toggleBlackout () {
      events.emit('toggleBlackout');
    }
  
    function toggleMirrored() {
      events.emit('toggleMirrored');
    }
  
    function toggleFullscreen () {
      events.emit('toggleFullscreen');
    }
  
    function createClone () {
      events.emit('createClone');
    }
  
    function resetTimer () {
      events.emit('resetTimer');
    }
  
    function getOrDefault (key, defaultValue) {
      return function () {
        if (options[key] === undefined) {
          return defaultValue;
        }
  
        return options[key];
      };
    }
  }
  
  function createSlides (slideshowSource, options) {
    var parser = new Parser()
     ,  parsedSlides = parser.parse(slideshowSource, macros)
      , slides = []
      , byName = {}
      , layoutSlide
      ;
  
    slides.byName = {};
  
    parsedSlides.forEach(function (slide, i) {
      var template, slideViewModel;
  
      if (slide.properties.continued === 'true' && i > 0) {
        template = slides[slides.length - 1];
      }
      else if (byName[slide.properties.template]) {
        template = byName[slide.properties.template];
      }
      else if (slide.properties.layout === 'false') {
        layoutSlide = undefined;
      }
      else if (layoutSlide && slide.properties.layout !== 'true') {
        template = layoutSlide;
      }
  
      if (slide.properties.continued === 'true' &&
          options.countIncrementalSlides === false &&
          slide.properties.count === undefined) {
        slide.properties.count = 'false';
      }
  
      slideViewModel = new Slide(slides.length, slide, template);
  
      if (slide.properties.layout === 'true') {
        layoutSlide = slideViewModel;
      }
  
      if (slide.properties.name) {
        byName[slide.properties.name] = slideViewModel;
      }
  
      if (slide.properties.layout !== 'true') {
        slides.push(slideViewModel);
        if (slide.properties.name) {
          slides.byName[slide.properties.name] = slideViewModel;
        }
      }
    });
  
    return slides;
  }
  
  function expandVariables (slides) {
    slides.forEach(function (slide) {
      slide.expandVariables();
    });
  }
  