#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <IRsend.h>

//IRcodes
IRsend irsend(4); // ESP8266 GPIO pin to use. Recommended: 4 (D2).

uint16_t tvPower[72] = {
  0x0000, 0x006C, 0x0000, 0x0022, 0x00AD, 0x00AD, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0041, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0041, 0x0016, 0x0016, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x0041, 0x0016, 0x06FB
};

//WiFi
// Set your Static IP address
IPAddress local_IP(192, 168, 1, 211);
// Set your Gateway IP address
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

//Webserver
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

const long CleanUpInterval = 300000;
unsigned long previousMillis = 0;

bool multivolUp = false;
bool multivolDown = false;

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>Ganush Remote</title>
  <meta name='viewport' content='width=device-width, initial-scale=1.0' charset="UTF-8">
  <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css'>
</head>
<body>
  <h1>Power</h1>
  <button id="powAll" onclick="pressButton(this)">Both</button>
  <button id="powRec" onclick="pressButton(this)">Receiver</button>
  <button id="powTV" onclick="pressButton(this)">TV</button>
  <h1>Volume</h1>
  <button id="volUp" onclick="pressButton(this)">🔊</button>
  <button id="volUp10" onclick="pressButton(this)">🔊 x10</button>
  <br>
  <button id="volDown" onclick="pressButton(this)">🔉</button>
  <button id="volDown10" onclick="pressButton(this)">🔉 x10</button>
  <br>
  <button id="mute" onclick="pressButton(this)">🔇</button>
  <h1>Device</h1>
  <button id="HDMI1" onclick="pressButton(this)">Pi</button>
  <button id="HDMI2" onclick="pressButton(this)">Chromecast</button>
  <button id="HDMI3" onclick="pressButton(this)">Switch</button>
  <button id="HDMI4" onclick="pressButton(this)">PS4</button>
</body>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function initWebSocket() {
  console.log('Trying to open a WebSocket connection...');
  websocket = new WebSocket(gateway);
}
function onLoad(event) {
  initWebSocket();
}
function pressButton(element) {
  console.log(element.id);
  websocket.send(element.id);
}
document.addEventListener('focus', (e) => {
  if (websocket.readyState === WebSocket.CLOSED) {
    initWebSocket();
  }
});
</script>
</html>
)rawliteral";

//handle data from websocket
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    //Serial.println((char*)data);
    data[len] = 0;
    if (strcmp((char*)data, "powAll") == 0) {
      Serial.println("PowAll Pressed");
      irsend.sendPronto(tvPower, 72);
      irsend.sendNEC(0x7E8154AB);
      return;
    }
    if (strcmp((char*)data, "powRec") == 0) {
      Serial.println("powRec Pressed");
      irsend.sendNEC(0x7E8154AB);
      return;
    }
    if (strcmp((char*)data, "powTV") == 0) {
      Serial.println("powTV Pressed");
      irsend.sendPronto(tvPower, 72);
      return;
    }
    if (strcmp((char*)data, "volUp") == 0) {
      Serial.println("volUp Pressed");
      irsend.sendNEC(0x5EA158A7);
      return;
    }
    if (strcmp((char*)data, "volUp10") == 0) {
      Serial.println("volUp10 Pressed");
      multivolUp = true;
      //irsend.sendNEC(0x5EA158A7);
      return;
    }
    if (strcmp((char*)data, "volDown") == 0) {
      Serial.println("volDown Pressed");
      irsend.sendNEC(0x5EA1D827);
      return;
    }
    if (strcmp((char*)data, "volDown10") == 0) {
      Serial.println("volDown10 Pressed");
      multivolDown = true;
      //irsend.sendNEC(0x5EA1D827);
      return;
    }
    if (strcmp((char*)data, "mute") == 0) {
      Serial.println("mute Pressed");
      irsend.sendNEC(0x5EA138C7);
      return;
    }
    if (strcmp((char*)data, "HDMI1") == 0) {
      Serial.println("HDMI1 Pressed");
      irsend.send(NEC_LIKE, 0x5EA1E21C, kNECBits);
      return;
    }
    if (strcmp((char*)data, "HDMI2") == 0) {
      Serial.println("HDMI2 Pressed");
      irsend.send(NEC_LIKE, 0x5EA152AC, kNECBits);
      return;
    }
    if (strcmp((char*)data, "HDMI3") == 0) {
      Serial.println("HDMI3 Pressed");
      irsend.send(NEC_LIKE, 0x5EA1B24C, kNECBits);
      return;
    }
    if (strcmp((char*)data, "HDMI4") == 0) {
      Serial.println("HDMI4 Pressed");
      irsend.send(NEC_LIKE, 0x5EA10AF4, kNECBits);
      return;
    }
  }
}

//websocket event handler
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
             void *arg, uint8_t *data, size_t len) {
    switch (type) {
      case WS_EVT_CONNECT:
        Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
        break;
      case WS_EVT_DISCONNECT:
        Serial.printf("WebSocket client #%u disconnected\n", client->id());
        break;
      case WS_EVT_DATA:
        handleWebSocketMessage(arg, data, len);
        break;
      case WS_EVT_PONG:
      case WS_EVT_ERROR:
        break;
  }
}

void setup() {
  Serial.begin(115200);
  //SPIFFS.begin();
  irsend.begin();
  
  //Wifi connect
  WiFi.mode(WIFI_STA);
  WiFi.config(local_IP, gateway, subnet);
  WiFi.begin("WIFI_SSID","PASSCODE");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());

  //Webserver
  ws.onEvent(onEvent);
  server.addHandler(&ws);

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html; charset=utf-8", index_html);
  });
  server.begin();
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= CleanUpInterval) {
    previousMillis = currentMillis;
    ws.cleanupClients();
    Serial.println("Cleaning up Clients");
  }

  if (multivolUp) {
    for (int i = 0; i <= 9; i++) {
        irsend.sendNEC(0x5EA158A7);
        delay(70);
    }
    multivolUp = false;
  }
  if (multivolDown) {
    for (int i = 0; i <= 9; i++) {
        irsend.sendNEC(0x5EA1D827);
        delay(70);
      }
  multivolDown = false;
  }
}
```