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

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

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

	ws := webserver.New(Version)
	ws.SetPanicChan(panicChan)
	ws.SetPilot(&fakePilot{})
	ws.SetTracer(&fakeTracer{points: []types.Point{{
		Latitude:  45.,
		Longitude: 5.,
		Time:      types.JSONTime(time.Now())}}})
	ws.SetDashboard(queryable{leds: map[string]bool{
		dashboard.NoGPSFix:                true,
		dashboard.InvalidGPSData:          true,
		dashboard.SpeedTooLow:             false,
		dashboard.HeadingErrorOutOfBounds: true,
		dashboard.CorrectionAtLimit:       true,
	}})
	ws.Start()

	// 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)
	})

}