// Copyright 2017, 2018 David Conran

#include "ir_Panasonic.h"
#include "IRac.h"
#include "IRrecv.h"
#include "IRrecv_test.h"
#include "IRsend.h"
#include "IRsend_test.h"
#include "IRutils.h"
#include "gtest/gtest.h"

// Tests for encodePanasonic().

TEST(TestEncodePanasonic, General) {
  IRsendTest irsend(4);
  EXPECT_EQ(0x0, irsend.encodePanasonic(0, 0, 0, 0));
  EXPECT_EQ(0x101010101, irsend.encodePanasonic(1, 1, 1, 1));
  EXPECT_EQ(0xFFFF, irsend.encodePanasonic(0, 0, 0, 0xFF));
  EXPECT_EQ(0xFF00FF, irsend.encodePanasonic(0, 0, 0xFF, 0));
  EXPECT_EQ(0xFF0000FF, irsend.encodePanasonic(0, 0xFF, 0, 0));
  EXPECT_EQ(0xFFFF00000000, irsend.encodePanasonic(0xFFFF, 0, 0, 0));
  EXPECT_EQ(0xFFFFFFFFFFFF, irsend.encodePanasonic(0xFFFF, 0xFF, 0xFF, 0xFF));
  EXPECT_EQ(0x40040190ED7C, irsend.encodePanasonic(0x4004, 0x01, 0x90, 0xED));
}

// Tests for sendPanasonic64().

// Test sending typical data only.
TEST(TestSendPanasonic64, SendDataOnly) {
  IRsendTest irsend(4);
  irsend.begin();

  irsend.reset();
  irsend.sendPanasonic64(0x0);
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s116208",
      irsend.outputStr());

  irsend.reset();
  irsend.sendPanasonic64(0x40040190ED7C);
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384",
      irsend.outputStr());

  irsend.reset();
  irsend.sendPanasonic64(0xFFFFFFFFFFFF);
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296"
      "m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296"
      "m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296"
      "m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296"
      "m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296"
      "m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296m432s1296"
      "m432s74736",
      irsend.outputStr());
}

// Test sending with different repeats.
TEST(TestSendPanasonic64, SendWithRepeats) {
  IRsendTest irsend(4);
  irsend.begin();

  irsend.reset();
  irsend.sendPanasonic64(0x40040190ED7C, kPanasonicBits, 0);  // 0 repeats.
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384",
      irsend.outputStr());

  irsend.reset();
  irsend.sendPanasonic64(0x40040190ED7C, kPanasonicBits, 1);  // 1 repeat.
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384",
      irsend.outputStr());

  irsend.sendPanasonic64(0x40040190ED7C, kPanasonicBits, 2);  // 2 repeats.
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s1296m432s432m432s432"
      "m432s102384",
      irsend.outputStr());
}

// Test sending an atypical data size.
TEST(TestSendPanasonic64, SendUnusualSize) {
  IRsendTest irsend(4);
  irsend.begin();

  irsend.reset();
  irsend.sendPanasonic64(0x0, 8);
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s150768",
      irsend.outputStr());

  irsend.reset();
  irsend.sendPanasonic64(0x1234567890ABCDEF, 64);
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s432m432s432m432s1296m432s432m432s432m432s1296m432s432"
      "m432s432m432s432m432s1296m432s1296m432s432m432s1296m432s432m432s432"
      "m432s432m432s1296m432s432m432s1296m432s432m432s1296m432s1296m432s432"
      "m432s432m432s1296m432s1296m432s1296m432s1296m432s432m432s432m432s432"
      "m432s1296m432s432m432s432m432s1296m432s432m432s432m432s432m432s432"
      "m432s1296m432s432m432s1296m432s432m432s1296m432s432m432s1296m432s1296"
      "m432s1296m432s1296m432s432m432s432m432s1296m432s1296m432s432m432s1296"
      "m432s1296m432s1296m432s1296m432s432m432s1296m432s1296m432s1296m432s1296"
      "m432s74736",
      irsend.outputStr());
}

// Tests for sendPanasonic().

TEST(TestSendPanasonic, CompareToSendPanasonic64) {
  IRsendTest panasonic(4);
  IRsendTest panasonic64(0);

  panasonic.begin();
  panasonic64.begin();

  panasonic.reset();
  panasonic64.reset();

  panasonic.sendPanasonic(0x4004, 0x0190ED7C);
  panasonic64.sendPanasonic64(0x40040190ED7C);
  EXPECT_EQ(panasonic64.outputStr(), panasonic.outputStr());

  panasonic.sendPanasonic(0x0, 0x0);
  panasonic64.sendPanasonic64(0x0);
  EXPECT_EQ(panasonic64.outputStr(), panasonic.outputStr());

  panasonic.sendPanasonic(0x0, 0x0, 8);
  panasonic64.sendPanasonic64(0x0, 8);
  EXPECT_EQ(panasonic64.outputStr(), panasonic.outputStr());

  panasonic.sendPanasonic(0x1234, 0x567890AB, 64);
  panasonic64.sendPanasonic64(0x1234567890AB, 64);
  EXPECT_EQ(panasonic64.outputStr(), panasonic.outputStr());

  panasonic.sendPanasonic(0x1234, 0x567890AB, kPanasonicBits, 2);
  panasonic64.sendPanasonic64(0x1234567890AB, kPanasonicBits, 2);
  EXPECT_EQ(panasonic64.outputStr(), panasonic.outputStr());
}

// Tests for decodePanasonic().

// Decode normal Panasonic messages.
TEST(TestDecodePanasonic, NormalDecodeWithStrict) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  // Normal Panasonic 48-bit message.
  irsend.reset();
  irsend.sendPanasonic64(0x40040190ED7C);
  irsend.makeDecodeResult();
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040190ED7C, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x0190ED7C, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);

  // Synthesised Normal Panasonic 48-bit message.
  irsend.reset();
  irsend.sendPanasonic64(irsend.encodePanasonic(0x4004, 0x12, 0x34, 0x56));
  irsend.makeDecodeResult();
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x400412345670, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x12345670, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);

  // Synthesised Normal Panasonic 48-bit message.
  irsend.reset();
  irsend.sendPanasonic64(irsend.encodePanasonic(0x4004, 0x1, 0x1, 0x1));
  irsend.makeDecodeResult();
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x400401010101, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x1010101, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);
}

// Decode normal repeated Panasonic messages.
TEST(TestDecodePanasonic, NormalDecodeWithRepeatAndStrict) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  // Normal Panasonic 48-bit message with 2 repeats.
  irsend.reset();
  irsend.sendPanasonic64(0x40040190ED7C, kPanasonicBits, 2);
  irsend.makeDecodeResult();
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040190ED7C, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x190ED7C, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);

  irsend.makeDecodeResult(2 * kPanasonicBits + 4);
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040190ED7C, irsend.capture.value);

  irsend.makeDecodeResult(2 * (2 * kPanasonicBits + 4));
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040190ED7C, irsend.capture.value);
}

// Decode Panasonic messages with unsupported values.
TEST(TestDecodePanasonic, DecodeWithNonStrictValues) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  irsend.reset();
  irsend.sendPanasonic64(0x0);  // Illegal value Panasonic 48-bit message.
  irsend.makeDecodeResult();
  // Should fail with strict on.
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                      kPanasonicBits, true));
  // Should pass if strict off.
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, false));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x0, irsend.capture.value);
  EXPECT_EQ(0x0, irsend.capture.address);
  EXPECT_EQ(0x0, irsend.capture.command);

  irsend.reset();
  // Illegal address/Manufacturer code. The rest is legal.
  irsend.sendPanasonic64(irsend.encodePanasonic(0, 1, 2, 3));
  irsend.makeDecodeResult();
  // Should fail with strict on.
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                      kPanasonicBits, true));
  // Should pass if strict off.
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, false));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x1020300, irsend.capture.value);
  EXPECT_EQ(0x0, irsend.capture.address);
  EXPECT_EQ(0x1020300, irsend.capture.command);
}

// Decode Panasonic messages with unsupported size/lengths.
TEST(TestDecodePanasonic, DecodeWithNonStrictSize) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  irsend.reset();
  irsend.sendPanasonic64(0x12345678, 32);  // Illegal size Panasonic message.
  irsend.makeDecodeResult();

  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                      kPanasonicBits, true));

  irsend.makeDecodeResult();
  // Should fail with strict when we ask for the wrong bit size.
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset, 32, true));
  // Should pass if strict off.
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset, 32, false));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(32, irsend.capture.bits);
  EXPECT_EQ(0x12345678, irsend.capture.value);
  EXPECT_EQ(0x0, irsend.capture.address);
  EXPECT_EQ(0x12345678, irsend.capture.command);

  // Illegal over length (56-bit) message.
  irsend.reset();
  irsend.sendPanasonic64(irsend.encodePanasonic(0x4004, 1, 2, 3), 56);
  irsend.makeDecodeResult();

  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                      kPanasonicBits, true));
  // Shouldn't pass if strict off and wrong bit size.
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                      kPanasonicBits, false));
  // Re-decode with correct bit size.
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset, 56, true));
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset, 56, false));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(56, irsend.capture.bits);
  EXPECT_EQ(0x400401020300, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x01020300, irsend.capture.command);
}

// Decode (non-standard) 64-bit messages.
TEST(TestDecodePanasonic, Decode64BitMessages) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  irsend.reset();
  // Illegal value & size Panasonic 64-bit message.
  irsend.sendPanasonic64(0xFFFFFFFFFFFFFFFF, 64);
  irsend.makeDecodeResult();
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset, 64, true));
  // Should work with a 'normal' match (not strict)
  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset, 64, false));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(64, irsend.capture.bits);
  EXPECT_EQ(0xFFFFFFFFFFFFFFFF, irsend.capture.value);
  EXPECT_EQ(0xFFFFFFFF, irsend.capture.address);
  EXPECT_EQ(0xFFFFFFFF, irsend.capture.command);
}

// Decode a 'real' example via GlobalCache
TEST(TestDecodePanasonic, DecodeGlobalCacheExample) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  irsend.reset();
  // Panasonic code from Global Cache.
  uint16_t gc_test[103] = {
      37000, 1,  1,  126, 64, 16, 17, 16, 49, 15, 16, 16, 16,  16, 16,
      16,    17, 15, 17,  15, 17, 15, 17, 15, 16, 16, 16, 16,  16, 16,
      17,    15, 49, 16,  16, 16, 16, 16, 17, 15, 17, 15, 17,  15, 17,
      15,    16, 16, 16,  16, 16, 16, 49, 15, 49, 16, 17, 15,  17, 15,
      49,    16, 16, 16,  17, 16, 17, 15, 17, 15, 49, 16, 49,  15, 49,
      16,    17, 16, 49,  15, 49, 16, 17, 15, 48, 16, 16, 16,  49, 15,
      48,    16, 49, 15,  49, 16, 49, 15, 17, 15, 16, 16, 2721};
  irsend.sendGC(gc_test, 103);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                     kPanasonicBits, true));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040190ED7C, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x0190ED7C, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);

  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040190ED7C, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x0190ED7C, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);
}

// Fail to decode a non-Panasonic example via GlobalCache
TEST(TestDecodePanasonic, FailToDecodeNonPanasonicExample) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  irsend.reset();
  // Modified a few entries to unexpected values, based on previous test case.
  uint16_t gc_test[39] = {38000, 1,  1,  322, 162, 20, 61,  20, 61, 20,
                          20,    20, 20, 20,  20,  20, 127, 20, 61, 9,
                          20,    20, 61, 20,  20,  20, 61,  20, 61, 20,
                          61,    20, 20, 20,  20,  20, 20,  20, 884};
  irsend.sendGC(gc_test, 39);
  irsend.makeDecodeResult();

  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture));
  ASSERT_FALSE(irrecv.decodePanasonic(&irsend.capture, kStartOffset,
                                      kPanasonicBits, false));
}

// Failing to decode Panasonic in Issue #245
TEST(TestDecodePanasonic, DecodeIssue245) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  irsend.reset();

  uint16_t rawData[100] = {
      3550, 1750, 500, 450,  500, 1300, 500, 450,  500, 450,  500, 450,
      500,  450,  500, 450,  500, 450,  500, 450,  500, 450,  500, 450,
      500,  450,  500, 450,  500, 1300, 500, 450,  500, 450,  500, 450,
      500,  450,  500, 450,  500, 450,  500, 450,  500, 450,  500, 450,
      500,  1300, 500, 450,  500, 450,  500, 450,  500, 450,  500, 450,
      500,  450,  500, 450,  500, 450,  500, 1300, 500, 450,  500, 1300,
      500,  1300, 500, 1300, 500, 1300, 500, 450,  500, 450,  500, 1300,
      500,  450,  500, 1300, 500, 1300, 500, 1300, 500, 1300, 500, 450,
      500,  1300, 500, 5000};

  irsend.sendRaw(rawData, 100, 37);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decodePanasonic(&irsend.capture));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040100BCBD, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x100BCBD, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);

  irsend.reset();
  irsend.sendRaw(rawData, 99, 37);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  EXPECT_EQ(PANASONIC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicBits, irsend.capture.bits);
  EXPECT_EQ(0x40040100BCBD, irsend.capture.value);
  EXPECT_EQ(0x4004, irsend.capture.address);
  EXPECT_EQ(0x100BCBD, irsend.capture.command);
  EXPECT_FALSE(irsend.capture.repeat);
}

// Tests for sendPanasonicAC().

// Test sending typical data only.
TEST(TestSendPanasonicAC, SendDataOnly) {
  IRsendTest irsend(0);
  irsend.begin();

  irsend.reset();

  uint8_t state[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x30, 0x32, 0x80, 0xAF, 0x00,
      0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x83};
  irsend.sendPanasonicAC(state);
  EXPECT_EQ(
      "f36700d50"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s1296m432s1296"
      "m432s432m432s432m432s1296m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s1296m432s1296m432s432m432s432m432s432m432s432m432s432"
      "m432s10000"
      "m3456s1728"
      "m432s432m432s1296m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s1296m432s1296"
      "m432s432m432s432m432s1296m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s1296m432s1296m432s432m432s432"
      "m432s432m432s1296m432s432m432s432m432s1296m432s1296m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s1296m432s1296m432s1296m432s1296m432s432m432s1296m432s432m432s1296"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s1296m432s1296m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s1296m432s1296m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s432m432s432m432s432m432s432m432s432m432s432m432s432m432s432"
      "m432s432m432s1296m432s1296m432s432m432s432m432s432m432s432m432s432"
      "m432s1296m432s1296m432s432m432s432m432s432m432s432m432s432m432s1296"
      "m432s100000",
      irsend.outputStr());
}

// Tests for the IRPanasonicAc class.

TEST(TestIRPanasonicAcClass, ChecksumCalculation) {
  IRPanasonicAc pana(0);

  const uint8_t originalstate[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x30, 0x32, 0x80, 0xAF, 0x00,
      0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x83};
  uint8_t examplestate[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x30, 0x32, 0x80, 0xAF, 0x00,
      0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x83};

  EXPECT_TRUE(IRPanasonicAc::validChecksum(examplestate));
  EXPECT_EQ(0x83, IRPanasonicAc::calcChecksum(examplestate));

  examplestate[kPanasonicAcStateLength - 1] = 0x0;  // Set incorrect checksum.
  EXPECT_FALSE(IRPanasonicAc::validChecksum(examplestate));
  EXPECT_EQ(0x83, IRPanasonicAc::calcChecksum(examplestate));
  pana.setRaw(examplestate);
  // Extracting the state from the object should have a correct checksum.
  EXPECT_TRUE(IRPanasonicAc::validChecksum(pana.getRaw()));
  EXPECT_STATE_EQ(originalstate, pana.getRaw(), kPanasonicAcBits);
  examplestate[kPanasonicAcStateLength - 1] = 0x83;  // Restore old checksum.

  // Change the state to force a different checksum.
  examplestate[6] = 0x01;  // Should increase checksum by 1.
  EXPECT_FALSE(IRPanasonicAc::validChecksum(examplestate));
  EXPECT_EQ(0x84, IRPanasonicAc::calcChecksum(examplestate));
}

TEST(TestIRPanasonicAcClass, SetAndGetPower) {
  IRPanasonicAc pana(0);
  pana.on();
  EXPECT_TRUE(pana.getPower());
  pana.off();
  EXPECT_FALSE(pana.getPower());
  pana.setPower(true);
  EXPECT_TRUE(pana.getPower());
  pana.setPower(false);
  EXPECT_FALSE(pana.getPower());
}

TEST(TestIRPanasonicAcClass, SetAndGetModel) {
  IRPanasonicAc pana(0);
  EXPECT_EQ(kPanasonicJke, pana.getModel());
  pana.setModel(kPanasonicDke);
  EXPECT_EQ(kPanasonicDke, pana.getModel());
  pana.setModel(kPanasonicLke);
  EXPECT_EQ(kPanasonicLke, pana.getModel());
  pana.setModel(kPanasonicNke);
  EXPECT_EQ(kPanasonicNke, pana.getModel());
  pana.setModel(kPanasonicUnknown);  // shouldn't change.
  EXPECT_EQ(kPanasonicNke, pana.getModel());
  pana.setModel((panasonic_ac_remote_model_t)255);  // shouldn't change.
  EXPECT_EQ(kPanasonicNke, pana.getModel());
  pana.setModel(kPanasonicJke);
  EXPECT_EQ(kPanasonicJke, pana.getModel());

  // This state tickled a bug in getModel(). Should read as a JKE.
  uint8_t jkeState[27] = {0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
                          0x20, 0xE0, 0x04, 0x00, 0x32, 0x2E, 0x80, 0xA2, 0x00,
                          0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x74};
  pana.setModel(kPanasonicDke);  // Make sure it isn't somehow set to JKE
  pana.setRaw(jkeState);
  EXPECT_EQ(kPanasonicJke, pana.getModel());
  EXPECT_STATE_EQ(jkeState, pana.getRaw(), kPanasonicAcBits);

  // This state tickled a bug in getModel(). Should read as CKP.
  uint8_t ckpState[27] = {0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
                          0x20, 0xE0, 0x04, 0x00, 0x67, 0x2E, 0x80, 0xAF, 0x00,
                          0xC0, 0x6B, 0x98, 0x10, 0x00, 0x81, 0x64, 0x05, 0x87};
  pana.setModel(kPanasonicDke);  // Make sure it isn't somehow set to CKP
  pana.setRaw(ckpState);
  EXPECT_EQ(kPanasonicCkp, pana.getModel());
  EXPECT_STATE_EQ(ckpState, pana.getRaw(), kPanasonicAcBits);
}

TEST(TestIRPanasonicAcClass, SetAndGetMode) {
  IRPanasonicAc pana(0);
  pana.setMode(kPanasonicAcCool);
  pana.setTemp(21);
  EXPECT_EQ(kPanasonicAcCool, pana.getMode());
  pana.setMode(kPanasonicAcHeat);
  EXPECT_EQ(kPanasonicAcHeat, pana.getMode());
  pana.setMode(kPanasonicAcAuto);
  EXPECT_EQ(kPanasonicAcAuto, pana.getMode());
  pana.setMode(kPanasonicAcDry);
  EXPECT_EQ(kPanasonicAcDry, pana.getMode());
  EXPECT_EQ(21, pana.getTemp());  // Temp should be unchanged.
  pana.setMode(kPanasonicAcFan);
  EXPECT_EQ(kPanasonicAcFan, pana.getMode());
  EXPECT_EQ(kPanasonicAcFanModeTemp, pana.getTemp());  // Temp should change.
  pana.setMode(kPanasonicAcCool);
  EXPECT_EQ(kPanasonicAcCool, pana.getMode());
  // Temp should be unchanged from the last manual change.
  EXPECT_EQ(21, pana.getTemp());
}

TEST(TestIRPanasonicAcClass, SetAndGetTemp) {
  IRPanasonicAc pana(0);
  pana.setTemp(25);
  EXPECT_EQ(25, pana.getTemp());
  pana.setTemp(kPanasonicAcMinTemp);
  EXPECT_EQ(kPanasonicAcMinTemp, pana.getTemp());
  pana.setTemp(kPanasonicAcMinTemp - 1);
  EXPECT_EQ(kPanasonicAcMinTemp, pana.getTemp());
  pana.setTemp(kPanasonicAcMaxTemp);
  EXPECT_EQ(kPanasonicAcMaxTemp, pana.getTemp());
  pana.setTemp(kPanasonicAcMaxTemp + 1);
  EXPECT_EQ(kPanasonicAcMaxTemp, pana.getTemp());
}

TEST(TestIRPanasonicAcClass, SetAndGetFan) {
  IRPanasonicAc pana(0);
  pana.setFan(kPanasonicAcFanAuto);
  EXPECT_EQ(kPanasonicAcFanAuto, pana.getFan());
  pana.setFan(kPanasonicAcFanMin);
  EXPECT_EQ(kPanasonicAcFanMin, pana.getFan());
  pana.setFan(kPanasonicAcFanMin - 1);
  EXPECT_EQ(kPanasonicAcFanAuto, pana.getFan());
  pana.setFan(kPanasonicAcFanMed);
  EXPECT_EQ(kPanasonicAcFanMed, pana.getFan());
  pana.setFan(kPanasonicAcFanMin + 1);
  EXPECT_EQ(kPanasonicAcFanAuto, pana.getFan());
  pana.setFan(kPanasonicAcFanMax);
  EXPECT_EQ(kPanasonicAcFanMax, pana.getFan());
  pana.setFan(kPanasonicAcFanMax + 1);
  EXPECT_EQ(kPanasonicAcFanAuto, pana.getFan());
  pana.setFan(kPanasonicAcFanMax - 1);
  EXPECT_EQ(kPanasonicAcFanAuto, pana.getFan());
}

TEST(TestIRPanasonicAcClass, SetAndGetSwings) {
  IRPanasonicAc pana(0);

  // Vertical
  pana.setSwingVertical(kPanasonicAcSwingVAuto);
  EXPECT_EQ(kPanasonicAcSwingVAuto, pana.getSwingVertical());

  pana.setSwingVertical(kPanasonicAcSwingVHighest);
  EXPECT_EQ(kPanasonicAcSwingVHighest, pana.getSwingVertical());
  pana.setSwingVertical(kPanasonicAcSwingVHighest - 1);
  EXPECT_EQ(kPanasonicAcSwingVHighest, pana.getSwingVertical());
  pana.setSwingVertical(kPanasonicAcSwingVHighest + 1);
  EXPECT_EQ(kPanasonicAcSwingVHighest + 1, pana.getSwingVertical());

  pana.setSwingVertical(kPanasonicAcSwingVLowest);
  EXPECT_EQ(kPanasonicAcSwingVLowest, pana.getSwingVertical());
  pana.setSwingVertical(kPanasonicAcSwingVLowest + 1);
  EXPECT_EQ(kPanasonicAcSwingVLowest, pana.getSwingVertical());
  pana.setSwingVertical(kPanasonicAcSwingVLowest - 1);
  EXPECT_EQ(kPanasonicAcSwingVLowest - 1, pana.getSwingVertical());

  pana.setSwingVertical(kPanasonicAcSwingVAuto);
  EXPECT_EQ(kPanasonicAcSwingVAuto, pana.getSwingVertical());

  // Horizontal is model dependant.
  pana.setModel(kPanasonicNke);  // NKE is always fixed in the middle.
  EXPECT_EQ(kPanasonicAcSwingHMiddle, pana.getSwingHorizontal());
  pana.setSwingHorizontal(kPanasonicAcSwingHAuto);
  EXPECT_EQ(kPanasonicAcSwingHMiddle, pana.getSwingHorizontal());

  pana.setModel(kPanasonicJke);  // JKE has no H swing.
  EXPECT_EQ(0, pana.getSwingHorizontal());
  pana.setSwingHorizontal(kPanasonicAcSwingHMiddle);
  EXPECT_EQ(0, pana.getSwingHorizontal());

  pana.setModel(kPanasonicLke);  // LKE is always fixed in the middle.
  EXPECT_EQ(kPanasonicAcSwingHMiddle, pana.getSwingHorizontal());
  pana.setSwingHorizontal(kPanasonicAcSwingHAuto);
  EXPECT_EQ(kPanasonicAcSwingHMiddle, pana.getSwingHorizontal());

  pana.setModel(kPanasonicDke);  // DKE has full control.
  ASSERT_EQ(kPanasonicDke, pana.getModel());
  // Auto was last requested.
  EXPECT_EQ(kPanasonicAcSwingHAuto, pana.getSwingHorizontal());
  pana.setSwingHorizontal(kPanasonicAcSwingHLeft);
  EXPECT_EQ(kPanasonicAcSwingHLeft, pana.getSwingHorizontal());
  // Changing models from DKE to something else, then back should not change
  // the intended swing.
  pana.setModel(kPanasonicLke);
  EXPECT_EQ(kPanasonicAcSwingHMiddle, pana.getSwingHorizontal());
  pana.setModel(kPanasonicDke);
  EXPECT_EQ(kPanasonicAcSwingHLeft, pana.getSwingHorizontal());
}

TEST(TestIRPanasonicAcClass, QuietAndPowerful) {
  IRPanasonicAc pana(0);
  pana.setQuiet(false);
  EXPECT_FALSE(pana.getQuiet());
  pana.setQuiet(true);
  EXPECT_TRUE(pana.getQuiet());
  EXPECT_FALSE(pana.getPowerful());
  pana.setPowerful(false);
  EXPECT_FALSE(pana.getPowerful());
  EXPECT_TRUE(pana.getQuiet());
  pana.setPowerful(true);
  EXPECT_TRUE(pana.getPowerful());
  EXPECT_FALSE(pana.getQuiet());
  pana.setPowerful(false);
  EXPECT_FALSE(pana.getPowerful());
  EXPECT_FALSE(pana.getQuiet());
  pana.setPowerful(true);
  pana.setQuiet(true);
  EXPECT_TRUE(pana.getQuiet());
  EXPECT_FALSE(pana.getPowerful());
}

TEST(TestIRPanasonicAcClass, SetAndGetIon) {
  IRPanasonicAc ac(0);
  // Ion Filter only works for DKE.
  ac.setModel(kPanasonicDke);
  ac.setIon(true);
  EXPECT_TRUE(ac.getIon());
  ac.setIon(false);
  EXPECT_FALSE(ac.getIon());
  ac.setIon(true);
  EXPECT_TRUE(ac.getIon());

  // Now try a different (a guess at unsupported) model.
  ac.setModel(kPanasonicRkr);
  EXPECT_FALSE(ac.getIon());
  ac.setIon(true);
  EXPECT_FALSE(ac.getIon());
  ac.setIon(false);
  EXPECT_FALSE(ac.getIon());
}

TEST(TestIRPanasonicAcClass, HumanReadable) {
  IRPanasonicAc pana(0);
  EXPECT_EQ(
      "Model: 4 (JKE), Power: Off, Mode: 0 (Auto), Temp: 0C, "
      "Fan: 253 (UNKNOWN), Swing(V): 0 (UNKNOWN), Quiet: Off, "
      "Powerful: Off, Clock: 00:00, On Timer: Off, Off Timer: Off",
      pana.toString());
  pana.setPower(true);
  pana.setTemp(kPanasonicAcMaxTemp);
  pana.setMode(kPanasonicAcHeat);
  pana.setFan(kPanasonicAcFanMax);
  pana.setSwingVertical(kPanasonicAcSwingVAuto);
  pana.setPowerful(true);
  EXPECT_EQ(
      "Model: 4 (JKE), Power: On, Mode: 4 (Heat), Temp: 30C, "
      "Fan: 4 (High), Swing(V): 15 (Auto), Quiet: Off, "
      "Powerful: On, Clock: 00:00, On Timer: Off, Off Timer: Off",
      pana.toString());
  pana.setQuiet(true);
  pana.setModel(kPanasonicLke);
  EXPECT_EQ(
      "Model: 1 (LKE), Power: Off, Mode: 4 (Heat), Temp: 30C, "
      "Fan: 4 (High), Swing(V): 15 (Auto), "
      "Swing(H): 6 (Middle), Quiet: On, Powerful: Off, "
      "Clock: 00:00, On Timer: 00:00, Off Timer: Off",
      pana.toString());
  pana.setModel(kPanasonicDke);
  pana.setSwingHorizontal(kPanasonicAcSwingHRight);
  EXPECT_EQ(
      "Model: 3 (DKE), Power: Off, Mode: 4 (Heat), Temp: 30C, "
      "Fan: 4 (High), Swing(V): 15 (Auto), "
      "Swing(H): 11 (Right), Quiet: On, Powerful: Off, Ion: Off, "
      "Clock: 00:00, On Timer: Off, Off Timer: Off",
      pana.toString());
}

// Tests for decodePanasonicAC().

// Decode normal Panasonic AC messages.
TEST(TestDecodePanasonicAC, RealExample) {
  IRsendTest irsend(4);
  IRrecv irrecv(4);
  irsend.begin();

  // Data from Issue #525
  uint16_t rawData[439] = {
      3582, 1686, 488, 378,  488, 1238, 488, 378,  488, 378,  488, 378,
      488,  378,  488, 378,  488, 384,  488, 378,  488, 378,  488, 378,
      488,  378,  488, 378,  488, 1242, 486, 378,  488, 384,  488, 378,
      488,  378,  488, 380,  486, 382,  484, 382,  484, 1264, 464, 1266,
      460,  1272, 462, 378,  488, 406,  460, 1266, 462, 380,  488, 382,
      484,  388,  478, 406,  462, 410,  462, 404,  462, 406,  462, 396,
      470,  406,  462, 404,  462, 406,  460, 404,  462, 410,  462, 404,
      462,  404,  462, 406,  464, 406,  462, 404,  462, 406,  462, 404,
      462,  410,  462, 404,  462, 406,  462, 404,  462, 404,  462, 404,
      462,  406,  460, 406,  462, 410,  462, 404,  462, 1264, 484, 1244,
      486,  382,  482, 382,  486, 382,  486, 378,  486, 382,  488, 9924,
      3554, 1686, 488, 378,  490, 1240, 486, 378,  488, 378,  488, 378,
      488,  378,  488, 382,  484, 386,  486, 378,  488, 382,  486, 378,
      488,  382,  486, 382,  484, 1242, 486, 380,  488, 386,  484, 382,
      486,  380,  486, 382,  486, 380,  486, 380,  486, 1242, 486, 1242,
      484,  1248, 484, 380,  488, 382,  484, 1242, 486, 382,  484, 382,
      484,  382,  484, 382,  486, 386,  484, 382,  486, 382,  484, 382,
      486,  382,  486, 380,  484, 382,  486, 382,  488, 380,  486, 382,
      484,  380,  462, 406,  488, 376,  484, 1246, 482, 1246, 460, 404,
      480,  392,  484, 386,  482, 1244, 484, 382,  484, 382,  484, 1242,
      482,  1244, 484, 382,  464, 410,  460, 404,  462, 406,  462, 404,
      462,  404,  470, 396,  462, 406,  462, 404,  462, 1286, 460, 1268,
      458,  1268, 460, 1266, 460, 1266, 460, 406,  460, 1266, 462, 406,
      460,  1272, 462, 406,  460, 406,  460, 406,  460, 406,  462, 404,
      462,  406,  460, 406,  462, 410,  462, 404,  462, 406,  460, 406,
      460,  406,  462, 404,  462, 406,  460, 406,  460, 410,  462, 406,
      460,  1268, 460, 1266, 460, 404,  460, 406,  462, 406,  460, 406,
      460,  412,  456, 410,  460, 410,  438, 428,  460, 410,  456, 410,
      456,  1272, 436, 1288, 438, 434,  438, 428,  438, 428,  438, 428,
      438,  428,  438, 428,  438, 428,  438, 428,  438, 434,  438, 428,
      438,  428,  438, 428,  438, 428,  438, 428,  440, 428,  438, 428,
      438,  432,  438, 428,  438, 428,  438, 428,  438, 428,  438, 428,
      438,  428,  438, 430,  438, 1294, 438, 428,  438, 428,  438, 428,
      438,  428,  438, 428,  438, 428,  438, 428,  438, 434,  438, 428,
      438,  1288, 438, 1290, 438, 428,  438, 428,  438, 428,  438, 428,
      438,  432,  438, 1288, 438, 1290, 438, 430,  438, 428,  438, 428,
      438,  428,  438, 428,  438, 1292, 438};
  uint8_t expectedState[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x30, 0x32, 0x80, 0xAF, 0x00,
      0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x83};

  irsend.sendRaw(rawData, 439, kPanasonicFreq);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  ASSERT_EQ(PANASONIC_AC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicAcBits, irsend.capture.bits);
  EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
  EXPECT_FALSE(irsend.capture.repeat);
}

// Decode synthetic Panasonic AC message.
TEST(TestDecodePanasonicAC, SyntheticExample) {
  IRsendTest irsend(0);
  IRrecv irrecv(0);
  irsend.begin();

  // Data from Issue #525
  uint8_t expectedState[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x30, 0x32, 0x80, 0xAF, 0x00,
      0x00, 0x06, 0x60, 0x00, 0x00, 0x80, 0x00, 0x06, 0x83};

  irsend.sendPanasonicAC(expectedState);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  ASSERT_EQ(PANASONIC_AC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicAcBits, irsend.capture.bits);
  EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
  EXPECT_FALSE(irsend.capture.repeat);
  EXPECT_EQ(
      "Model: 4 (JKE), Power: Off, Mode: 3 (Cool), Temp: 25C, "
      "Fan: 7 (Auto), Swing(V): 15 (Auto), Quiet: Off, "
      "Powerful: Off, Clock: 00:00, On Timer: Off, Off Timer: Off",
      IRAcUtils::resultAcToString(&irsend.capture));
  stdAc::state_t r, p;
  ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
}

// Tests for general utility functions.
TEST(TestGeneralPanasonic, hasACState) {
  EXPECT_TRUE(hasACState(PANASONIC_AC));
  ASSERT_FALSE(hasACState(PANASONIC));
}

TEST(TestGeneralPanasonic, typeToString) {
  EXPECT_EQ("PANASONIC_AC", typeToString(PANASONIC_AC));
  EXPECT_EQ("PANASONIC", typeToString(PANASONIC));
}

// Decode a problematic Panasonic AC message
TEST(TestDecodePanasonicAC, Issue540) {
  IRsendTest irsend(0);
  IRrecv irrecv(0);
  irsend.begin();

  // Data from Issue #540
  uint16_t rawData[439] = {
      3512, 1714, 466, 408,  466, 1280, 470, 408,  466, 412,  466, 408,
      466,  412,  462, 412,  466, 414,  466, 408,  466, 412,  462, 412,
      466,  412,  466, 408,  466, 1280, 466, 412,  462, 416,  462, 412,
      466,  408,  466, 412,  462, 416,  462, 412,  462, 1282, 462, 1284,
      462,  1288, 466, 412,  462, 412,  462, 1284, 462, 416,  440, 438,
      462,  412,  462, 412,  462, 416,  466, 412,  462, 412,  462, 412,
      440,  442,  462, 412,  462, 412,  460, 418,  462, 416,  462, 412,
      462,  418,  462, 412,  462, 416,  462, 412,  436, 442,  462, 412,
      460,  418,  462, 416,  462, 412,  460, 412,  462, 420,  436, 438,
      462,  412,  462, 416,  432, 448,  436, 438,  436, 1310, 436, 1310,
      462,  420,  432, 442,  436, 438,  462, 416,  432, 444,  432, 10008,
      3480, 1744, 492, 382,  492, 1254, 492, 386,  488, 390,  492, 382,
      492,  386,  488, 386,  492, 386,  492, 386,  488, 386,  488, 386,
      492,  386,  492, 382,  492, 1258, 488, 386,  488, 390,  492, 386,
      488,  386,  488, 386,  492, 390,  488, 386,  488, 1256, 488, 1258,
      488,  1262, 488, 390,  488, 386,  488, 1258, 488, 390,  488, 392,
      488,  386,  488, 386,  488, 394,  488, 386,  488, 386,  488, 390,
      488,  390,  488, 386,  488, 390,  462, 412,  488, 390,  462, 1282,
      488,  390,  456, 416,  458, 1292, 456, 1288, 488, 1258, 488, 392,
      456,  422,  488, 390,  484, 392,  484, 1262, 458, 420,  484, 1262,
      482,  1262, 488, 392,  484, 394,  484, 416,  436, 442,  458, 416,
      458,  422,  430, 448,  432, 442,  458, 416,  458, 1296, 432, 1314,
      458,  1288, 432, 1312, 432, 1322, 428, 446,  428, 1318, 432, 442,
      432,  1318, 432, 1318, 428, 446,  428, 1318, 428, 1322, 430, 448,
      426,  448,  428, 452,  426, 452,  426, 448,  428, 472,  400, 478,
      402,  478,  402, 472,  402, 476,  402, 472,  402, 478,  402, 472,
      402,  1348, 398, 1348, 398, 1352, 398, 508,  370, 478,  398, 476,
      398,  512,  366, 508,  370, 502,  372, 508,  340, 538,  372, 504,
      344,  1400, 344, 1400, 346, 1434, 314, 560,  316, 588,  290, 560,
      314,  564,  396, 400,  474, 400,  480, 394,  480, 404,  474, 400,
      454,  446,  454, 426,  448, 430,  424, 450,  428, 452,  448, 426,
      426,  452,  424, 1322, 454, 426,  450, 424,  426, 452,  428, 452,
      450,  424,  428, 446,  426, 1322, 454, 426,  422, 450,  454, 426,
      448,  430,  454, 426,  448, 426,  428, 446,  454, 430,  454, 422,
      452,  424,  424, 452,  452, 430,  424, 452,  452, 426,  448, 426,
      426,  456,  448, 426,  448, 1296, 424, 1322, 426, 1326, 450, 1270,
      478,  422,  454, 424,  424, 450,  454};
  uint8_t expectedState[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x39, 0x34, 0x80, 0xAF, 0x0D,
      0x00, 0x0E, 0xE0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x1E};

  irsend.sendRaw(rawData, 439, kPanasonicFreq);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  ASSERT_EQ(PANASONIC_AC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicAcBits, irsend.capture.bits);
  EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
  EXPECT_FALSE(irsend.capture.repeat);
  IRPanasonicAc pana(0);
  pana.setRaw(irsend.capture.state);
  // TODO(crankyoldgit): Try to figure out what model this should be.
  EXPECT_EQ(
      "Model: 0 (UNKNOWN), Power: On, Mode: 3 (Cool), Temp: 26C, "
      "Fan: 7 (Auto), Swing(V): 15 (Auto), "
      "Swing(H): 13 (Auto), Quiet: Off, Powerful: Off, "
      "Clock: 00:00, On Timer: Off, Off Timer: Off",
      pana.toString());
}

TEST(TestIRPanasonicAcClass, TimeBasics) {
  EXPECT_EQ(0x186, IRPanasonicAc::encodeTime(6, 30));
  EXPECT_EQ(0x3CA, IRPanasonicAc::encodeTime(16, 10));
  EXPECT_EQ(0x448, IRPanasonicAc::encodeTime(18, 16));
  EXPECT_EQ(0, IRPanasonicAc::encodeTime(0, 0));
  EXPECT_EQ(kPanasonicAcTimeMax, IRPanasonicAc::encodeTime(23, 59));
  EXPECT_EQ(kPanasonicAcTimeMax, IRPanasonicAc::encodeTime(23, 59));
  EXPECT_EQ(kPanasonicAcTimeMax, IRPanasonicAc::encodeTime(25, 72));
  EXPECT_EQ(59, IRPanasonicAc::encodeTime(0, 72));
  EXPECT_EQ(23 * 60, IRPanasonicAc::encodeTime(27, 0));
}

TEST(TestIRPanasonicAcClass, TimersAndClock) {
  IRPanasonicAc pana(0);
  // Data from Issue #544
  uint8_t state[27] = {0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
                       0x20, 0xE0, 0x04, 0x00, 0x4E, 0x2E, 0x80, 0xAF, 0x00,
                       0xCA, 0x6B, 0x98, 0x10, 0x00, 0x01, 0x48, 0x04, 0xDB};
  pana.setRaw(state);
  EXPECT_TRUE(pana.isOnTimerEnabled());
  EXPECT_EQ(0x3CA, pana.getOnTimer());
  EXPECT_TRUE(pana.isOffTimerEnabled());
  EXPECT_EQ(0x186, pana.getOffTimer());
  EXPECT_EQ(0x448, pana.getClock());

  pana.cancelOnTimer();
  EXPECT_FALSE(pana.isOnTimerEnabled());
  EXPECT_EQ(0, pana.getOnTimer());
  EXPECT_TRUE(pana.isOffTimerEnabled());
  EXPECT_EQ(0x186, pana.getOffTimer());
  EXPECT_EQ(0x448, pana.getClock());

  pana.cancelOffTimer();
  EXPECT_FALSE(pana.isOnTimerEnabled());
  EXPECT_EQ(0, pana.getOnTimer());
  EXPECT_FALSE(pana.isOffTimerEnabled());
  EXPECT_EQ(0, pana.getOffTimer());
  EXPECT_EQ(0x448, pana.getClock());

  pana.setOnTimer(7 * 60 + 50);
  EXPECT_TRUE(pana.isOnTimerEnabled());
  EXPECT_EQ(7 * 60 + 50, pana.getOnTimer());
  EXPECT_FALSE(pana.isOffTimerEnabled());
  EXPECT_EQ(0, pana.getOffTimer());
  EXPECT_EQ(0x448, pana.getClock());

  pana.setOnTimer(7 * 60 + 57);  // It should round down.
  EXPECT_EQ(7 * 60 + 50, pana.getOnTimer());
  pana.setOnTimer(28 * 60);  // It should round down.
  EXPECT_EQ(kPanasonicAcTimeMax - 9, pana.getOnTimer());
  pana.setOnTimer(kPanasonicAcTimeSpecial);
  EXPECT_EQ(0, pana.getOnTimer());

  pana.setOnTimer(7 * 60 + 50);
  pana.setOffTimer(19 * 60 + 30);

  EXPECT_TRUE(pana.isOnTimerEnabled());
  EXPECT_EQ(7 * 60 + 50, pana.getOnTimer());
  EXPECT_TRUE(pana.isOffTimerEnabled());
  EXPECT_EQ(19 * 60 + 30, pana.getOffTimer());
  EXPECT_EQ(0x448, pana.getClock());

  pana.setOffTimer(19 * 60 + 57);  // It should round down.
  EXPECT_EQ(19 * 60 + 50, pana.getOffTimer());
  pana.setOffTimer(28 * 60);  // It should round down.
  EXPECT_EQ(kPanasonicAcTimeMax - 9, pana.getOffTimer());
  pana.setOffTimer(kPanasonicAcTimeSpecial);
  EXPECT_EQ(0, pana.getOffTimer());

  pana.setClock(0);
  EXPECT_EQ(0, pana.getClock());
  pana.setClock(kPanasonicAcTimeMax);
  EXPECT_EQ(kPanasonicAcTimeMax, pana.getClock());
  pana.setClock(kPanasonicAcTimeMax - 1);
  EXPECT_EQ(kPanasonicAcTimeMax - 1, pana.getClock());
  pana.setClock(kPanasonicAcTimeMax + 1);
  EXPECT_EQ(kPanasonicAcTimeMax, pana.getClock());
  pana.setClock(kPanasonicAcTimeSpecial);
  EXPECT_EQ(0, pana.getClock());
}

// Decode a real short Panasonic AC message
TEST(TestDecodePanasonicAC, RealExampleOfShortMessage) {
  IRsendTest irsend(0);
  IRrecv irrecv(0);
  irsend.begin();

  // Data from Issue #544 (Odour Wash)
  uint16_t rawData[263] = {
      3496, 1734, 506, 366,  448, 1294, 504, 368,  498, 374,  452, 418,
      448,  424,  444, 428,  450, 422,  446, 426,  450, 420,  448, 424,
      452,  418,  448, 422,  444, 1300, 498, 374,  504, 368,  448, 424,
      452,  418,  448, 424,  444, 428,  450, 422,  446, 1296, 500, 1242,
      502,  1242, 504, 368,  498, 374,  452, 1292, 504, 366,  450, 422,
      444,  426,  450, 420,  446, 424,  452, 418,  448, 424,  444, 428,
      450,  422,  444, 426,  450, 420,  446, 424,  452, 418,  448, 422,
      444,  428,  450, 422,  446, 426,  452, 420,  446, 426,  452, 418,
      448,  424,  442, 428,  448, 422,  444, 426,  450, 420,  446, 426,
      452,  418,  448, 424,  444, 428,  450, 422,  444, 1298, 500, 1244,
      500,  372,  444, 428,  450, 422,  446, 426,  452, 418,  448, 10020,
      3500, 1732, 498, 372,  452, 1290, 506, 366,  450, 422,  446, 426,
      452,  420,  448, 424,  452, 418,  448, 422,  444, 426,  450, 420,
      446,  426,  452, 420,  446, 1296, 500, 370,  444, 428,  450, 422,
      446,  426,  452, 420,  446, 424,  442, 428,  448, 1294, 502, 1240,
      504,  1238, 506, 366,  448, 422,  444, 1298, 498, 374,  452, 418,
      448,  424,  444, 428,  450, 422,  446, 426,  450, 420,  446, 424,
      452,  418,  448, 422,  444, 428,  450, 420,  446, 1298, 498, 1244,
      500,  1242, 502, 368,  446, 1298, 500, 1244, 500, 372,  444, 428,
      450,  1292, 504, 368,  446, 1296, 502, 370,  444, 426,  452, 1290,
      504,  1238, 506, 366,  450, 422,  446, 1298, 498, 1246, 500, 372,
      444,  428,  450, 1294, 452, 420,  446, 1296, 448, 422,  444};  // UNKNOWN
                                                                     // 1FB51F79

  uint8_t expectedState[kPanasonicAcStateShortLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06,
      0x02, 0x20, 0xE0, 0x04, 0x80, 0x9B, 0x32, 0x53};

  irsend.sendRaw(rawData, 263, kPanasonicFreq);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  ASSERT_EQ(PANASONIC_AC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicAcShortBits, irsend.capture.bits);
  EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
}

// Create and decode a short Panasonic AC message
TEST(TestDecodePanasonicAC, SyntheticShortMessage) {
  IRsendTest irsend(0);
  IRrecv irrecv(0);
  irsend.begin();

  uint8_t odourWash[kPanasonicAcStateShortLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06,
      0x02, 0x20, 0xE0, 0x04, 0x80, 0x9B, 0x32, 0x53};

  irsend.sendPanasonicAC(odourWash, kPanasonicAcStateShortLength);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  ASSERT_EQ(PANASONIC_AC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicAcShortBits, irsend.capture.bits);
  EXPECT_STATE_EQ(odourWash, irsend.capture.state, irsend.capture.bits);
}
//
// Test for CKP model / see issue #544
TEST(TestDecodePanasonicAC, CkpModelSpecifics) {
  IRsendTest irsend(0);
  IRrecv irrecv(0);
  irsend.begin();

  // Data from Issue #544
  uint8_t ckpPowerfulOn[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x4E, 0x2E, 0x80, 0xAF, 0x00,
      0x00, 0x0E, 0xE0, 0x11, 0x00, 0x01, 0x00, 0x06, 0xB7};
  uint8_t ckpQuietOn[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x4E, 0x2E, 0x80, 0xAF, 0x00,
      0x00, 0x0E, 0xE0, 0x30, 0x00, 0x01, 0x00, 0x06, 0xD6};

  irsend.sendPanasonicAC(ckpPowerfulOn);
  irsend.makeDecodeResult();

  ASSERT_TRUE(irrecv.decode(&irsend.capture));
  ASSERT_EQ(PANASONIC_AC, irsend.capture.decode_type);
  EXPECT_EQ(kPanasonicAcBits, irsend.capture.bits);
  EXPECT_STATE_EQ(ckpPowerfulOn, irsend.capture.state, irsend.capture.bits);
  EXPECT_FALSE(irsend.capture.repeat);

  IRPanasonicAc pana(0);
  pana.setRaw(irsend.capture.state);
  EXPECT_EQ(
      "Model: 5 (CKP), Power: Off, Mode: 4 (Heat), Temp: 23C, "
      "Fan: 7 (Auto), Swing(V): 15 (Auto), Quiet: Off, "
      "Powerful: On, Clock: 00:00, On Timer: 00:00, Off Timer: 00:00",
      pana.toString());

  pana.setQuiet(true);
  EXPECT_FALSE(pana.getPowerful());
  EXPECT_TRUE(pana.getQuiet());
  EXPECT_EQ(kPanasonicCkp, pana.getModel());
  EXPECT_STATE_EQ(ckpQuietOn, pana.getRaw(), kPanasonicAcBits);

  pana.setPowerful(true);
  EXPECT_TRUE(pana.getPowerful());
  EXPECT_FALSE(pana.getQuiet());
  EXPECT_EQ(kPanasonicCkp, pana.getModel());
  EXPECT_STATE_EQ(ckpPowerfulOn, pana.getRaw(), kPanasonicAcBits);
}

TEST(TestIRPanasonicAcClass, toCommon) {
  IRPanasonicAc ac(0);
  ac.setModel(panasonic_ac_remote_model_t::kPanasonicDke);
  ac.setPower(true);
  ac.setMode(kPanasonicAcCool);
  ac.setTemp(20);
  ac.setFan(kPanasonicAcFanMax);
  ac.setSwingVertical(kPanasonicAcSwingVAuto);
  ac.setSwingHorizontal(kPanasonicAcSwingHMiddle);
  ac.setPowerful(true);
  ac.setQuiet(false);
  // Now test it.
  ASSERT_EQ(decode_type_t::PANASONIC_AC, ac.toCommon().protocol);
  ASSERT_EQ(panasonic_ac_remote_model_t::kPanasonicDke, ac.toCommon().model);
  ASSERT_TRUE(ac.toCommon().power);
  ASSERT_TRUE(ac.toCommon().celsius);
  ASSERT_EQ(20, ac.toCommon().degrees);
  ASSERT_EQ(stdAc::opmode_t::kCool, ac.toCommon().mode);
  ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed);
  ASSERT_EQ(stdAc::swingv_t::kAuto, ac.toCommon().swingv);
  ASSERT_EQ(stdAc::swingh_t::kMiddle, ac.toCommon().swingh);
  ASSERT_TRUE(ac.toCommon().turbo);
  ASSERT_FALSE(ac.toCommon().quiet);
  // Unsupported.
  ASSERT_FALSE(ac.toCommon().econo);
  ASSERT_FALSE(ac.toCommon().clean);
  ASSERT_FALSE(ac.toCommon().light);
  ASSERT_FALSE(ac.toCommon().filter);
  ASSERT_FALSE(ac.toCommon().beep);
  ASSERT_EQ(-1, ac.toCommon().sleep);
  ASSERT_EQ(-1, ac.toCommon().clock);

  // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/921#issuecomment-532267240
  ac.setSwingVertical(kPanasonicAcSwingVLow);
  ASSERT_EQ(stdAc::swingv_t::kLow, ac.toCommon().swingv);
  // Real data.
  uint8_t swingVMiddle[27] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02, 0x20, 0xE0, 0x04,
      0x00, 0x39, 0x32, 0x80, 0x73, 0x00, 0x00, 0x0E, 0xE0, 0x00, 0x00, 0x89,
      0x00, 0x00, 0xDB};
  ac.setRaw(swingVMiddle);
  ASSERT_EQ(stdAc::swingv_t::kMiddle, ac.toCommon().swingv);
  ASSERT_EQ(kPanasonicAcSwingVMiddle,
            IRPanasonicAc::convertSwingV(stdAc::swingv_t::kMiddle));
}

//
// Test for DKE/DKW model / see issue #1024
TEST(TestDecodePanasonicAC, DkeIonRealMessages) {
  IRsendTest irsend(0);
  IRrecv irrecv(0);
  irsend.begin();

  // Data from Issue #1024
  // 0x0220E004000000060220E004004F3280AF0D000660000001000630
  uint8_t dkeIonOff[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x4F, 0x32, 0x80, 0xAF, 0x0D,
      0x00, 0x06, 0x60, 0x00, 0x00, 0x01, 0x00, 0x06, 0x30};

  // 0x0220E004000000060220E004004F3280AF0D000660000101000631
  uint8_t dkeIonOn[kPanasonicAcStateLength] = {
      0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02,
      0x20, 0xE0, 0x04, 0x00, 0x4F, 0x32, 0x80, 0xAF, 0x0D,
      0x00, 0x06, 0x60, 0x00, 0x01, 0x01, 0x00, 0x06, 0x31};

  IRPanasonicAc ac(0);
  ac.setRaw(dkeIonOff);
  EXPECT_EQ(
      "Model: 3 (DKE), Power: On, Mode: 4 (Heat), Temp: 25C, Fan: 7 (Auto), "
      "Swing(V): 15 (Auto), Swing(H): 13 (Auto), Quiet: Off, Powerful: Off, "
      "Ion: Off, Clock: 00:00, On Timer: 00:00, Off Timer: 00:00",
      ac.toString());
  ac.setRaw(dkeIonOn);
  EXPECT_EQ(
      "Model: 3 (DKE), Power: On, Mode: 4 (Heat), Temp: 25C, Fan: 7 (Auto), "
      "Swing(V): 15 (Auto), Swing(H): 13 (Auto), Quiet: Off, Powerful: Off, "
      "Ion: On, Clock: 00:00, On Timer: 00:00, Off Timer: 00:00",
      ac.toString());
}
