content: import React, { Component } from 'react';
  import ReactDOM from 'react-dom';
  import Immutable from 'immutable';
  import traverse from 'traverse';
  import smoothScrollTo from './smoothScrollTo';
  import scrollStream from './scrollStream';
  import createEmmiterStream from './createEmmiterStream';
  
  const debug = true
  
  let visibilityMap = Immutable.fromJS({});
  let registered = {};
  
  const getSibling = (node, offset) =>{
      const parent = node.parent.parent
      const keys = parent.keys;
      const myIndex = keys.indexOf(node.node.id)
  
      const siblingKey = keys[myIndex - offset]
      const siblingNode = parent.node[siblingKey];
      if(!siblingNode){
          return null;
      }
      const res = siblingNode.value.id
  
      return res
  }
  
  const getSiblingByHash = (hash, direction)=>{
      let found = null;
      traverse(visibilityMap.toJS())
      .forEach(function (x) {
          if(found){
              return;
          }
          if(hash === x.id){
              found = getSibling(this, direction)
          }
      });
  
      return found;
  }
  
  const makeHashPathArray = (id)=>
      id.split('#').reduce((previousValue, currentItem)=> {
          const last = previousValue.length - 1;
          const lastArr = previousValue.length === 0 ? [] : previousValue[last]
          return [...previousValue, [...lastArr, currentItem]];
      }, [])
  
  const splitLocationOnPathnameAndHash = (location)=>{
      const hashPos = location.indexOf('#');
      if(hashPos === -1) {
          return {pathname:location}
      }
      const pathname = location.slice(0, hashPos);
      const hash = location.slice(hashPos + 1);
      return {pathname, hash}
  }
  
  export const transitionTo = (location, router, link, scrollEmmiter)=>{
      const {pathname, hash:id} = splitLocationOnPathnameAndHash(link);
  
      if(location.pathname != pathname) {
          router.push(link);
          return;
      }
  
      let arrays = makeHashPathArray(id).map((arr)=>arr.join('#'));
  
      const oldHash = location.hash.slice(1)
  
      arrays = arrays.filter((partialHash, index, numbersArray)=>{
          if(numbersArray.length - 1 === index){
              return true;
          }
          debug && console.log(`oldHash - ${oldHash} | partialHash - ${partialHash} | matched - ${oldHash.indexOf(partialHash) !== -1}`)
          return oldHash.indexOf(partialHash) === -1
      })
  
      const getTransitionData = (id)=>
      ({
          scroller: registered[id].outerScroller,
          targetElement: document.getElementById(id),
          id
      })
  
      const arraysOfTransitionData = arrays.reduce((previousValue, currentItem)=> {
          const last = previousValue.length - 1;
          const lastArr = previousValue.length === 0 ? [] : previousValue[last];
  
          const currentTransitionData = getTransitionData(currentItem);
  
          if(previousValue.length === 0){
              return [currentTransitionData];
          }
  
          const res = previousValue.slice(0, last);
  
          let append = [];
          if(currentTransitionData.scroller !== previousValue[last].scroller) {
              append.push(previousValue[last])
          }
  
          append.push(currentTransitionData)
  
          return [...res, ...append];
      }, []);
  
      if(arrays.length === 0) {
          return
      }
  
  
      const scrollToIdPromise = ({scroller, targetElement, id})=>{
  
          return smoothScrollTo(scroller, targetElement.parentNode.offsetTop - scroller.offsetTop, 500, scrollEmmiter)
          .then(()=>{
              debug && console.log(`transited to ${id}`);
          });    
      }
  
      const chainAllPromise = Promise.all(arraysOfTransitionData.map(scrollToIdPromise));
  
      return chainAllPromise.then(()=>{
          router.push(`${location.pathname}#${arrays[arrays.length - 1]}`);
      });
  }
  
  export const MyLink = ({children}, {location, router, scrollEmmiter})=>
      <a style={{
              color: '-webkit-link',
              textDecoration: 'underline',
              cursor: 'pointer'
          }}
          onClick={()=>transitionTo(location, router, children, scrollEmmiter)}>
          {children}
      </a>
  
  MyLink.contextTypes = {
      location: React.PropTypes.object,
      router: React.PropTypes.object,
      scrollEmmiter: React.PropTypes.object,
  }
  
  const getFirstHashWithInnerScroll = (hash)=>{
      let res = null;
  
      makeHashPathArray(hash).map((arr)=>arr.join('#'))
      .reverse()
      .forEach((item)=>{
          if(res){
              return;
          }
          if(registered[item].innerScroller){
              res = item;
          }
      })
      return res ? res : hash;
  }
  
  export default class SectionContainer extends Component {
      constructor(...args){
          super(...args);
  
          this.state = {
              arr: Immutable.fromJS([])
          }
  
          this.register = this.register.bind(this);
          this.unregister = this.unregister.bind(this);
          this.setVisible = this.setVisible.bind(this);
          this.tryToRouteToSibling = this.tryToRouteToSibling.bind(this);
          this.getCurrentHash = this.getCurrentHash.bind(this);
          this.transitionTo = this.transitionTo.bind(this);
      }
  
      getCurrentHash () {
          return this.context.location.hash.slice(1)
      }
  
      register(id, data) {
          registered[id] = data;
      }
  
      unregister(id) {
          delete registered[id];
      }
  
      componentWillReceiveProps(props, context) {
          if((context.location && this.context.location) && 
              (context.location.pathname !== this.context.location.pathname ||
               context.location.hash !== this.context.location.hash))
          {
              debug && console.log(context.location.pathname + context.location.hash)
              // this.transitionTo(context.location.pathname + context.location.hash);
          }
      }
  
      transitionTo(nextLocation) {
         const {pathname, hash} = splitLocationOnPathnameAndHash(nextLocation);
  
         this.state.enableEmmiter.emit(false);
         this.state.myScrollStream.offValue(this.tryToRouteToSibling)
         const siblingInnerScroller = registered[hash].innerScroller;
         if(siblingInnerScroller){
              siblingInnerScroller.style.overflow = 'hidden'; 
         }
         
         transitionTo(this.context.location, this.context.router, nextLocation)
         .then(()=>{
              setTimeout(()=>{
                  this.state.enableEmmiter.emit(true);
                  this.state.myScrollStream.onValue(this.tryToRouteToSibling)
                  if(siblingInnerScroller){
                      siblingInnerScroller.style.overflow = 'scroll';
                  }
              }, this.props.delayAfterTransition || 300)
         })
      }
  
      tryToRouteToSibling(scrollData){
          const {isAtTop, isAtBottom, direction} = scrollData;
  
          if(direction !== 0 && (isAtTop && direction > 0) || (isAtBottom && direction < 0)){
             let sibling = getSiblingByHash(getFirstHashWithInnerScroll(this.getCurrentHash()), direction);
  
             if(!sibling){
              return 
             }
  
             this.transitionTo(this.context.location.pathname + "#" + sibling)
          }
      }
  
      componentDidMount() {
          const {
              emitter: scrollOnTransitionEmmiter,
              stream: scrollOnTransitionStream
          } = createEmmiterStream()
  
          const {
              emitter: enableEmmiter,
              stream: enableStream
          } = createEmmiterStream()
  
          const currentHash  = this.getCurrentHash();
          if(!currentHash){
              this.context.router.replace(this.context.location.pathname + "/#" + this.props.defaultId);
          }
  
          const myScrollStream = scrollStream()
              .throttle(this.props.throttleScrollEvents || 100)
              .filter(({wheelDelta})=>Math.abs(wheelDelta) > 3)
              .filterBy(enableStream.toProperty(() => true))
              .map(({wheelDelta})=>{
                  const currentHash  = this.getCurrentHash();
                  const id = currentHash ? getFirstHashWithInnerScroll(currentHash) : getFirstHashWithInnerScroll(this.props.defaultId);
                  const {innerScroller, node} = registered[id];
  
                  const treshold = 1;
  
                  const isAtTop = !innerScroller ? true : innerScroller.scrollTop < treshold;
                  const isAtBottom = !innerScroller ? true : innerScroller.scrollTop + node.offsetHeight > innerScroller.scrollHeight - treshold;
                  return {
                      isAtTop,
                      isAtBottom,
                      wheelDelta, 
                      direction: wheelDelta > 0 ? 1 : (wheelDelta < 0 ? -1 : 0)
                  }
              });
  
          this.setState({
              enableEmmiter,
              myScrollStream
          }, ()=>{
              this.setState({
                  scroller: this.props.getMainScroll(ReactDOM.findDOMNode(this))
              })
  
              this.state.myScrollStream.onValue(this.tryToRouteToSibling);
              // setTimeout(()=>this.transitionTo(this.context.location.pathname + this.context.location.hash), 100)
  
              this.setState({
                  scrollOnTransitionEmmiter,
                  scrollStream: this.state.myScrollStream.merge(scrollOnTransitionStream)
              })
          })
      }
  
      componentWillUnmount() {
          this.state.myScrollStream.offValue(this.tryToRouteToSibling);
      }
  
      setVisible({id, isVisible}){
          visibilityMap = visibilityMap.updateIn([...id.split('#'), 'value'], ()=>Immutable.fromJS({isVisible, id}));
  
          let arr = Immutable.fromJS([]);
          const hash = {};
          traverse(visibilityMap.toJS())
          .forEach(function (x) {
              if(this.isLeaf) {
                  return;
              }
  
              if(x.isVisible && !hash[x.id]) {
                  arr = arr.push(x)
                  hash[x.id] = true
              }
          });
  
          let finalArray = arr.map((item)=>item.id);
  
          if(!Immutable.is(this.state.arr, finalArray)){
              this.setState({arr: finalArray});    
          }
      }
  
      getChildContext() {
          return {
              scrollEmmiter: this.state.scrollOnTransitionEmmiter,
              scrollStream: this.state.scrollStream,
              register: this.register,
              unregister: this.unregister,
              scroller: this.state.scroller,
              arr: this.state.arr,
              setVisible: this.setVisible
          }
      }
  
      render(){   
          return <div className="SectionContainer">
              {this.props.debugPanel && this.props.debugPanel(this.state)}
              {this.props.children}
          </div>
      }
  }
  
  SectionContainer.contextTypes = {
      location: React.PropTypes.object,
      router: React.PropTypes.object
  }
  
  SectionContainer.childContextTypes = {
      setVisible: React.PropTypes.func,
      scrollStream: React.PropTypes.object,
      scrollEmmiter: React.PropTypes.object,
      arr: React.PropTypes.object,
      scroller: React.PropTypes.object,
      register: React.PropTypes.func,
      unregister: React.PropTypes.func,
  }