/* Minimal Morse encoder for the KE0WPC beacon.
 * Apache License 2.0 */

#include "morse.h"

/* ITU timing, in dot units:
 *   dot          1 on
 *   dash         3 on
 *   intra-char   1 off  (between elements of one character)
 *   inter-char   3 off  (total, so 2 more after the intra gap)
 *   inter-word   7 off  (total, so 4 more after the inter-char gap)
 */

static const char *const kLetters[26] = {
    ".-",    /* A */  "-...",  /* B */  "-.-.",  /* C */  "-..",   /* D */
    ".",     /* E */  "..-.",  /* F */  "--.",   /* G */  "....",  /* H */
    "..",    /* I */  ".---",  /* J */  "-.-",   /* K */  ".-..",  /* L */
    "--",    /* M */  "-.",    /* N */  "---",   /* O */  ".--.",  /* P */
    "--.-",  /* Q */  ".-.",   /* R */  "...",   /* S */  "-",     /* T */
    "..-",   /* U */  "...-",  /* V */  ".--",   /* W */  "-..-",  /* X */
    "-.--",  /* Y */  "--..",  /* Z */
};

static const char *const kDigits[10] = {
    "-----", /* 0 */  ".----", /* 1 */  "..---", /* 2 */  "...--", /* 3 */
    "....-", /* 4 */  ".....", /* 5 */  "-....", /* 6 */  "--...", /* 7 */
    "---..", /* 8 */  "----.", /* 9 */
};

const char *MORSE_Pattern(char c)
{
    if (c >= 'a' && c <= 'z')
        c = (char)(c - 'a' + 'A');

    if (c >= 'A' && c <= 'Z')
        return kLetters[c - 'A'];

    if (c >= '0' && c <= '9')
        return kDigits[c - '0'];

    switch (c) {
        case '/':  return "-..-.";
        case '.':  return ".-.-.-";
        case ',':  return "--..--";
        case '?':  return "..--..";
        case '-':  return "-....-";
        case '=':  return "-...-";   /* BT, the beacon break sign */
        case '+':  return ".-.-.";   /* AR */
        case '\'': return ".----.";
        case '(':  return "-.--.";
        case ')':  return "-.--.-";
        case ':':  return "---...";
        case '@':  return ".--.-.";
        default:   return (const char *)0;
    }
}

/* Returns false if the caller asked us to stop. */
static bool wait(const morse_io_t *io, uint32_t ms)
{
    if (io->aborted && io->aborted())
        return false;
    io->delay_ms(ms);
    return !(io->aborted && io->aborted());
}

bool MORSE_SendString(const morse_io_t *io, const char *text, uint16_t wpm)
{
    if (wpm == 0)
        wpm = 12;

    const uint32_t dot = 1200u / wpm;   /* PARIS standard, milliseconds */

    bool first_char = true;

    for (const char *p = text; *p != '\0'; p++) {

        if (*p == ' ') {
            /* A space means word gap. We have already emitted the 1-dot
             * intra gap after the last element, so add 6 more for 7 total. */
            if (!wait(io, dot * 6))
                return false;
            first_char = true;
            continue;
        }

        const char *pattern = MORSE_Pattern(*p);
        if (pattern == (const char *)0)
            continue;

        if (!first_char) {
            /* 1 dot already elapsed after the previous element, add 2. */
            if (!wait(io, dot * 2))
                return false;
        }
        first_char = false;

        for (const char *e = pattern; *e != '\0'; e++) {

            if (io->aborted && io->aborted())
                return false;

            uint32_t on_ms = (*e == '-') ? dot * 3 : dot;

            /* The keying envelope itself is carrier, so charge it against
             * the element rather than letting it stretch every dit. */
            on_ms = (on_ms > io->key_overhead_ms)
                      ? on_ms - io->key_overhead_ms : 1u;

            io->key_down();
            const bool ok_on = wait(io, on_ms);
            io->key_up();
            if (!ok_on)
                return false;

            /* intra-element gap */
            if (!wait(io, dot))
                return false;
        }
    }

    return true;
}
