WIP [VOL-2811] - Incorporate preliminary onu-adapter-go code into opencord repo

- reason "discovery-mibsync-complete" reached (via full MibUpload only, received data won't be stored yet)
- first review comments of patchset #4 considered
 (please have a look into our inline-comments in Gerrit to know more about the current state)
- no refactoring done yet

Change-Id: Iac47817f8ce4bd28dd8132f530b0570d57ae99b8
Signed-off-by: Holger Hildebrandt <holger.hildebrandt@adtran.com>
diff --git a/adaptercoreont/ont_device_state_transition.go b/adaptercoreont/ont_device_state_transition.go
new file mode 100644
index 0000000..e11f918
--- /dev/null
+++ b/adaptercoreont/ont_device_state_transition.go
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//Package adaptercoreont provides the utility for onu devices, flows and statistics
+package adaptercoreont
+
+import (
+	"context"
+	"reflect"
+	"runtime"
+
+	"github.com/opencord/voltha-lib-go/v3/pkg/log"
+)
+
+// DeviceState ONU Device state
+type DeviceState int
+
+//ONU device state machine may have to be adapted to specific ONU Adapter device needd ... !!!!!
+
+const (
+	// deviceStateNull OLT is not instantiated
+	deviceStateNull DeviceState = iota
+	// deviceStateInit OLT is instantiated
+	deviceStateInit
+	// deviceStateConnected Grpc session established with OLT
+	deviceStateConnected
+	// deviceStateUp Admin state of OLT is UP
+	deviceStateUp
+	// deviceStateDown Admin state of OLT is down
+	deviceStateDown
+)
+
+// Trigger for changing the state
+type Trigger int
+
+const (
+	// DeviceInit Go to Device init state
+	DeviceInit Trigger = iota
+	// GrpcConnected Go to connected state
+	GrpcConnected
+	// DeviceUpInd Go to Device up state
+	DeviceUpInd
+	// DeviceDownInd Go to Device down state
+	DeviceDownInd
+	// GrpcDisconnected Go to Device init state
+	GrpcDisconnected
+)
+
+// TransitionHandler function type for handling transition
+type TransitionHandler func(ctx context.Context) error
+
+// Transition to store state machine
+type Transition struct {
+	previousState []DeviceState
+	currentState  DeviceState
+	before        []TransitionHandler
+	after         []TransitionHandler
+}
+
+// TransitionMap to store all the states and current device state
+type TransitionMap struct {
+	transitions        map[Trigger]Transition
+	currentDeviceState DeviceState
+}
+
+//    OpenOnuDevice state machine:
+//
+//        null ----> init ------> connected -----> up -----> down
+//                   ^ ^             |             ^         | |
+//                   | |             |             |         | |
+//                   | +-------------+             +---------+ |
+//                   |                                         |
+//                   +-----------------------------------------+
+
+// NewTransitionMap create a new state machine with all the transitions
+func NewTransitionMap(dh *DeviceHandler) *TransitionMap {
+	var transitionMap TransitionMap
+	transitionMap.currentDeviceState = deviceStateNull
+	transitionMap.transitions = make(map[Trigger]Transition)
+	// In doInit create the Pon port, then set the device communication
+	transitionMap.transitions[DeviceInit] =
+		Transition{
+			previousState: []DeviceState{deviceStateNull, deviceStateDown},
+			currentState:  deviceStateInit,
+			before:        []TransitionHandler{dh.doStateInit},
+			after:         []TransitionHandler{dh.postInit}}
+	// not yet relevant?
+	transitionMap.transitions[GrpcDisconnected] =
+		Transition{
+			previousState: []DeviceState{deviceStateConnected, deviceStateDown},
+			currentState:  deviceStateInit,
+			before:        []TransitionHandler{dh.doStateInit},
+			after:         []TransitionHandler{dh.postInit}}
+	// in doConnected, create logical device and read the indications
+	transitionMap.transitions[GrpcConnected] =
+		Transition{
+			previousState: []DeviceState{deviceStateInit},
+			currentState:  deviceStateConnected,
+			before:        []TransitionHandler{dh.doStateConnected}}
+
+	// Once the olt UP is indication received, then do state up
+	transitionMap.transitions[DeviceUpInd] =
+		Transition{
+			previousState: []DeviceState{deviceStateConnected, deviceStateDown},
+			currentState:  deviceStateUp,
+			before:        []TransitionHandler{dh.doStateUp}}
+	// If olt DOWN indication comes then do sate down
+	transitionMap.transitions[DeviceDownInd] =
+		Transition{
+			previousState: []DeviceState{deviceStateUp},
+			currentState:  deviceStateDown,
+			before:        []TransitionHandler{dh.doStateDown}}
+
+	return &transitionMap
+}
+
+// funcName gets the handler function name
+func funcName(f interface{}) string {
+	p := reflect.ValueOf(f).Pointer()
+	rf := runtime.FuncForPC(p)
+	return rf.Name()
+}
+
+// isValidTransition checks for the new state transition is valid from current state
+func (tMap *TransitionMap) isValidTransition(trigger Trigger) bool {
+	// Validate the state transition
+	for _, state := range tMap.transitions[trigger].previousState {
+		if tMap.currentDeviceState == state {
+			return true
+		}
+	}
+	return false
+}
+
+// Handle moves the state machine to next state based on the trigger and invokes the before and
+// after handlers if the transition is a valid transition
+func (tMap *TransitionMap) Handle(ctx context.Context, trigger Trigger) {
+
+	// Check whether the transtion is valid from current state
+	if !tMap.isValidTransition(trigger) {
+		log.Errorw("Invalid transition triggered ", log.Fields{"CurrentState": tMap.currentDeviceState, "Trigger": trigger})
+		return
+	}
+
+	// Invoke the before handlers
+	beforeHandlers := tMap.transitions[trigger].before
+	if beforeHandlers == nil {
+		log.Debugw("No handlers for before", log.Fields{"trigger": trigger})
+	}
+	for _, handler := range beforeHandlers {
+		log.Debugw("running-before-handler", log.Fields{"handler": funcName(handler)})
+		if err := handler(ctx); err != nil {
+			// TODO handle error
+			log.Error(err)
+			return
+		}
+	}
+
+	// Update the state
+	tMap.currentDeviceState = tMap.transitions[trigger].currentState
+	log.Debugw("Updated device state ", log.Fields{"CurrentDeviceState": tMap.currentDeviceState})
+
+	// Invoke the after handlers
+	afterHandlers := tMap.transitions[trigger].after
+	if afterHandlers == nil {
+		log.Debugw("No handlers for after", log.Fields{"trigger": trigger})
+	}
+	for _, handler := range afterHandlers {
+		log.Debugw("running-after-handler", log.Fields{"handler": funcName(handler)})
+		if err := handler(ctx); err != nil {
+			// TODO handle error
+			log.Error(err)
+			return
+		}
+	}
+}