content: import React from 'react';
  import Tools from './tools.js';
  import Environment from '../environment/environment.js';
  
  import './router.less';
  
  export const Router = React.createClass({
  
    //@@viewOn:mixins
    //@@viewOff:mixins
  
    //@@viewOn:statics
    statics: {
      tagName: 'UU5.Common.Router'
    },
    //@@viewOff:statics
  
    //@@viewOn:propTypes
    propTypes: {
      basePath: React.PropTypes.string,
      route: React.PropTypes.oneOfType([React.PropTypes.string, // path
        React.PropTypes.element, React.PropTypes.shape({
          tag: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
          props: React.PropTypes.object,
          source: React.PropTypes.string
        })]),
      notFoundRoute: React.PropTypes.oneOfType([React.PropTypes.string, // path
        React.PropTypes.element, React.PropTypes.shape({
          tag: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
          props: React.PropTypes.object
        })]),
      routes: React.PropTypes.object
    },
    //@@viewOff:propTypes
  
    //@@viewOn:getDefaultProps
    getDefaultProps() {
      return {
        basePath: null,
        route: null,
        notFoundRoute: null,
        routes: null
      };
    },
    //@@viewOff:getDefaultProps
  
    //@@viewOn:standardComponentLifeCycle
    getInitialState() {
      Environment.router = this;
      this._history = [];
      this._routeIndex = null;
  
      return {
        route: null
      };
    },
  
    componentWillMount() {
      if (window.location.pathname && this.props.routes && typeof this.props.route === 'string') {
        let path = window.location.pathname;
        this.props.basePath && (path = path.replace(this.props.basePath, ''));
  
        let params = null;
        if (window.location.search) {
          params = Tools.decodeQuery(window.location.search);
        }
        this._setRoute(path, params);
      } else if (!this._shouldImport(this.props.route)) {
        this._setRoute(this.props.route);
      }
    },
  
    componentDidMount() {
      if (this._shouldImport(this.props.route)) {
        this._importRoute(this.props.route);
      }
  
      window.onpopstate = this._routeByNavigation;
    },
    //@@viewOff:standardComponentLifeCycle
  
    //@@viewOn:interface
    // newRoute = {tag: 'Node', props: {}, source: '', notFoundRoute: {} || < />} || <Node {...props} />
    setRoute(newRoute, params, setStateCallback) {
      // TODO: backward compatibility
      if (typeof params === 'function') {
        setStateCallback = params;
        params = null;
      }
  
      if (this._shouldImport(newRoute)) {
        this._importRoute(newRoute, params, setStateCallback);
      } else {
        this._setRoute(newRoute, params, setStateCallback);
      }
      return this;
    },
    //@@viewOff:interface
  
    //@@viewOn:overridingMethods
    //@@viewOff:overridingMethods
  
    //@@viewOn:componentSpecificHelpers
    _shouldImport(route) {
      return route && typeof route === 'object' && route.tag && !Tools.checkTag(route.tag, true);
    },
  
    _importRoute(route, params, setStateCallback) {
      if (window.System && window.System.import) {
        if (route.source) {
          System.import(route.source).then(exports => {
            let tagArray = route.tag.split('.');
            let calculatedTag = window;
            while (calculatedTag && tagArray.length > 1) {
              let moduleName = tagArray.shift();
              calculatedTag[moduleName] = calculatedTag[moduleName] || {};
              calculatedTag = calculatedTag[moduleName];
            }
            let tagName = tagArray.shift();
            let tagExport = exports[tagName];
            tagExport = tagExport || exports.default;
            calculatedTag[tagName] = tagExport;
            this._setRoute(route, params, setStateCallback);
          }, error => {
            Tools.error('Loading package "' + route.source + '" failed with error:', {
              error: error,
              tagName: this.constructor.tagName
            });
          });
        } else {
          Tools.error('Route was not found and has not set any source to import.', {
            route: route,
            tagName: this.constructor.tagName
          });
          this._setRoute(route.notFoundRoute);
        }
      } else {
        Tools.error('System is not defined in window! Cannot import source:', {
          source: route.source,
          tagName: this.constructor.tagName
        });
      }
      return this;
    },
  
    _setRoute(newRoute, params, setStateCallback) {
      this.setState({ route: this._buildRoute(newRoute, params) }, setStateCallback);
      return this;
    },
  
    _getRouteByPath(searchedPath) {
      return this.props.routes[searchedPath] || null;
    },
  
    _routeByNavigation(event) {
      if (event.state) {
        if (event.state.index < this._routeIndex) {
          // go back
          this._routeIndex = event.state.index;
          // console.log('goBack', this._history[this._routeIndex], event.state, this._routeIndex, this._history);
        } else {
          // go forward
          this._routeIndex = event.state.index;
          // console.log('goForward', this._history[this._routeIndex], event.state, this._routeIndex, this._history);
        }
        let newRoute = this._history[this._routeIndex];
        newRoute && this.setState({ route: this._buildRoute(newRoute.path, newRoute.params, true) });
      }
      // else {
      //   console.log('goInvalid', window.location.pathname, event.state);
      // }
      return this;
    },
  
    _getUrl(route, params) {
      let url = document.location.origin + (this.props.basePath || '') + route;
      params && (url += Tools.encodeQuery(params));
      return url;
    },
  
    _buildRoute(route, params, replace) {
      let newRouteChild = null;
  
      if (route) {
        let newRoute = route;
  
        if (typeof route === 'string' && this.props.routes) {
          let foundRoute = this._getRouteByPath(route);
          let method = 'replaceState';
  
          if (foundRoute) {
            if (!foundRoute.noHistory) {
              if (this._routeIndex === null) {
                this._routeIndex = 0;
                this._history.push({ path: route, params: params });
              } else if (!replace) {
                this._routeIndex++;
                this._history.splice(this._routeIndex, this._history.length - 1);
                method = 'pushState';
                this._history.push({ path: route, params: params });
              }
            }
  
            // console.log(method, route, this._routeIndex, this._history);
            history[method]({ path: route, index: this._routeIndex }, document.title, this._getUrl(route, params));
  
            newRoute = foundRoute.component;
          }
        }
  
        if (newRoute && typeof newRoute === 'object') {
          if (newRoute.tag) {
            let tag = Tools.checkTag(newRoute.tag);
            tag && (newRouteChild = React.createElement(tag, params ? Tools.merge({}, newRoute.props, {params: params}) : newRoute.props));
          } else {
            newRouteChild = newRoute;
          }
        }
      }
  
      if (newRouteChild && (!newRouteChild.type || newRouteChild.type.layoutType !== 'vuc')) {
        Tools.error('Route component which should be set is not Visual Use Case.', {
          routeParam: route,
          routeChild: newRouteChild,
          tagName: this.constructor.tagName
        });
      }
  
      return newRouteChild;
    },
  
    _buildChild() {
      let child;
  
      if (this.state.route) {
        child = this.state.route;
      } else if (this.props.notFoundRoute) {
        child = this._buildRoute(this.props.notFoundRoute);
      } else {
        child = <div />;
        Tools.error(this.constructor.tagName, 'Router has not any content.');
      }
  
      return child;
    },
    //@@viewOff:componentSpecificHelpers
  
    //@@viewOn:render
    render() {
      return this._buildChild();
    }
    //@@viewOff:render
  });
  
  export default Router;
  