contract ServiceController is MultiSigAdapter {

    event Error(uint _errorCode);

    uint constant SERVICE_CONTROLLER = 350000;
    uint constant SERVICE_CONTROLLER_EMISSION_EXIST = SERVICE_CONTROLLER + 1;
    uint constant SERVICE_CONTROLLER_BURNING_MAN_EXIST = SERVICE_CONTROLLER + 2;
    uint constant SERVICE_CONTROLLER_ALREADY_INITIALIZED = SERVICE_CONTROLLER + 3;
    uint constant SERVICE_CONTROLLER_SERVICE_EXIST = SERVICE_CONTROLLER + 4;

    address public profiterole;
    address public treasury;
    address public pendingManager;
    address public proxy;

    uint public sideServicesCount;
    mapping(uint => address) public index2sideService;
    mapping(address => uint) public sideService2index;
    mapping(address => bool) public sideServices;

    uint public emissionProvidersCount;
    mapping(uint => address) public index2emissionProvider;
    mapping(address => uint) public emissionProvider2index;
    mapping(address => bool) public emissionProviders;

    uint public burningMansCount;
    mapping(uint => address) public index2burningMan;
    mapping(address => uint) public burningMan2index;
    mapping(address => bool) public burningMans;

    /// @notice Default ServiceController's constructor
    ///
    /// @param _pendingManager pending manager address
    /// @param _proxy ERC20 proxy address
    /// @param _profiterole profiterole address
    /// @param _treasury treasury address
    function ServiceController(address _pendingManager, address _proxy, address _profiterole, address _treasury) public {
        require(_pendingManager != 0x0);
        require(_proxy != 0x0);
        require(_profiterole != 0x0);
        require(_treasury != 0x0);
        pendingManager = _pendingManager;
        proxy = _proxy;
        profiterole = _profiterole;
        treasury = _treasury;
    }

    /// @notice Return pending manager address
    ///
    /// @return code
    function getPendingManager() public view returns (address) {
        return pendingManager;
    }

    /// @notice Add emission provider
    ///
    /// @param _provider emission provider address
    ///
    /// @return code
    function addEmissionProvider(address _provider, uint _block) public returns (uint _code) {
        if (emissionProviders[_provider]) {
            return _emitError(SERVICE_CONTROLLER_EMISSION_EXIST);
        }
        _code = _multisig(keccak256(_provider), _block);
        if (OK != _code) {
            return _code;
        }

        emissionProviders[_provider] = true;
        uint _count = emissionProvidersCount + 1;
        index2emissionProvider[_count] = _provider;
        emissionProvider2index[_provider] = _count;
        emissionProvidersCount = _count;

        return OK;
    }

    /// @notice Remove emission provider
    ///
    /// @param _provider emission provider address
    ///
    /// @return code
    function removeEmissionProvider(address _provider, uint _block) public returns (uint _code) {
        _code = _multisig(keccak256(_provider), _block);
        if (OK != _code) {
            return _code;
        }

        uint _idx = emissionProvider2index[_provider];
        uint _lastIdx = emissionProvidersCount;
        if (_idx != 0) {
            if (_idx != _lastIdx) {
                address _lastEmissionProvider = index2emissionProvider[_lastIdx];
                index2emissionProvider[_idx] = _lastEmissionProvider;
                emissionProvider2index[_lastEmissionProvider] = _idx;
            }

            delete emissionProvider2index[_provider];
            delete index2emissionProvider[_lastIdx];
            delete emissionProviders[_provider];
            emissionProvidersCount = _lastIdx - 1;
        }

        return OK;
    }

    /// @notice Add burning man
    ///
    /// @param _burningMan burning man address
    ///
    /// @return code
    function addBurningMan(address _burningMan, uint _block) public returns (uint _code) {
        if (burningMans[_burningMan]) {
            return _emitError(SERVICE_CONTROLLER_BURNING_MAN_EXIST);
        }

        _code = _multisig(keccak256(_burningMan), _block);
        if (OK != _code) {
            return _code;
        }

        burningMans[_burningMan] = true;
        uint _count = burningMansCount + 1;
        index2burningMan[_count] = _burningMan;
        burningMan2index[_burningMan] = _count;
        burningMansCount = _count;

        return OK;
    }

    /// @notice Remove burning man
    ///
    /// @param _burningMan burning man address
    ///
    /// @return code
    function removeBurningMan(address _burningMan, uint _block) public returns (uint _code) {
        _code = _multisig(keccak256(_burningMan), _block);
        if (OK != _code) {
            return _code;
        }

        uint _idx = burningMan2index[_burningMan];
        uint _lastIdx = burningMansCount;
        if (_idx != 0) {
            if (_idx != _lastIdx) {
                address _lastBurningMan = index2burningMan[_lastIdx];
                index2burningMan[_idx] = _lastBurningMan;
                burningMan2index[_lastBurningMan] = _idx;
            }
            
            delete burningMan2index[_burningMan];
            delete index2burningMan[_lastIdx];
            delete burningMans[_burningMan];
            burningMansCount = _lastIdx - 1;
        }

        return OK;
    }

    /// @notice Update a profiterole address
    ///
    /// @param _profiterole profiterole address
    ///
    /// @return result code of an operation
    function updateProfiterole(address _profiterole, uint _block) public returns (uint _code) {
        _code = _multisig(keccak256(_profiterole), _block);
        if (OK != _code) {
            return _code;
        }

        profiterole = _profiterole;
        return OK;
    }

    /// @notice Update a treasury address
    ///
    /// @param _treasury treasury address
    ///
    /// @return result code of an operation
    function updateTreasury(address _treasury, uint _block) public returns (uint _code) {
        _code = _multisig(keccak256(_treasury), _block);
        if (OK != _code) {
            return _code;
        }

        treasury = _treasury;
        return OK;
    }

    /// @notice Update pending manager address
    ///
    /// @param _pendingManager pending manager address
    ///
    /// @return result code of an operation
    function updatePendingManager(address _pendingManager, uint _block) public returns (uint _code) {
        _code = _multisig(keccak256(_pendingManager), _block);
        if (OK != _code) {
            return _code;
        }

        pendingManager = _pendingManager;
        return OK;
    }

    function addSideService(address _service, uint _block) public returns (uint _code) {
        if (sideServices[_service]) {
            return SERVICE_CONTROLLER_SERVICE_EXIST;
        }
        _code = _multisig(keccak256(_service), _block);
        if (OK != _code) {
            return _code;
        }

        sideServices[_service] = true;
        uint _count = sideServicesCount + 1;
        index2sideService[_count] = _service;
        sideService2index[_service] = _count;
        sideServicesCount = _count;

        return OK;
    }

    function removeSideService(address _service, uint _block) public returns (uint _code) {
        _code = _multisig(keccak256(_service), _block);
        if (OK != _code) {
            return _code;
        }

        uint _idx = sideService2index[_service];
        uint _lastIdx = sideServicesCount;
        if (_idx != 0) {
            if (_idx != _lastIdx) {
                address _lastSideService = index2sideService[_lastIdx];
                index2sideService[_idx] = _lastSideService;
                sideService2index[_lastSideService] = _idx;
            }
            
            delete sideService2index[_service];
            delete index2sideService[_lastIdx];
            delete sideServices[_service];
            sideServicesCount = _lastIdx - 1;
        }

        return OK;
    }

    function getEmissionProviders()
    public
    view
    returns (address[] _emissionProviders)
    {
        _emissionProviders = new address[](emissionProvidersCount);
        for (uint _idx = 0; _idx < _emissionProviders.length; ++_idx) {
            _emissionProviders[_idx] = index2emissionProvider[_idx + 1];
        }
    }

    function getBurningMans()
    public
    view
    returns (address[] _burningMans)
    {
        _burningMans = new address[](burningMansCount);
        for (uint _idx = 0; _idx < _burningMans.length; ++_idx) {
            _burningMans[_idx] = index2burningMan[_idx + 1];
        }
    }

    function getSideServices()
    public
    view
    returns (address[] _sideServices)
    {
        _sideServices = new address[](sideServicesCount);
        for (uint _idx = 0; _idx < _sideServices.length; ++_idx) {
            _sideServices[_idx] = index2sideService[_idx + 1];
        }
    }

    /// @notice Check target address is service
    ///
    /// @param _address target address
    ///
    /// @return `true` when an address is a service, `false` otherwise
    function isService(address _address) public view returns (bool check) {
        return _address == profiterole ||
            _address == treasury || 
            _address == proxy || 
            _address == pendingManager || 
            emissionProviders[_address] || 
            burningMans[_address] ||
            sideServices[_address];
    }

    function _emitError(uint _errorCode) internal returns (uint) {
        Error(_errorCode);
        return _errorCode;
    }
}
/// @title ServiceController
///
/// Base implementation
/// Serves for managing service instances
function updatePendingManager(address _pendingManager, uint _block) public returns (uint _code) {
    _code = _multisig(keccak256(_pendingManager), _block);
    if (OK != _code) {
        return _code;
    }

    pendingManager = _pendingManager;
    return OK;
}

/// @notice Update pending manager address
///
/// @param _pendingManager pending manager address
///
/// @return result code of an operation