# Copyright 2024 Allen Synthesis
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from europi import *
from europi_script import EuroPiScript

BLANK_LETTER = "."

morse_alphabet = {
    BLANK_LETTER: "",
    "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": "--..",
    "0": "-----",
    "1": ".----",
    "2": "..---",
    "3": "...--",
    "4": "....-",
    "5": ".....",
    "6": "-....",
    "7": "--...",
    "8": "---..",
    "9": "----.",
}

valid_letters = [
    BLANK_LETTER,
    "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",
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
]

class Morse(EuroPiScript):
    dot_high_out = cv1
    dot_high_latched_out = cv2
    dot_low_out = cv4
    dot_low_latched_out = cv5
    end_of_letter_out = cv3
    end_of_word_out = cv6

    def __init__(self):
        super().__init__()

        default_letters = [BLANK_LETTER] * 16
        saved_cfg = self.load_state_json()
        self.letters = saved_cfg.get("letters", default_letters)

        # the index of the letter the user is hovering over
        self.hover_letter = 0

        self.ui_dirty = True
        self.gate_recvd = False
        self.sequence_counter = 0

        @b1.handler
        def on_b1_press():
            # advance the letter 1 place down
            idx = valid_letters.index(self.letters[self.hover_letter])
            idx = (idx - 1) % len(valid_letters)
            self.letters[self.hover_letter] = valid_letters[idx]
            self.ui_dirty = True

        @b2.handler
        def on_b2_press():
            # advance the letter 1 place up
            idx = valid_letters.index(self.letters[self.hover_letter])
            idx = (idx + 1) % len(valid_letters)
            self.letters[self.hover_letter] = valid_letters[idx]
            self.ui_dirty = True

        @din.handler
        def on_din_rise():
            self.gate_recvd = True

        @din.handler_falling
        def on_din_fall():
            # turn off the unlatched CVs
            self.dot_low_out.off()
            self.dot_high_out.off()
            self.end_of_letter_out.off()
            self.end_of_word_out.off()

    def save_state(self):
        cfg = {
            "letters": self.letters
        }
        self.save_state_json(cfg)

    def expand_sequence(self):
        """
        Expand the more representation to a boolean sequence

        We treat "." as True and "-" as False
        """
        seq = []
        for ch in self.letters:
            if ch is not None:
                for bit in morse_alphabet[ch]:
                    if bit == ".":
                        seq.append(True)
                    else:
                        seq.append(False)
        return seq

    def get_letter_ends(self):
        seq = []
        n = 0
        for ch in self.letters:
            if ch is not None:
                n += len(morse_alphabet[ch])
                seq.append(n)
        return seq

    def draw(self):
        top = OLED_HEIGHT // 2 - CHAR_HEIGHT // 2
        left = OLED_WIDTH // 2 - (CHAR_WIDTH * len(self.letters)) // 2

        oled.fill(0)

        w = ''
        for ch in self.letters:
            if ch is None:
                w = w + "."
            else:
                w = w + ch

        oled.text(
            w,
            left,
            top,
            1
        )

        oled.line(
            left + self.hover_letter * CHAR_WIDTH,
            top + CHAR_HEIGHT + 1,
            left + (self.hover_letter + 1) * CHAR_WIDTH,
            top + CHAR_HEIGHT + 1,
            1
        )

        oled.show()

    def main(self):
        while True:
            letter = int(k2.percent() * len(self.letters))
            if letter == len(self.letters):
                # .percent() can return 1, so avoid out-of-bounds issues
                letter -= 1
            if letter != self.hover_letter:
                self.hover_letter = letter
                self.ui_dirty = True

            if self.ui_dirty:
                self.ui_dirty = False
                self.draw()

            sequence = self.expand_sequence()
            end_of_letters = self.get_letter_ends()
            if len(sequence) == 0:
                turn_off_all_cvs()
                continue

            # kick out if there's no gate to process
            if not self.gate_recvd:
                continue

            self.gate_recvd = False

            # if we've modified the sequence, check for overflow
            if self.sequence_counter >= len(sequence):
                self.sequence_counter = 0

            # play the sequence
            if sequence[self.sequence_counter]:
                self.dot_high_out.on()
                self.dot_high_latched_out.on()
                self.dot_low_out.off()
                self.dot_low_latched_out.off()
            else:
                self.dot_high_out.off()
                self.dot_high_latched_out.off()
                self.dot_low_out.on()
                self.dot_low_latched_out.on()

            # increment
            self.sequence_counter += 1

            # check for end of letter
            if self.sequence_counter in end_of_letters:
                self.end_of_letter_out.on()

            # check for end of word
            if self.sequence_counter == len(sequence):
                self.end_of_word_out.on()


if __name__ == "__main__":
    Morse().main()
