#!/usr/bin/python3
"""Define a PGPWords object inherited from bytearray.

Adding initialization via hex-, or pgp-word-string,
adding .hex() method and
overriding __str__

Mainline code:

Convert pgp words to hex strings and vice versa.
Example:

  $ pypgpwords.py DEAD 1337
  tactics perceptive Aztec consensus

or

  $ pypgpwords.py absurd bodyguard baboon unicorn
  0116 14EC

moki@posteo.de
"""
from __future__ import print_function
import sys

SEPARATOR = " "

EVEN = ("aardvark",
        "absurd",
        "accrue",
        "acme",
        "adrift",
        "adult",
        "afflict",
        "ahead",
        "aimless",
        "Algol",
        "allow",
        "alone",
        "ammo",
        "ancient",
        "apple",
        "artist",
        "assume",
        "Athens",
        "atlas",
        "Aztec",
        "baboon",
        "backfield",
        "backward",
        "banjo",
        "beaming",
        "bedlamp",
        "beehive",
        "beeswax",
        "befriend",
        "Belfast",
        "berserk",
        "billiard",
        "bison",
        "blackjack",
        "blockade",
        "blowtorch",
        "bluebird",
        "bombast",
        "bookshelf",
        "brackish",
        "breadline",
        "breakup",
        "brickyard",
        "briefcase",
        "Burbank",
        "button",
        "buzzard",
        "cement",
        "chairlift",
        "chatter",
        "checkup",
        "chisel",
        "choking",
        "chopper",
        "Christmas",
        "clamshell",
        "classic",
        "classroom",
        "cleanup",
        "clockwork",
        "cobra",
        "commence",
        "concert",
        "cowbell",
        "crackdown",
        "cranky",
        "crowfoot",
        "crucial",
        "crumpled",
        "crusade",
        "cubic",
        "dashboard",
        "deadbolt",
        "deckhand",
        "dogsled",
        "dragnet",
        "drainage",
        "dreadful",
        "drifter",
        "dropper",
        "drumbeat",
        "drunken",
        "Dupont",
        "dwelling",
        "eating",
        "edict",
        "egghead",
        "eightball",
        "endorse",
        "endow",
        "enlist",
        "erase",
        "escape",
        "exceed",
        "eyeglass",
        "eyetooth",
        "facial",
        "fallout",
        "flagpole",
        "flatfoot",
        "flytrap",
        "fracture",
        "framework",
        "freedom",
        "frighten",
        "gazelle",
        "Geiger",
        "glitter",
        "glucose",
        "goggles",
        "goldfish",
        "gremlin",
        "guidance",
        "hamlet",
        "highchair",
        "hockey",
        "indoors",
        "indulge",
        "inverse",
        "involve",
        "island",
        "jawbone",
        "keyboard",
        "kickoff",
        "kiwi",
        "klaxon",
        "locale",
        "lockup",
        "merit",
        "minnow",
        "miser",
        "Mohawk",
        "mural",
        "music",
        "necklace",
        "Neptune",
        "newborn",
        "nightbird",
        "Oakland",
        "obtuse",
        "offload",
        "optic",
        "orca",
        "payday",
        "peachy",
        "pheasant",
        "physique",
        "playhouse",
        "Pluto",
        "preclude",
        "prefer",
        "preshrunk",
        "printer",
        "prowler",
        "pupil",
        "puppy",
        "python",
        "quadrant",
        "quiver",
        "quota",
        "ragtime",
        "ratchet",
        "rebirth",
        "reform",
        "regain",
        "reindeer",
        "rematch",
        "repay",
        "retouch",
        "revenge",
        "reward",
        "rhythm",
        "ribcage",
        "ringbolt",
        "robust",
        "rocker",
        "ruffled",
        "sailboat",
        "sawdust",
        "scallion",
        "scenic",
        "scorecard",
        "Scotland",
        "seabird",
        "select",
        "sentence",
        "shadow",
        "shamrock",
        "showgirl",
        "skullcap",
        "skydive",
        "slingshot",
        "slowdown",
        "snapline",
        "snapshot",
        "snowcap",
        "snowslide",
        "solo",
        "southward",
        "soybean",
        "spaniel",
        "spearhead",
        "spellbind",
        "spheroid",
        "spigot",
        "spindle",
        "spyglass",
        "stagehand",
        "stagnate",
        "stairway",
        "standard",
        "stapler",
        "steamship",
        "sterling",
        "stockman",
        "stopwatch",
        "stormy",
        "sugar",
        "surmount",
        "suspense",
        "sweatband",
        "swelter",
        "tactics",
        "talon",
        "tapeworm",
        "tempest",
        "tiger",
        "tissue",
        "tonic",
        "topmost",
        "tracker",
        "transit",
        "trauma",
        "treadmill",
        "Trojan",
        "trouble",
        "tumor",
        "tunnel",
        "tycoon",
        "uncut",
        "unearth",
        "unwind",
        "uproot",
        "upset",
        "upshot",
        "vapor",
        "village",
        "virus",
        "Vulcan",
        "waffle",
        "wallet",
        "watchword",
        "wayside",
        "willow",
        "woodlark",
        "Zulu")

ODD = ("adroitness",
       "adviser",
       "aftermath",
       "aggregate",
       "alkali",
       "almighty",
       "amulet",
       "amusement",
       "antenna",
       "applicant",
       "Apollo",
       "armistice",
       "article",
       "asteroid",
       "Atlantic",
       "atmosphere",
       "autopsy",
       "Babylon",
       "backwater",
       "barbecue",
       "belowground",
       "bifocals",
       "bodyguard",
       "bookseller",
       "borderline",
       "bottomless",
       "Bradbury",
       "bravado",
       "Brazilian",
       "breakaway",
       "Burlington",
       "businessman",
       "butterfat",
       "Camelot",
       "candidate",
       "cannonball",
       "Capricorn",
       "caravan",
       "caretaker",
       "celebrate",
       "cellulose",
       "certify",
       "chambermaid",
       "Cherokee",
       "Chicago",
       "clergyman",
       "coherence",
       "combustion",
       "commando",
       "company",
       "component",
       "concurrent",
       "confidence",
       "conformist",
       "congregate",
       "consensus",
       "consulting",
       "corporate",
       "corrosion",
       "councilman",
       "crossover",
       "crucifix",
       "cumbersome",
       "customer",
       "Dakota",
       "decadence",
       "December",
       "decimal",
       "designing",
       "detector",
       "detergent",
       "determine",
       "dictator",
       "dinosaur",
       "direction",
       "disable",
       "disbelief",
       "disruptive",
       "distortion",
       "document",
       "embezzle",
       "enchanting",
       "enrollment",
       "enterprise",
       "equation",
       "equipment",
       "escapade",
       "Eskimo",
       "everyday",
       "examine",
       "existence",
       "exodus",
       "fascinate",
       "filament",
       "finicky",
       "forever",
       "fortitude",
       "frequency",
       "gadgetry",
       "Galveston",
       "getaway",
       "glossary",
       "gossamer",
       "graduate",
       "gravity",
       "guitarist",
       "hamburger",
       "Hamilton",
       "handiwork",
       "hazardous",
       "headwaters",
       "hemisphere",
       "hesitate",
       "hideaway",
       "holiness",
       "hurricane",
       "hydraulic",
       "impartial",
       "impetus",
       "inception",
       "indigo",
       "inertia",
       "infancy",
       "inferno",
       "informant",
       "insincere",
       "insurgent",
       "integrate",
       "intention",
       "inventive",
       "Istanbul",
       "Jamaica",
       "Jupiter",
       "leprosy",
       "letterhead",
       "liberty",
       "maritime",
       "matchmaker",
       "maverick",
       "Medusa",
       "megaton",
       "microscope",
       "microwave",
       "midsummer",
       "millionaire",
       "miracle",
       "misnomer",
       "molasses",
       "molecule",
       "Montana",
       "monument",
       "mosquito",
       "narrative",
       "nebula",
       "newsletter",
       "Norwegian",
       "October",
       "Ohio",
       "onlooker",
       "opulent",
       "Orlando",
       "outfielder",
       "Pacific",
       "pandemic",
       "Pandora",
       "paperweight",
       "paragon",
       "paragraph",
       "paramount",
       "passenger",
       "pedigree",
       "Pegasus",
       "penetrate",
       "perceptive",
       "performance",
       "pharmacy",
       "phonetic",
       "photograph",
       "pioneer",
       "pocketful",
       "politeness",
       "positive",
       "potato",
       "processor",
       "provincial",
       "proximate",
       "puberty",
       "publisher",
       "pyramid",
       "quantity",
       "racketeer",
       "rebellion",
       "recipe",
       "recover",
       "repellent",
       "replica",
       "reproduce",
       "resistor",
       "responsive",
       "retraction",
       "retrieval",
       "retrospect",
       "revenue",
       "revival",
       "revolver",
       "sandalwood",
       "sardonic",
       "Saturday",
       "savagery",
       "scavenger",
       "sensation",
       "sociable",
       "souvenir",
       "specialist",
       "speculate",
       "stethoscope",
       "stupendous",
       "supportive",
       "surrender",
       "suspicious",
       "sympathy",
       "tambourine",
       "telephone",
       "therapist",
       "tobacco",
       "tolerance",
       "tomorrow",
       "torpedo",
       "tradition",
       "travesty",
       "trombonist",
       "truncated",
       "typewriter",
       "ultimate",
       "undaunted",
       "underfoot",
       "unicorn",
       "unify",
       "universe",
       "unravel",
       "upcoming",
       "vacancy",
       "vagabond",
       "vertigo",
       "Virginia",
       "visitor",
       "vocalist",
       "voyager",
       "warranty",
       "Waterloo",
       "whimsical",
       "Wichita",
       "Wilmington",
       "Wyoming",
       "yesteryear",
       "Yucatan")


class InvalidWordError(ValueError):
    pass


def words_to_int(word_iter, odd=False):
    """Generator yielding integer indices for each word in word_iter.

    :param word_iter: iterable of pgp words
    :type word_iter: iterable
    :param odd: start with odd word list
    :type odd: boolean
    :return: integer
    :rtype: generator
    """
    for word in word_iter:
        try:
            yield (ODD if odd else EVEN).index(word)
        except ValueError:
            msg = "not in {} word list: '{}'"
            raise InvalidWordError(msg.format("odd" if odd else "even", word))
        # toggle odd/even
        odd = not odd


def ints_to_word(int_iter, odd=False):
    """Generator yielding PGP words for each byte/int in int_iter.

    :param int_iter: iterable of integers between 0 and 255
    :type int_iter: iterable
    :param odd: start with odd word list
    :type odd: boolean
    :return: pgp words
    :rtype: generator
    """
    for idx in int_iter:
        yield (ODD if odd else EVEN)[idx]
        # toggle odd/even
        odd = not odd


class PGPWords(bytearray):
    """Inherits from bytearray. Add .hex() method and overwrite __str__"""
    def __init__(self, source, **kwargs):
        """Initiate bytearray. Added initialization styles:
        E.g.:
            p = PGPWords("absurd bodyguard baboon", encoding="pgp-words")
            p = PGPWords("DEAD 1337", encoding="hex")

        """
        enc = kwargs.get("encoding")

        if enc == "pgp-words":
            kwargs.pop("encoding")
            source = words_to_int(source.split(SEPARATOR), **kwargs)
            kwargs = {}
        elif enc == "hex" or source.startswith('0x'):
            kwargs.pop("encoding")
            tmp = source.replace("0x", '').replace(' ', '')
            source = (int(tmp[i:i+2], 16) for i in range(0, len(tmp), 2))

        super(PGPWords, self).__init__(source, **kwargs)

    def __str__(self):
        """Return corresponding pgp words, separated by SEPARATOR."""
        gen = ints_to_word(self)
        return SEPARATOR.join(gen)

    def hex(self):
        """Return corresponding hex representation as string"""
        tmp = ''.join([hex(i).split('x')[1].zfill(2) for i in self])
        gen = (tmp[i:i+4].upper() for i in range(0, len(tmp), 4))
        return SEPARATOR.join(gen)


def main():
    """Try to convert arguments in either direction."""
    if len(sys.argv) < 2 or sys.argv[1].startswith('-'):
        print(__doc__.split("Mainline code:\n\n")[1], file=sys.stderr)
        exit(-1)

    arg_str = ' '.join(sys.argv[1:])

    try:
        result = PGPWords(arg_str, encoding="hex")
        print(result)
    except ValueError as err1:
        try:
            result = PGPWords(arg_str, encoding="pgp-words").hex()
            print(result)
        except InvalidWordError as err2:
            print(err1, file=sys.stderr)
            print(err2, file=sys.stderr)
            exit(-1)


if __name__ == "__main__":
    main()
