text: import debug from 'debug';
  import reqwest from 'reqwest';
  
  import { clearControlError, closeWebsocket, openWebsocket, receiveError,
    receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
    receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
    receiveControlSuccess, receiveTopologies, receiveNotFound,
    receiveNodesForTopology } from '../actions/app-actions';
  
  import { API_INTERVAL, TOPOLOGY_INTERVAL } from '../constants/timer';
  
  const log = debug('scope:web-api-utils');
  
  const reconnectTimerInterval = 5000;
  const updateFrequency = '5s';
  
  let socket;
  let reconnectTimer = 0;
  let currentUrl = null;
  let currentOptions = null;
  let topologyTimer = 0;
  let apiDetailsTimer = 0;
  let controlErrorTimer = 0;
  
  function buildOptionsQuery(options) {
    if (options) {
      return options.reduce((query, value, param) => `${query}&${param}=${value}`, '');
    }
    return '';
  }
  
  export function basePath(urlPath) {
    //
    // "/scope/terminal.html" -> "/scope"
    // "/scope/" -> "/scope"
    // "/scope" -> "/scope"
    // "/" -> ""
    //
    const parts = urlPath.split('/');
    // if the last item has a "." in it, e.g. foo.html...
    if (parts[parts.length - 1].indexOf('.') !== -1) {
      return parts.slice(0, -1).join('/');
    }
    return parts.join('/').replace(/\/$/, '');
  }
  
  export function basePathSlash(urlPath) {
    //
    // "/scope/terminal.html" -> "/scope/"
    // "/scope/" -> "/scope/"
    // "/scope" -> "/scope/"
    // "/" -> "/"
    //
    return `${basePath(urlPath)}/`;
  }
  
  const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
  const wsUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`;
  
  function createWebsocket(topologyUrl, optionsQuery, dispatch) {
    if (socket) {
      socket.onclose = null;
      socket.onerror = null;
      socket.close();
      // onclose() is not called, but that's fine since we're opening a new one
      // right away
    }
  
    socket = new WebSocket(`${wsUrl}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`);
  
    socket.onopen = () => {
      dispatch(openWebsocket());
    };
  
    socket.onclose = () => {
      clearTimeout(reconnectTimer);
      log(`Closing websocket to ${topologyUrl}`, socket.readyState);
      socket = null;
      dispatch(closeWebsocket());
  
      reconnectTimer = setTimeout(() => {
        createWebsocket(topologyUrl, optionsQuery, dispatch);
      }, reconnectTimerInterval);
    };
  
    socket.onerror = () => {
      log(`Error in websocket to ${topologyUrl}`);
      dispatch(receiveError(currentUrl));
    };
  
    socket.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      dispatch(receiveNodesDelta(msg));
    };
  }
  
  /* keep URLs relative */
  
  /**
   * Gets nodes for all topologies (for search)
   */
  export function getAllNodes(getState, dispatch) {
    const state = getState();
    const topologyOptions = state.get('topologyOptions');
    // fetch sequentially
    state.get('topologyUrlsById')
      .reduce((sequence, topologyUrl, topologyId) => sequence.then(() => {
        const optionsQuery = buildOptionsQuery(topologyOptions.get(topologyId));
        return fetch(`${topologyUrl}?${optionsQuery}`);
      })
      .then(response => response.json())
      .then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
      Promise.resolve());
  }
  
  export function getTopologies(options, dispatch) {
    clearTimeout(topologyTimer);
    const optionsQuery = buildOptionsQuery(options);
    const url = `api/topology?${optionsQuery}`;
    reqwest({
      url,
      success: (res) => {
        dispatch(receiveTopologies(res));
        topologyTimer = setTimeout(() => {
          getTopologies(options, dispatch);
        }, TOPOLOGY_INTERVAL);
      },
      error: (err) => {
        log(`Error in topology request: ${err.responseText}`);
        dispatch(receiveError(url));
        topologyTimer = setTimeout(() => {
          getTopologies(options, dispatch);
        }, TOPOLOGY_INTERVAL);
      }
    });
  }
  
  export function getNodesDelta(topologyUrl, options, dispatch) {
    const optionsQuery = buildOptionsQuery(options);
  
    // only recreate websocket if url changed
    if (topologyUrl && (topologyUrl !== currentUrl || currentOptions !== optionsQuery)) {
      createWebsocket(topologyUrl, optionsQuery, dispatch);
      currentUrl = topologyUrl;
      currentOptions = optionsQuery;
    }
  }
  
  export function getNodeDetails(topologyUrlsById, nodeMap, dispatch) {
    // get details for all opened nodes
    const obj = nodeMap.last();
    if (obj && topologyUrlsById.has(obj.topologyId)) {
      const topologyUrl = topologyUrlsById.get(obj.topologyId);
      const url = [topologyUrl, '/', encodeURIComponent(obj.id)]
        .join('').substr(1);
      reqwest({
        url,
        success: (res) => {
          // make sure node is still selected
          if (nodeMap.has(res.node.id)) {
            dispatch(receiveNodeDetails(res.node));
          }
        },
        error: (err) => {
          log(`Error in node details request: ${err.responseText}`);
          // dont treat missing node as error
          if (err.status === 404) {
            dispatch(receiveNotFound(obj.id));
          } else {
            dispatch(receiveError(topologyUrl));
          }
        }
      });
    } else if (obj) {
      log('No details or url found for ', obj);
    }
  }
  
  export function getApiDetails(dispatch) {
    clearTimeout(apiDetailsTimer);
    const url = 'api';
    reqwest({
      url,
      success: (res) => {
        dispatch(receiveApiDetails(res));
        apiDetailsTimer = setTimeout(() => {
          getApiDetails(dispatch);
        }, API_INTERVAL);
      },
      error: (err) => {
        log(`Error in api details request: ${err.responseText}`);
        receiveError(url);
        apiDetailsTimer = setTimeout(() => {
          getApiDetails(dispatch);
        }, API_INTERVAL / 2);
      }
    });
  }
  
  export function doControlRequest(nodeId, control, dispatch) {
    clearTimeout(controlErrorTimer);
    const url = `api/control/${encodeURIComponent(control.probeId)}/`
      + `${encodeURIComponent(control.nodeId)}/${control.id}`;
    reqwest({
      method: 'POST',
      url,
      success: (res) => {
        dispatch(receiveControlSuccess(nodeId));
        if (res) {
          if (res.pipe) {
            dispatch(receiveControlPipe(res.pipe, nodeId, res.raw_tty, true));
          }
          if (res.removedNode) {
            dispatch(receiveControlNodeRemoved(nodeId));
          }
        }
      },
      error: (err) => {
        dispatch(receiveControlError(nodeId, err.response));
        controlErrorTimer = setTimeout(() => {
          dispatch(clearControlError(nodeId));
        }, 10000);
      }
    });
  }
  
  export function deletePipe(pipeId, dispatch) {
    const url = `api/pipe/${encodeURIComponent(pipeId)}`;
    reqwest({
      method: 'DELETE',
      url,
      success: () => {
        log('Closed the pipe!');
      },
      error: (err) => {
        log(`Error closing pipe:${err}`);
        dispatch(receiveError(url));
      }
    });
  }
  
  export function getPipeStatus(pipeId, dispatch) {
    const url = `api/pipe/${encodeURIComponent(pipeId)}/check`;
    reqwest({
      method: 'GET',
      url,
      complete: (res) => {
        const status = {
          204: 'PIPE_ALIVE',
          404: 'PIPE_DELETED'
        }[res.status];
  
        if (!status) {
          log('Unexpected pipe status:', res.status);
          return;
        }
  
        dispatch(receiveControlPipeStatus(pipeId, status));
      }
    });
  }
  