//! Using a static IP example
//!
//! - set SSID and PASSWORD env variable
//! - set STATIC_IP and GATEWAY_IP env variable (e.g. "192.168.2.191" / "192.168.2.1")
//! - might be necessary to configure your WiFi access point accordingly
//! - uses the given static IP
//! - responds with some HTML content when connecting to port 8080
//!

//% FEATURES: esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6

#![no_std]
#![no_main]

use core::cell::RefCell;
use core::ops::Deref;
use core::str::FromStr;

use blocking_network_stack::Stack;
use critical_section::Mutex;
use dumbswitch::network_data;
use embedded_io::*;
use esp_alloc as _;
use esp_backtrace as _;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Event, Input, Io, Level, Output, Pull};
use esp_hal::handler;
use esp_hal::interrupt::InterruptConfigurable;
use esp_hal::{
  clock::CpuClock,
  main,
  rng::Rng,
  time::{self, Duration},
  timer::timg::TimerGroup,
};
use esp_println::println;
use esp_wifi::{
  init,
  wifi::{
    utils::create_network_interface, AccessPointInfo, ClientConfiguration, Configuration,
    WifiError, WifiStaDevice,
  },
};
use log::{debug, error, info, trace, warn};
use smoltcp::iface::{SocketSet, SocketStorage};

#[main]
fn main() -> ! {
  esp_println::logger::init_logger_from_env();
  let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
  let peripherals = esp_hal::init(config);

  let delay = Delay::new();

  esp_alloc::heap_allocator!(72 * 1024);

  info!("Preparing GPIO");

  // Set GPIO1 as an output, and set its state low initially.
  let mut led = Output::new(peripherals.GPIO1, Level::Low);

  // Set GPIO4 as an input
  let button = Input::new(peripherals.GPIO3, Pull::Up);

  info!("Waiting 500ms pre-init");
  delay.delay_millis(500);

  led.set_low();

  let timg0 = TimerGroup::new(peripherals.TIMG0);

  let mut rng = Rng::new(peripherals.RNG);

  let init = init(timg0.timer0, rng.clone(), peripherals.RADIO_CLK).unwrap();

  let mut wifi = peripherals.WIFI;
  let (iface, device, mut controller) =
    create_network_interface(&init, &mut wifi, WifiStaDevice).unwrap();

  let mut socket_set_entries: [SocketStorage; 3] = Default::default();
  let socket_set = SocketSet::new(&mut socket_set_entries[..]);

  let now = || time::now().duration_since_epoch().to_millis();
  let mut stack = Stack::new(iface, device, socket_set, now, rng.random());

  let client_config = Configuration::Client(ClientConfiguration {
    ssid: network_data::SSID.try_into().unwrap(),
    password: network_data::PASSWORD.try_into().unwrap(),
    ..Default::default()
  });
  let res = controller.set_configuration(&client_config);
  debug!("wifi_set_configuration returned {:?}", res);

  controller.start().unwrap();
  debug!("is wifi started: {:?}", controller.is_started());

  info!("Start Wifi Scan");
  let res: Result<(heapless::Vec<AccessPointInfo, 30>, usize), WifiError> = controller.scan_n();
  if let Ok((res, _count)) = res {
    for ap in res {
      debug!("AP Info: {:?}", ap);
    }
  }

  debug!("{:?}", controller.capabilities());
  debug!("wifi_connect {:?}", controller.connect());

  // wait to get connected
  info!("Wait to get connected");
  loop {
    match controller.is_connected() {
      Ok(true) => break,
      Ok(false) => {}
      Err(err) => {
        error!("Failed to connect to wifi: {:?}", err);
        let mut high = false;
        loop {
          delay.delay_millis(1000);
          high = !high;
          if high {
            led.set_high();
          } else {
            led.set_low();
          }
        }
      }
    }
  }
  debug!("{:?}", controller.is_connected());

  info!("Setting static IP {}", network_data::STATIC_IP);

  stack
    .set_iface_configuration(&blocking_network_stack::ipv4::Configuration::Client(
      blocking_network_stack::ipv4::ClientConfiguration::Fixed(
        blocking_network_stack::ipv4::ClientSettings {
          ip: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip(network_data::STATIC_IP)),
          subnet: blocking_network_stack::ipv4::Subnet {
            gateway: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip(
              network_data::GATEWAY_IP,
            )),
            mask: blocking_network_stack::ipv4::Mask(24),
          },
          dns: None,
          secondary_dns: None,
        },
      ),
    ))
    .unwrap();

  for i in 0..4 {
    delay.delay_millis(100);
    if i % 2 == 0 {
      led.set_high();
    } else {
      led.set_low();
    }
  }

  info!(
    "Start busy loop on main. Point your browser to http://{}:8080/",
    network_data::STATIC_IP
  );

  let mut rx_buffer = [0u8; 1536];
  let mut tx_buffer = [0u8; 1536];
  let mut socket = stack.get_socket(&mut rx_buffer, &mut tx_buffer);

  let mut is_open = false;

  socket.listen(8080).unwrap();

  fn update_open(is_open: bool, button: &Input<'_>, led: &mut Output<'_>, delay: &Delay) -> bool {
    let mut is_open = if is_open { true } else { false };
    if button.is_low() {
      is_open = !is_open;

      if is_open {
        led.set_high();
      } else {
        led.set_low();
      }
      while button.is_low() {
        trace!("waiting for unpress btn");
        delay.delay_millis(10);
      }
    }
    is_open
  }

  loop {
    socket.work();
    is_open = update_open(is_open, &button, &mut led, &delay);

    if !socket.is_open() {
      socket.listen(8080).unwrap();
    }

    if socket.is_connected() {
      is_open = update_open(is_open, &button, &mut led, &delay);
      // debug!("Established Connection");

      // let mut time_out = false;
      // let deadline = time::now() + Duration::millis(500);
      // let mut buffer = [0u8; 8192];
      // let mut pos = 0;
      // while let Ok(len) = socket.read(&mut buffer[pos..]) {
      //   if pos + len > buffer.len() {
      //     error!(
      //       "We got {} bytes. Buffer overflowed, treating as timeout.",
      //       pos + len
      //     );
      //     time_out = true;
      //     break;
      //   }

      //   let to_print = unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) };

      //   if to_print.contains("\r\n\r\n") {
      //     break;
      //   }

      //   pos += len;

      //   if time::now() > deadline {
      //     println!("Timeout");
      //     time_out = true;
      //     break;
      //   }
      // }

      let parts = [
        (r##"{
  "api": "0.13",
  "api_compatibility": [
    "14",
    "15"
  ],
  "space": "Chaostreff Bern",
  "logo": "https://www.chaostreffbern.ch/images/logo_v1.1.png",
  "url": "https://www.chaostreffbern.ch","##)
          .as_bytes(),
        // TODO: Possibly add location.hint that we are in the basement?
        // TODO: Possibly add location.areas for each area in the space?
        (r##"
  "location": {
    "address": "Zwyssigstrasse 45, 3007 Bern, Switzerland",
    "lon": 7.421927,
    "lat": 46.944178,
    "timezone": "Europe/Zurich",
    "country_code": "CH"
  },
  "spacefed": {
    "spacenet": false,
    "spacesaml": false,
    "spacephone": false
  },
  "state": {
    "open": "##)
          .as_bytes(),
        (if is_open { "true" } else { "false" }).as_bytes(),
        r##",
    "message": "Open every Tuesday from 19h"
  },
  "contact": {
    "email": "info@chaostreffbern.ch",
    "ml": "bern@chaostreff.ch",
    "matrix": "#chaostreffbern:chaostreffbern.ch",
    "jabber": "xmpp://chaostreffbern@conference.chaostreffbern.ch",
    "mastodon": "@chaostreffbern@chaos.social"
  },
  "issue_report_channels": [
    "email"
  ],
  "feeds": {
    "blog": {
      "type": "rss",
      "url": "https://www.chaosbern.ch/feeds/chaosbern_rss.xml"
    },
    "calendar": {
      "type": "caldav",
      "url": "https://nextcloud.jenix.ch/remote.php/dav/public-calendars/xFMZfKSBNfp3mRNR/"
    }
  },
  "ext_ccc": "chaostreff"
}
"##
          .as_bytes(),
      ];

      // if !time_out {
      socket
        .write_all(
          b"HTTP/1.0 200 OK\r\n\
Content-Type: application/json\r\n\
UwU: if u read this u have been catgirled :3\r\n\
\r\n\
",
        )
        .unwrap();
      for part in parts {
        socket.write_all(part).unwrap();
      }

      socket.flush().unwrap();
      socket.work();
      // }

      socket.close();
      is_open = update_open(is_open, &button, &mut led, &delay);
    }
    // TODO: what
    let deadline = time::now() + Duration::millis(100);
    while time::now() < deadline && !socket.is_connected() {
      socket.work();
      is_open = update_open(is_open, &button, &mut led, &delay);
    }
  }
}

fn parse_ip(ip: &str) -> [u8; 4] {
  let mut result = [0u8; 4];
  for (idx, octet) in ip.split(".").into_iter().enumerate() {
    result[idx] = u8::from_str_radix(octet, 10).unwrap();
  }
  result
}