func main() {

	// parse inputs
	if _, err := parser.Parse(); err != nil {
		log.Fatalf("failed to parse options: %v", err)
	}

	// Scenario input format
	// we want scenario with different speed
	// we want scenario with positive and negative steering changes
	// we want different values of those changes
	// we want different initial steering (do we?)
	// we want a scenario where we just go straight to record the noise
	// ---> all that can be defined with the step height and the duration

	log.Info("Starting -- version %s", Version)

	log.Info("Opts: %#v", opts)

	panicChan := make(chan interface{})
	defer func() {
		if r := recover(); r != nil {
			panicChan <- r
		}
	}()

	stepperChan := make(chan interface{})

	// The motor
	motor := motor.New(
		conf.MotorStepPin,
		conf.MotorStepPwm,
		conf.MotorDirPin,
		conf.MotorSleepPin)
	defer motor.Unexport()

	// the input button
	switchGpio := func(pin byte) gpio.Gpio {

		// kill the process (via log.Panic) in case we can't create the GPIO
		err := gpio.EnableGPIO(pin)
		if err != nil {
			log.Panic(err)
		}

		var g = gpio.New(pin)
		if !g.IsExported() {
			err = g.Export()
			if err != nil {
				log.Panic(err)
			}
		}

		err = g.SetDirection(gpio.InDirection)
		if err != nil {
			log.Panic(err)
		}

		err = g.SetActiveLevel(gpio.ActiveHigh)
		if err != nil {
			log.Panic(err)
		}

		// Test we can read it and make we we don't go beyond this point until the switch is OFF
		// to prevent the autopilot to be re-enabled when the system restart.
		// Since the alarm has not been initialized yet, after a reboot it will be ON.
		for value, err := g.Value(); value; value, err = g.Value() {
			if err != nil {
				log.Panic(err)
			}

			log.Info("[AUTOTEST] current autopilot switch position is ON - switch it OFF to proceed.")

			time.Sleep(200 * time.Millisecond)
		}

		return g
	}(conf.SwitchGpioPin)
	defer switchGpio.Unexport()

	////////////////////////////////////////
	// an astonishing steering
	////////////////////////////////////////
	steering := steering.New(motor)
	steeringChan := make(chan interface{})
	steering.SetInputChan(steeringChan)
	steering.SetPanicChan(panicChan)

	////////////////////////////////////////
	// a fake tracer
	////////////////////////////////////////
	tracerChan := make(chan interface{})
	go func() {
		for {
			<-tracerChan
		}
	}()

	////////////////////////////////////////
	// a wonderful gps
	////////////////////////////////////////
	gps := gps.New(conf.Conf.GpsSerialPort)
	gps.SetMessagesChan(stepperChan)
	gps.SetErrorChan(stepperChan)
	gps.SetPanicChan(panicChan)
	gps.SetTracerChan(tracerChan)

	////////////////////////////////////////
	// a crazy stepper
	////////////////////////////////////////
	stepper := stepper.New()
	stepper.SetInputChan(stepperChan)
	stepper.SetPanicChan(panicChan)
	stepper.SetSteeringChan(steeringChan)

	////////////////////////////////////////
	// a surprising input
	////////////////////////////////////////
	control := control.New(switchGpio, stepper)
	control.SetPanicChan(panicChan)

	// tell the pilot what we are going to do
	log.Notice(`When the autopilot button is switched to ON, we are going to acquire the 
current heading and start a steering step of %f degree and hold it for %v. As a pilot, 
you'll have to make sure there is enough place for that and the vessel is safe. 
Changing the steering during the test will make it invalide. 
When the test is over your are free to resume normal operations.`, opts.Step, opts.Duration)

	gps.Start()
	control.Start()
	steering.Start()
	stepper.Start()
	defer steering.Shutdown()
	defer stepper.Shutdown()
	defer control.Shutdown()

	go func() {
		select {
		case m := <-panicChan:

			// make sure the motor is stopped
			err := motor.Disable()
			if err != nil {
				log.Error("Failed to disable the motor - exiting anyway", err)
			}

			log.Fatalf("Version %v -- Received a panic error -- exiting: %v", Version, m)
		}
	}()

	go func() {
		defer func() {
			if r := recover(); r != nil {
				panicChan <- r
			}
		}()

		http.HandleFunc("/", webserver.VersionEndpoint(Version))
		http.HandleFunc("/calibration", stepper.CalibrationEndpoint)
		err := http.ListenAndServe(":8000", nil)
		if err != nil {
			log.Panic("Already running! or something is living on port 8000 - exiting")
		}
	}()

	// populate the scenario
	stepper.NewStep(opts.Step, time.Duration(opts.Duration)*time.Second, opts.Description)

	// FUTURE(ssoudan) We can imagine to generate the input from matlab? :)

	// FUTURE(ssoudan) support periodic response testing

	// Wait until we receive a signal
	utils.WaitForInterrupt(func() {
		log.Info("Interrupted - exiting")
		log.Info("Exiting -- version %v", Version)
	})
}
func main() {
	log.Info("Starting -- version %s", Version)

	panicChan := make(chan interface{})
	defer func() {
		if r := recover(); r != nil {
			panicChan <- r
		}
	}()

	go func() {
		select {
		case m := <-panicChan:

			// kill the process (via log.Fatal) in case we can't create the PWM
			if pwm, err := pwm.New(conf.AlarmGpioPWM, conf.AlarmGpioPin); err == nil {
				if !pwm.IsExported() {
					err = pwm.Export()
					if err != nil {
						log.Error("Failed to raise the alarm")
					}
				}

				pwm.Enable()
			} else {
				log.Error("Failed to raise the alarm")
			}
			// The motor
			motor := motor.New(
				conf.MotorStepPin,
				conf.MotorStepPwm,
				conf.MotorDirPin,
				conf.MotorSleepPin)
			if err := motor.Disable(); err != nil {
				log.Error("Failed to stop the motor")
			}
			motor.Unexport()

			log.Fatalf("Version %v -- Received a panic error -- exiting: %v", Version, m)
		}
	}()

	ws := webserver.New(Version)
	ws.SetPanicChan(panicChan)
	ws.Start()

	////////////////////////////////////////
	// Init the IO
	////////////////////////////////////////
	// the LEDs
	mapMessageToGPIO := func(message string, pin byte) gpio.Gpio {

		// kill the process (via log.Panic -> recover -> panicChan -> go routine -> log.Fatal) in case we can't create the GPIO
		err := gpio.EnableGPIO(pin)
		if err != nil {
			log.Panic(err)
		}

		var g = gpio.New(pin)
		if !g.IsExported() {
			err = g.Export()
			if err != nil {
				log.Panic(err)
			}
		}

		err = g.SetDirection(gpio.OutDirection)
		if err != nil {
			log.Panic(err)
		}

		// Test Disabled and Enabled state for each LEDs
		err = g.Disable()
		if err != nil {
			log.Panic(err)
		}

		log.Info("[AUTOTEST] %s LED is ON", message)
		time.Sleep(1 * time.Second)

		err = g.Enable()
		if err != nil {
			log.Panic(err)
		}

		log.Info("[AUTOTEST] %s LED is OFF", message)
		time.Sleep(1 * time.Second)

		err = g.Disable()
		if err != nil {
			log.Panic(err)
		}

		return g
	}
	dashboardGPIOs := make(map[string]gpio.Gpio, len(conf.MessageToPin))
	for _, v := range conf.MessageToPin {
		g := mapMessageToGPIO(v.Message, v.Pin)
		dashboardGPIOs[v.Message] = g
	}
	defer func() {
		for _, g := range dashboardGPIOs {
			g.Disable()
			g.Unexport()
		}
	}()

	// the input button
	switchGpio := func(pin byte) gpio.Gpio {

		// kill the process (via log.Panic) in case we can't create the GPIO
		err := gpio.EnableGPIO(pin)
		if err != nil {
			log.Panic(err)
		}

		var g = gpio.New(pin)
		if !g.IsExported() {
			err = g.Export()
			if err != nil {
				log.Panic(err)
			}
		}

		err = g.SetDirection(gpio.InDirection)
		if err != nil {
			log.Panic(err)
		}

		err = g.SetActiveLevel(gpio.ActiveHigh)
		if err != nil {
			log.Panic(err)
		}

		// Test we can read it and make we we don't go beyond this point until the switch is OFF
		// to prevent the autopilot to be re-enabled when the system restart.
		// Since the alarm has not been initialized yet, after a reboot it will be ON.
		for value, err := g.Value(); value; value, err = g.Value() {
			if err != nil {
				log.Panic(err)
			}

			log.Info("[AUTOTEST] current autopilot switch position is ON - switch it OFF to proceed.")

			time.Sleep(200 * time.Millisecond)
		}

		return g
	}(conf.SwitchGpioPin)
	defer switchGpio.Unexport()

	// The motor
	motor := motor.New(
		conf.MotorStepPin,
		conf.MotorStepPwm,
		conf.MotorDirPin,
		conf.MotorSleepPin)
	defer motor.Disable()
	defer motor.Unexport()

	// The alarm
	alarmPwm := func(pin byte, pwmId byte) *pwm.Pwm {

		// kill the process (via log.Panic) in case we can't create the PWM
		pwm, err := pwm.New(pwmId, pin)
		if err != nil {
			log.Panic(err)
		}

		if !pwm.IsExported() {
			err = pwm.Export()
			if err != nil {
				log.Panic(err)
			}
		}

		pwm.Disable()

		if err = pwm.SetPeriodAndDutyCycle(200*time.Millisecond, 0.5); err != nil {
			log.Panic(err)
		}

		if err = pwm.Enable(); err != nil {
			log.Panic(err)
		}
		log.Info("[AUTOTEST] alarm is ON")

		time.Sleep(2 * time.Second)
		if err = pwm.Disable(); err != nil {
			log.Panic(err)
		}
		log.Info("[AUTOTEST] alarm is OFF")

		return pwm
	}(conf.AlarmGpioPin, conf.AlarmGpioPWM)
	defer alarmPwm.Unexport()

	// The compass sincos output interface
	compass := sincos.New(conf.I2CBus, conf.SinAddress, conf.CosAddress)

	////////////////////////////////////////
	// a nice and delicate alarm
	////////////////////////////////////////

	alarm := alarm.New(alarmPwm)
	alarmChan := make(chan interface{})
	alarm.SetInputChan(alarmChan)
	alarm.SetPanicChan(panicChan)

	////////////////////////////////////////
	// a beautiful dashboard
	////////////////////////////////////////
	dashboard := dashboard.New()
	dashboardChan := make(chan interface{})
	dashboard.SetInputChan(dashboardChan)
	dashboard.SetPanicChan(panicChan)
	for m, g := range dashboardGPIOs {
		dashboard.RegisterMessageHandler(m, g)
	}
	ws.SetDashboard(dashboard)

	////////////////////////////////////////
	// an astonishing steering
	////////////////////////////////////////
	steering := steering.New(motor)
	steeringChan := make(chan interface{})
	steering.SetInputChan(steeringChan)
	steering.SetPanicChan(panicChan)

	////////////////////////////////////////
	// a stunning tracer
	////////////////////////////////////////
	tracer := tracer.New(conf.Conf.TraceSize)
	tracerChan := make(chan interface{})
	tracer.SetInputChan(tracerChan)
	tracer.SetPanicChan(panicChan)
	ws.SetTracer(tracer)

	////////////////////////////////////////
	// an amazing PID
	////////////////////////////////////////
	pidController := pid.New(
		conf.Conf.P,
		conf.Conf.I,
		conf.Conf.D,
		conf.Conf.N,
		conf.Conf.MinPIDOutputLimits,
		conf.Conf.MaxPIDOutputLimits)

	////////////////////////////////////////
	// a great pilot
	////////////////////////////////////////
	thePilot := pilot.New(pidController, conf.Conf.Bounds)
	pilotChan := make(chan interface{})
	thePilot.SetInputChan(pilotChan)
	thePilot.SetDashboardChan(dashboardChan)
	thePilot.SetAlarmChan(alarmChan)
	thePilot.SetSteeringChan(steeringChan)
	thePilot.SetPanicChan(panicChan)
	ws.SetPilot(thePilot)

	////////////////////////////////////////
	// a surprising input
	////////////////////////////////////////
	control := control.New(switchGpio, thePilot)
	control.SetPanicChan(panicChan)

	////////////////////////////////////////
	// a friendly interface to the AP100
	////////////////////////////////////////
	ap100 := ap100.New(compass)
	headingChan := make(chan interface{})
	ap100.SetInputChan(headingChan)
	ap100.SetPanicChan(panicChan)

	////////////////////////////////////////
	// a wonderful gps
	////////////////////////////////////////
	gps := gps.New(conf.Conf.GpsSerialPort)
	gps.SetMessagesChan(pilotChan)
	gps.SetHeadingChan(headingChan)
	gps.SetErrorChan(pilotChan)
	gps.SetPanicChan(panicChan)
	gps.SetTracerChan(tracerChan)

	tracer.Start()
	defer tracer.Shutdown()
	ap100.Start()
	defer ap100.Shutdown()
	gps.Start()
	control.Start()
	defer control.Shutdown()
	alarm.Start()
	defer alarm.Shutdown()
	dashboard.Start()
	defer dashboard.Shutdown()
	steering.Start()
	defer steering.Shutdown()
	thePilot.Start()
	defer thePilot.Shutdown()

	// Wait until we receive a signal
	utils.WaitForInterrupt(func() {
		log.Info("Interrupted - exiting")
		log.Info("Exiting -- version %v", Version)
	})

}