import { stringify, parse } from 'jsan';
import socketCluster from 'socketcluster-client';
import configureStore from './configureStore';
import { socketOptions } from './constants';

let instanceName;
let socket;
let channel;
let store = {};
let shouldInit = true;
let lastAction;
let filters = {};

function isMonitored(actionType) {
  return (
    !actionType ||
    (!filters.whitelist && !filters.blacklist) ||
    (filters.whitelist && actionType.match(filters.whitelist.join('|'))) ||
    (filters.blacklist && !actionType.match(filters.blacklist.join('|')))
  );
}


function relay(type, state, action, nextActionId) {
  if (!action || (action && action.action && isMonitored(action.action.type))) {
    setTimeout(() => {
      const message = {
        payload: state ? stringify(state) : '',
        action: action ? stringify(action) : '',
        nextActionId: nextActionId || '',
        type,
        id: socket.id,
        name: instanceName,
        init: shouldInit
      };
      if (shouldInit) shouldInit = false;
      socket.emit(socket.id ? 'log' : 'log-noid', message);
    }, 0);
  }
}


function handleMessages(message) {
  if (message.type === 'DISPATCH') {
    store.liftedStore.dispatch(message.action);
  } else if (message.type === 'UPDATE') {
    relay('STATE', store.liftedStore.getState());
  } else if (message.type === 'SYNC') {
    if (socket.id && message.id !== socket.id) {
      store.liftedStore.dispatch({
        type: 'IMPORT_STATE', nextLiftedState: parse(message.state)
      });
    }
  }
}

function init(options) {
  if (channel) channel.unwatch();
  if (socket) socket.disconnect();
  if (options && options.port && !options.hostname) {
    options.hostname = 'localhost';
  }
  socket = socketCluster.connect(options && options.port ? options : socketOptions);

  socket.on('error', function (err) {
    console.warn(err);
  });

  socket.emit('login', 'master', (err, channelName) => {
    if (err) { console.error(err); return; }
    channel = socket.subscribe(channelName);
    channel.watch(handleMessages);
    socket.on(channelName, handleMessages);
  });
  if (options && options.filters) {
    filters = options.filters;
  }
  if (options) instanceName = options.name;
  relay('STATE', store.liftedStore.getState());
}

function monitorReducer(state = {}, action) {
  lastAction = action.type;
  return state;
}

function filterStagedActions(state) {
  if (!filters.whitelist && !filters.blacklist) return state;

  const filteredStagedActionIds = [];
  const filteredComputedStates = [];

  state.stagedActionIds.forEach((id, idx) => {
    if (isMonitored(state.actionsById[id].action.type)) {
      filteredStagedActionIds.push(id);
      filteredComputedStates.push(state.computedStates[idx]);
    }
  });

  return Object.assign({}, state, {
    stagedActionIds: filteredStagedActionIds,
    computedStates: filteredComputedStates
  });
}

function handleChange(state, liftedState) {
  const nextActionId = liftedState.nextActionId;
  const liftedAction = liftedState.actionsById[nextActionId - 1];
  const action = liftedAction.action;
  if (action.type === '@@INIT') {
    relay('INIT', state, { timestamp: Date.now() });
  } else if (lastAction !== 'TOGGLE_ACTION' && lastAction !== 'SWEEP') {
    if (lastAction === 'JUMP_TO_STATE') return;
    relay('ACTION', state, liftedAction, nextActionId);
  } else {
    relay('STATE', filterStagedActions(liftedState));
  }
}

export default function devTools(options) {
  return (next) => {
    return (reducer, initialState) => {
      store = configureStore(next, monitorReducer)(reducer, initialState);
      init(options);
      store.subscribe(() => { handleChange(store.getState(), store.liftedStore.getState()); });
      return store;
    };
  };
}

@@ -286,21 +286,16 @@
  filters
- = %7B%7D
 ;%0A%0Afunct
@@ -300,22 +300,21 @@
 ction is
-Monito
+Filte
 red(acti
@@ -319,101 +319,97 @@
 tion
-Type
 ) %7B%0A  
-return (%0A    !actionType %7C%7C%0A    (!filters.whitelist && !filters.blacklist) %7C%7C
+if (!action %7C%7C !action.action %7C%7C !action.action.type) return false;%0A  return (
 %0A    
-(
 filt
@@ -425,23 +425,32 @@
 list && 
+!
 action
-T
+.action.t
 ype.matc
@@ -483,17 +483,16 @@
 %7C'))
-)
  %7C%7C%0A    
 (fil
@@ -487,17 +487,16 @@
  %7C%7C%0A    
-(
 filters.
@@ -512,16 +512,23 @@
  && 
-!
+action.
 action
-T
+.t
 ype.
@@ -561,17 +561,16 @@
 in('%7C'))
-)
 %0A  );%0A%7D%0A
@@ -633,87 +633,49 @@
 if (
-!action %7C%7C (action && action.action && isMonitored(action.action.type))) %7B%0A 
+filters && isFiltered(action)) return;%0A
   
- 
 setT
@@ -693,18 +693,16 @@
 %3E %7B%0A    
-  
 const me
@@ -717,18 +717,16 @@
 %7B%0A      
- 
- 
 payload:
@@ -753,26 +753,24 @@
 tate) : '',%0A
-  
       action
@@ -810,18 +810,16 @@
 ,%0A      
- 
- 
 nextActi
@@ -854,18 +854,14 @@
     
-  
 type,%0A
-  
     
@@ -877,18 +877,16 @@
 ket.id,%0A
-  
       na
@@ -905,26 +905,24 @@
 Name,%0A      
-  
 init: should
@@ -930,23 +930,19 @@
 nit%0A    
-  
 %7D;%0A
-  
     if (
@@ -977,18 +977,16 @@
 se;%0A    
- 
- 
 socket.e
@@ -1031,18 +1031,16 @@
 ssage);%0A
-  
   %7D, 0);
@@ -1040,20 +1040,16 @@
  %7D, 0);%0A
-  %7D%0A
 %7D%0A%0A%0Afunc
@@ -2360,40 +2360,8 @@
 ters
-.whitelist && !filters.blacklist
 ) re
@@ -2508,16 +2508,16 @@
 if (
+!
 is
-Monito
+Filte
 red(
@@ -2537,28 +2537,16 @@
 ById%5Bid%5D
-.action.type
 )) %7B%0A   

Fix filtering monitor actions