class Bmv2FlowRuleTranslatorImpl implements Bmv2FlowRuleTranslator {

    @Override
    public Bmv2TableEntry translate(FlowRule rule, Bmv2DeviceContext context)
            throws Bmv2FlowRuleTranslatorException {

        Bmv2Configuration configuration = context.configuration();
        Bmv2Interpreter interpreter = context.interpreter();

        int tableId = rule.tableId();
        String tableName = interpreter.tableIdMap().get(tableId);

        Bmv2TableModel table = (tableName == null) ? configuration.table(tableId) : configuration.table(tableName);

        if (table == null) {
            throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId);
        }

        /* Translate selector */
        Bmv2MatchKey bmv2MatchKey = buildMatchKey(interpreter, rule.selector(), table);

        /* Translate treatment */
        TrafficTreatment treatment = rule.treatment();
        Bmv2Action bmv2Action = null;
        // If treatment has only 1 instruction of type extension, use that
        for (Instruction inst : treatment.allInstructions()) {
            if (inst.type() == Instruction.Type.EXTENSION) {
                if (treatment.allInstructions().size() == 1) {
                    bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst);
                } else {
                    throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " +
                                                                      "instructions of which one is an extension: " +
                                                                      treatment.toString());
                }
            }
        }

        if (bmv2Action == null) {
            // No extension, use interpreter to build action.
            try {
                bmv2Action = interpreter.mapTreatment(treatment, configuration);
            } catch (Bmv2InterpreterException e) {
                throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment. " + e.toString());
            }
        }

        if (bmv2Action == null) {
            // Interpreter returned null.
            throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment");
        }

        // Check action
        Bmv2ActionModel actionModel = configuration.action(bmv2Action.name());
        if (actionModel == null) {
            throw new Bmv2FlowRuleTranslatorException("Unknown action " + bmv2Action.name());
        }
        if (!table.actions().contains(actionModel)) {
            throw new Bmv2FlowRuleTranslatorException("Action " + bmv2Action.name()
                                                              + " is not defined for table " + tableName);
        }
        if (actionModel.runtimeDatas().size() != bmv2Action.parameters().size()) {
            throw new Bmv2FlowRuleTranslatorException("Wrong number of parameters for action "
                                                              + actionModel.name() + ", expected "
                                                              + actionModel.runtimeDatas().size() + ", but found "
                                                              + bmv2Action.parameters().size());
        }
        for (int i = 0; i < actionModel.runtimeDatas().size(); i++) {
            Bmv2RuntimeDataModel data = actionModel.runtimeDatas().get(i);
            ImmutableByteSequence param = bmv2Action.parameters().get(i);
            if (param.size() != roundToBytes(data.bitWidth())) {
                throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for parameter " + data.name()
                                                                  + " of action " + actionModel.name()
                                                                  + ", expected " + roundToBytes(data.bitWidth())
                                                                  + " bytes, but found " + param.size());
            }
        }

        Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder();

        // In BMv2 0 is the highest priority.
        int newPriority = Integer.MAX_VALUE - rule.priority();

        tableEntryBuilder
                .withTableName(table.name())
                .withPriority(newPriority)
                .withMatchKey(bmv2MatchKey)
                .withAction(bmv2Action);

        if (!rule.isPermanent()) {
            if (table.hasTimeouts()) {
                tableEntryBuilder.withTimeout((double) rule.timeout());
            } else {
                log.warn("Flow rule is temporary but table {} doesn't support timeouts, translating to permanent",
                         table.name());
            }

        }

        return tableEntryBuilder.build();
    }

}