#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
    WiCC (Wifi Cracking Camp)
    GUI tool for wireless pentesting on WEP and WPA/WPA2 networks.
    Project developed by Pablo Sanz Alguacil, Miguel Yanes Fernández and Adan Chalkley,
    as the Group Project for the 3rd year of the Bachelor of Sicence in Computing in Digital Forensics and CyberSecurity
    at TU Dublin - Blanchardstown Campus
"""

import webbrowser
from tkinter import *
from tkinter import Tk, ttk, Frame, Button, Label, Checkbutton, Menu, RIGHT, N, E, S, W, END, StringVar, \
    messagebox, filedialog
from wicc_operations import Operation
from wicc_view_dos import DoS
from wicc_view_wordlist import GenerateWordlist
from wicc_view_mac import ViewMac
from wicc_view_splash import Splash
from wicc_view_popup import PopUpWindow
from wicc_view_about import About


class View:
    control = ""
    interfaces = ""
    networks = ""
    width = 850
    height = 470
    interfaces_old = []
    networks_old = []
    encryption_types = ('ALL', 'WEP', 'WPA')
    channels = ('ALL', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14')
    mac_spoofing_status = False
    silent_mode_status = False
    icon_path = "resources/icon.png"
    icon_path_small = "resources/icon_small.png"

    def __init__(self, control):
        self.control = control
        self.splash = Splash()
        self.popup_gen = PopUpWindow()

    def build_window(self):
        """
        Generates the window.

        :author: Pablo Sanz Alguacil
        """

        self.root = Tk()
        self.root.protocol("WM_DELETE_WINDOW", self.notify_kill)
        try:
            self.root.style = ttk.Style()
            self.root.style.theme_use('default')
        except:
            pass
        # get screen width and height
        ws = self.root.winfo_screenwidth()
        hs = self.root.winfo_screenheight()
        # calculate position x, y
        x = (ws / 2) - (self.width / 2)
        y = (hs / 2) - (self.height / 2)
        self.root.geometry('%dx%d+%d+%d' % (self.width, self.height, x, y))
        self.root.resizable(width=False, height=False)
        self.root.title('WiCC - Wifi Cracking Camp')
        icon = Image("photo", file=self.icon_path_small)
        self.root.call('wm', 'iconphoto', self.root._w, icon)

        # MENU BAR
        self.menubar = Menu(self.root)
        self.root['menu'] = self.menubar

        self.file_menu = Menu(self.menubar)
        self.tools_menu = Menu(self.menubar)
        self.help_menu = Menu(self.menubar)
        self.menubar.add_cascade(menu=self.file_menu, label='File')
        self.menubar.add_cascade(menu=self.tools_menu, label='Tools')
        self.menubar.add_cascade(menu=self.help_menu, label='Help')

        # MENU 1
        self.file_menu.add_command(label='Show cracked passwords',
                                   command=self.show_cracked_passwords,
                                   underline=13,
                                   compound=LEFT)

        self.file_menu.add_command(label='Temporary files location',
                                   command=self.temporary_files_location,
                                   underline=0,
                                   compound=LEFT)

        self.file_menu.add_command(label='Select wordlist',
                                   command=self.select_custom_wordlist,
                                   underline=7,
                                   compound=LEFT)

        self.file_menu.add_command(label='Exit',
                                   command=self.notify_kill,
                                   underline=0,
                                   compound=LEFT)



        # MENU 2
        self.tools_menu.add_command(label='MAC menu',
                                    command=self.mac_tools_window,
                                    underline=0,
                                    compound=LEFT)

        self.tools_menu.add_command(label='Generate wordlist',
                                    command=self.generate_wordlists_window,
                                    underline=0,
                                    compound=LEFT)

        self.tools_menu.add_command(label='Decrypt capture file',
                                    command=self.decrypt_cap_file,
                                    underline=0,
                                    compound=LEFT)

        # MENU 3
        self.help_menu.add_command(label='Help',
                                   command=self.open_link,
                                   underline=0,
                                   compound=LEFT)

        self.help_menu.add_command(label='About',
                                   command=self.show_about,
                                   underline=0,
                                   compound=LEFT)

        # LABEL FRAME - SCAN
        self.labelframe_scan = LabelFrame(self.root, text="Scan")
        self.labelframe_scan.pack(fill="both", expand="yes")

        # LABEL FRAME - FILTERS
        self.labelframe_filters = LabelFrame(self.root, text="Optional Filters")
        self.labelframe_filters.pack(fill="both", expand="yes")

        # LABEL FRAME - AVAILABLE NETWORKS
        self.labelframe_networks = LabelFrame(self.root, text="Available Networks")
        self.labelframe_networks.pack(fill="both", expand="yes")

        # LABEL FRAME - SELECT NETWORK NETWORK
        self.labelframe_sel_net = LabelFrame(self.root, text="")
        self.labelframe_sel_net.pack(fill="both", expand="yes")
        self.labelframe_sel_net.grid_columnconfigure(1, weight=1)

        # LABEL FRAME - ATTACK OPTIONS
        self.labelframe_attack_options = LabelFrame(self.root, text="Attack Options")
        self.labelframe_attack_options.pack(fill="both", expand="yes")

        # LABEL FRAME - WEP
        self.labelframe_wep = LabelFrame(self.root, text="WEP Attack Options")

        # LABEL FRMAE - WPA
        self.labelframe_wpa = LabelFrame("", text="WPA Attack Options")

        # LABEL - INTERFACES
        self.label_interfaces = Label(self.labelframe_scan, text="Interface: ")
        self.label_interfaces.grid(column=1, row=0, padx=5)

        # COMBO BOX - NETWORK INTERFACES
        self.interfaceVar = StringVar()
        self.interfaces_combobox = ttk.Combobox(self.labelframe_scan, textvariable=self.interfaceVar, state="readonly")
        self.interfaces_combobox['values'] = self.interfaces
        self.interfaces_combobox.bind("<<ComboboxSelected>>")
        self.interfaces_combobox.grid(column=2, row=0)

        # FRAME - START/STOP SCAN
        self.frame_start_stop = Frame(self.labelframe_scan)
        self.frame_start_stop.grid(column=3, row=0, padx=230, pady=5)

        # BUTTON - START SCAN
        self.button_start_scan = Button(self.frame_start_stop, text='      Scan networks      ',
                                        command=self.start_scan)
        self.button_start_scan.grid(column=1, row=0, padx=5)

        # BUTTON - STOP SCAN
        self.button_stop_scan = Button(self.frame_start_stop, text='      Stop scanning      ', state=DISABLED,
                                       command=self.stop_scan)
        self.button_stop_scan.grid(column=2, row=0, padx=5)

        # LABEL - CHANNELS
        self.label_channels = Label(self.labelframe_filters, text="Channel: ", padx=10, pady=10)
        self.label_channels.grid(column=1, row=0)

        # COMBO BOX - CHANNELS
        self.channelVar = StringVar()
        self.channels_combobox = ttk.Combobox(self.labelframe_filters, textvariable=self.channelVar, state="readonly")
        self.channels_combobox['values'] = self.channels
        self.channels_combobox.bind("<<ComboboxSelected>>")
        self.channels_combobox.current(0)
        self.channels_combobox.grid(column=2, row=0)

        # LABEL - ENCRYPTIONS
        self.label_encryptions = Label(self.labelframe_filters, text="Encryption: ")
        self.label_encryptions.grid(column=3, row=0, padx=5)

        # COMBO BOX - ENCRYPTOION
        self.encryptionVar = StringVar()
        self.encryption_combobox = ttk.Combobox(self.labelframe_filters, textvariable=self.encryptionVar,
                                                state="readonly")
        self.encryption_combobox['values'] = self.encryption_types
        self.encryption_combobox.current(0)
        self.encryption_combobox.bind("<<ComboboxSelected>>")
        self.encryption_combobox.grid(column=4, row=0)

        # CHECKBOX - CLIENTS
        self.clients_status = BooleanVar()
        self.clients_checkbox = Checkbutton(self.labelframe_filters, text="Only clients",
                                            variable=self.clients_status)
        self.clients_checkbox.grid(column=5, row=0, padx=15)

        # TREEVIEW - NETWORKS
        self.networks_treeview = ttk.Treeview(self.labelframe_networks)
        self.networks_treeview["columns"] = ("id", "bssid_col", "channel_col", "encryption_col", "power_col",
                                             "clients_col")
        self.networks_treeview.column("id", width=60)
        self.networks_treeview.column("bssid_col", width=150)
        self.networks_treeview.column("channel_col", width=60)
        self.networks_treeview.column("encryption_col", width=85)
        self.networks_treeview.column("power_col", width=70)
        self.networks_treeview.column("clients_col", width=60)

        self.networks_treeview.heading("id", text="ID")
        self.networks_treeview.heading("bssid_col", text="BSSID")
        self.networks_treeview.heading("channel_col", text="CH")
        self.networks_treeview.heading("encryption_col", text="ENC")
        self.networks_treeview.heading("power_col", text="PWR")
        self.networks_treeview.heading("clients_col", text="CLNTS")

        self.scrollBar = Scrollbar(self.labelframe_networks)
        self.scrollBar.pack(side=RIGHT, fill=Y)
        self.scrollBar.config(command=self.networks_treeview.yview)
        self.networks_treeview.config(yscrollcommand=self.scrollBar.set)

        self.networks_treeview.pack(fill=X)

        # BUTTON - SELECT NETWORK
        self.button_select_network = Button(self.labelframe_sel_net, text="1 - Select network",
                                            command=self.select_network, state=DISABLED)
        self.button_select_network.grid(column=1, row=0, padx=5, pady=5, sticky=W + E + N + S)

        # CHECKBUTTON - SILENT MODE
        self.checkbutton_silent = Checkbutton(self.labelframe_sel_net, text="Silent Mode",
                                              command=self.silent_mode)
        self.checkbutton_silent.grid(column=2, row=0, padx=5, pady=5, sticky=W)

        # CHECKBUTTON - SILENT MODE
        self.button_select_wordlist = Button(self.labelframe_sel_net, text=" Select wordlist",
                                             command=self.select_custom_wordlist)
        self.button_select_wordlist.grid(column=3, row=0, padx=5, pady=5, sticky=W)

        # LABEL - NULL LABEL
        self.label_info_attack = Label(self.labelframe_attack_options,
                                       text="Select a network to see the available options")
        self.label_info_attack.grid(column=0, row=0, padx=5)

        # BUTTON - START ATTACK WEP
        self.button_start_attack_wep = Button(self.labelframe_wep, text="2 - Attack", command=self.start_attack)
        self.button_start_attack_wep.grid(column=0, row=0, padx=5)

        # BUTTON - DOS ATTACK WEP
        self.button_dos_wep = Button(self.labelframe_wep, text="DoS Attack", command=self.dos_attack)
        self.button_dos_wep.grid(column=1, row=0, padx=640)

        # BUTTON - SCAN WPA
        self.button_scan_wpa = Button(self.labelframe_wpa, text="2 - Capture handshake", command=self.start_scan_wpa)
        self.button_scan_wpa.grid(column=0, row=0, padx=5)

        # BUTTON - START ATTACK WPA
        self.button_start_attack_wpa = Button(self.labelframe_wpa, text="3 - Attack", command=self.start_attack)
        self.button_start_attack_wpa.grid(column=1, row=0, padx=5)

        # BUTTON - DOS ATTACK WPA
        self.button_dos_wpa = Button(self.labelframe_wpa, text="DoS Attack", command=self.dos_attack)
        self.button_dos_wpa.grid(column=2, row=0, padx=455)

        self.root.mainloop()

    def select_network(self):
        """
        Changes the attack labelframe to WEP or WPA depending on the selected network. Then ends the selected network to
        Control.

        :author: Pablo Sanz Alguacil
        """
        try:
            current_item = self.networks_treeview.focus()
            network_enc = self.networks_treeview.item(current_item)['values'][3]
            network_id = self.networks_treeview.item(current_item)['values'][0]

            if "WEP" in network_enc:
                self.labelframe_attack_options.pack_forget()
                self.labelframe_wpa.pack_forget()
                self.labelframe_wep.pack(fill="both", expand="yes")
            elif "WPA" in network_enc:
                self.labelframe_attack_options.pack_forget()
                self.labelframe_wep.pack_forget()
                self.labelframe_wpa.pack(fill="both", expand="yes")

            self.send_notify(Operation.SELECT_NETWORK, network_id)

        except:
            self.send_notify(Operation.SELECT_NETWORK, "")

    def start_scan(self):
        """
        Sends filters to Control and the sends the selected interface to Control to start scanning.
        Activates button dehabilitation.

        :author: Pablo Sanz Alguacil
        """

        self.set_buttons(False)
        self.send_notify(Operation.SCAN_OPTIONS, self.apply_filters())
        self.send_notify(Operation.SELECT_INTERFACE, self.interfaceVar.get())

    def stop_scan(self):
        """
        Sends the stop scannig order to Control.
        Deactivates buttons dehabilitation.

        :author: Pablo Sanz Alguacil
        """

        self.set_buttons(True)
        self.send_notify(Operation.STOP_SCAN, "")

    def set_buttons(self, status):
        """
        Sets all buttons state to "ACTIVE" or "DISABLED".

        :param status: boolean
        :author: Pablo Sanz Alguacil
        """
        if status:
            state = ACTIVE
            self.button_stop_scan['state'] = DISABLED
            self.menubar.entryconfig("File", state="normal")
            self.menubar.entryconfig("Tools", state="normal")
            self.menubar.entryconfig("Help", state="normal")
        else:
            state = DISABLED
            self.button_stop_scan['state'] = ACTIVE
            self.menubar.entryconfig("File", state="disabled")
            self.menubar.entryconfig("Tools", state="disabled")
            self.menubar.entryconfig("Help", state="disabled")

        self.interfaces_combobox['state'] = state
        self.encryption_combobox['state'] = state
        self.channels_combobox['state'] = state
        self.clients_checkbox['state'] = state
        self.button_start_scan['state'] = state
        self.button_select_network['state'] = state
        self.checkbutton_silent['state'] = state
        self.button_start_attack_wep['state'] = state
        self.button_scan_wpa['state'] = state
        self.button_start_attack_wpa['state'] = state
        self.checkbutton_silent['state'] = state
        self.button_select_wordlist['state'] = state
        self.button_dos_wep['state'] = state
        self.button_dos_wpa['state'] = state


    def start_attack(self):
        """
        Sends an order d to Control, to start the attack.

        :author: Pablo Sanz Alguacil
        """

        self.send_notify(Operation.ATTACK_NETWORK, "")

    def notify_kill(self):
        """
        Sends and order to kill all processes when X is clicked

        :author: Pablo Sanz Alguacil
        """

        self.send_notify(Operation.STOP_RUNNING, "")

    def reaper_calls(self):
        """
        Receives a notification to kill root

        :author: Pablo Sanz Alguacil
        """

        self.root.destroy()

    def select_custom_wordlist(self):
        """
        Shows a window to select a custom wordlist to use. Then sends the path to control.

        :author: Pablo Sanz Alguacil
        """
        select_window = filedialog.askopenfilename(parent=self.root,
                                                   initialdir='/home',
                                                   title='Choose wordlist file',
                                                   filetypes=[('Text files', '.txt'),
                                                              ('List files', '.lst'),
                                                              ("All files", "*.*")])
        if select_window:
            try:
                self.send_notify(Operation.SELECT_CUSTOM_WORDLIST, select_window)
            except:
                self.popup_gen.error("Open Source File", "Failed to read file \n'%s'" % select_window)
                return

    def randomize_mac(self):
        """
        Generates a popup window asking for authorisation to change the MAC, then sends the randomize order to Control,
        and shows another popup showing the new MAC.

        :author: Pablo Sanz Alguacil
        """
        if self.interfaceVar.get() != "":
            current_mac_alert = self.popup_gen.yesno("", "Your current MAC is: " + self.current_mac()
                                                     + "\n\nAre you sure you want to change it? ")
            if current_mac_alert:
                self.send_notify(Operation.RANDOMIZE_MAC, self.interfaceVar.get())
                self.popup_gen.info("", "Your new MAC is: " + self.current_mac())
        else:
            self.popup_gen.warning("", "No interface selected. Close the window and select one")

    def customize_mac(self, new_mac):
        """
        Generates a popup window asking for authorisation to change the MAC, then sends the customize order to Control,
        and shows another popup showing the new MAC.
        :param new_mac: new MAC to be set

        :author: Pablo Sanz Alguacil
        """

        if self.interfaceVar.get() != "":
            current_mac_alert = self.popup_gen.yesno("", "Your current MAC is: " + self.current_mac()
                                                     + "\n\nAre you sure you want to change it for\n" +
                                                     new_mac + " ?")
            if current_mac_alert:
                self.send_notify(Operation.CUSTOMIZE_MAC, (self.interfaceVar.get(), new_mac))
                self.popup_gen.info("", "Your new MAC is: " + self.current_mac())
        else:
            self.popup_gen.warning("", "No interface selected. Close the window and select one")

    def restore_mac(self):
        """
        Generates a popup window asking for authorisation to restore the MAC, then sends the restore order to Control,
        and shows another popup showing the new MAC.

        :author: Pablo Sanz Alguacil
        """

        if self.interfaceVar.get() != "":
            current_mac_alert = messagebox.askyesno("", "Your current MAC is: " + self.current_mac()
                                                    + "\n\nAre you sure you want to restore original?")
            if current_mac_alert:
                self.send_notify(Operation.RESTORE_MAC, self.interfaceVar.get())
                self.popup_gen.info("", "Your new MAC is: " + self.current_mac())
        else:
            self.popup_gen.warning("", "No interface selected. Close the window and select one")

    def spoofing_mac(self, status):
        """
        Sends the order to activate MAC spoofing to Control.
        :param status: current status of MAC spoofing

        :author: Pablo Sanz Alguacil
        """

        if self.interfaceVar.get() != "":
            self.send_notify(Operation.SPOOF_MAC, status)
        else:
            self.popup_gen.warning("", "No interface selected. Close the window and select one")

    def mac_tools_window(self):
        """
        Generates the MAC tools window.
        Activates buttons dehabilitation.

        :author: Pablo Sanz Alguacil
        """

        self.disable_window(True)
        ViewMac(self, self.mac_spoofing_status)

    def apply_filters(self):
        """
        [0]ENCRYPTION (string)
        [1]WPS (boolean)
        [2]CLIENTS (boolean)
        [3]CHANNEL (string)
        Sets the filters parameters depending on the options choosed.
        :return: array containing filter parameters

        :author: Pablo Sanz Alguacil
        """

        filters_status = ["ALL", False, False, "ALL"]
        if self.encryptionVar.get() != "ALL":
            filters_status[0] = self.encryptionVar.get()
        if self.clients_status.get():
            filters_status[2] = True
        if self.channelVar.get() != "ALL":
            filters_status[3] = self.channelVar.get()
        return filters_status

    def get_notify(self, interfaces, networks):
        """
        Introduces the interfaces and networks received in their respective structures.
        :param interfaces: array containing strings of the interfaces names.
        :param networks: array containing the networks and its properties.

        :author: Pablo Sanz Alguacil
        """

        if interfaces:
            self.interfaces_old = interfaces
            interfaces_list = []
            for item in interfaces:
                interfaces_list.append(item[0])
            self.interfaces_combobox['values'] = interfaces_list
            self.interfaces_combobox.update()

        self.networks_old = networks
        self.networks_treeview.delete(*self.networks_treeview.get_children())
        for item in networks:
            self.networks_treeview.insert("", END, text=item[13], values=(item[0], item[1], item[4], item[6],
                                                                          item[9] + " dbi", item[16]))
            self.networks_treeview.update()

    def current_mac(self):
        """
        Gets the current MAC from Control.
        :return: string containing the MAC address

        :author: Pablo Sanz Alguacil
        """

        return str(self.control.mac_checker(self.interfaceVar.get()))

    def get_notify_childs(self, operation, value):
        """
        [0] Custom MAC
        [1] Random MAC
        [2] Restore MAC
        [3] MAC spoofing
        [4] Save directory to generated wordlists
        [5] Generate wordlist
        [6] DoS Attack
        Manages the operations received by the child windows (MAC tools, Crunch window)
        :param operation: integer. Is the id of the operation.
        :param value: value of the operation

        :author: Pablo Sanz Alguacil
        """

        if operation == 0:
            self.customize_mac(value)
        elif operation == 1:
            self.randomize_mac()
        elif operation == 2:
            self.restore_mac()
        elif operation == 3:
            self.mac_spoofing_status = value
            self.spoofing_mac(value)
        elif operation == 4:
            self.send_notify(Operation.PATH_GENERATED_LISTS, value)
        elif operation == 5:
            self.send_notify(Operation.GENERATE_LIST, value)
        elif operation == 6:
            self.send_notify(Operation.DOS_ATTACK, value)

    def get_spoofing_status(self):
        """
        Gets the current spoofing status.
        :return: boolean

        :author: Pablo Sanz Alguacil
        """

        return self.mac_spoofing_status

    def send_notify(self, operation, value):
        """
        Sends an order to Control
        :param operation: Opertaion from Operations class
        :param value: value of the operation
        :return:

        :author: Pablo Sanz Alguacil
        """

        self.control.get_notify(operation, value)
        return

    def disable_window(self, value):
        """
        Disables all buttons
        :param value: boolean. True for disable, False for enable.

        :author: Pablo Sanz Alguacil
        """

        if value:
            self.set_buttons(False)
            self.button_stop_scan['state'] = DISABLED
        elif not value:
            self.set_buttons(True)

    def generate_wordlists_window(self):
        """
        Generates the custom wordlists generator window.

        :author: Pablo Sanz Alguacil
        """

        self.disable_window(True)
        GenerateWordlist(self)

    def temporary_files_location(self):
        """
        Shows a window to select a location to save temporary files. Then sends the path to control.

        :author: Pablo Sanz Alguacil
        """

        select_window = filedialog.askdirectory(parent=self.root,
                                                initialdir='/home',
                                                title='Choose directory')
        if select_window:
            try:
                self.send_notify(Operation.SELECT_TEMPORARY_FILES_LOCATION, select_window)

            except:
                self.popup_gen.error("Error", "Failed to set directory \n'%s'" % select_window)
                return

    def start_scan_wpa(self):
        """
        Sends a notification to start a WPA scan.

        :author: Pablo Sanz Alguacil
        """

        self.send_notify(Operation.START_SCAN_WPA, "")

    def silent_mode(self):
        """
        Sends an order to control to set or unset the silent mode. Saves the status in a local variable.

        :author: Pablo Sanz Alguacil
        """

        if self.silent_mode_status:
            self.silent_mode_status = False
            self.send_notify(Operation.SILENT_SCAN, False)
        else:
            self.silent_mode_status = True
            self.send_notify(Operation.SILENT_SCAN, True)

    def get_notify_buttons(self, buttons, state):
        """
        Gets a notification to enable or disable buttons from the wep and wpa labelframes.
        :param buttons: Array[String] names of the buttons to be dissabled.
        :param state: boolean

        :author: Pablo Sanz Alguacil
        """

        if state:
            status = ACTIVE
        else:
            status = DISABLED

        for button in buttons:
            if button == "scan_wpa":
                self.button_scan_wpa['state'] = status
            elif button == "attack_wpa":
                self.button_start_attack_wpa['state'] = status
            elif button == "attack_wep":
                self.button_start_attack_wep['state'] = status
            elif button == "select network":
                self.button_select_network['state'] = status

    def show_about(self):
        """
        Creates a new About object

        :author: Pablo Sanz Alguacil
        """
        About()

    def open_link(self):
        """
        Opens the URL on a new tab in the default web browser.

        :author: Pablo Sanz Alguacil
        """

        url = "http://www.github.com/pabloibiza/WiCC"
        webbrowser.open_new_tab(url)

    def show_cracked_passwords(self):
        """
        Sends a notification to Control to open the cracked passwords file.

        :author: Pablo Sanz Alguacil
        """
        self.send_notify(Operation.OPEN_CRACKED, "")

    def dos_attack(self):
        """
        Sends an order to control to start a DoS Attack.

        :author: Pablo Sanz Alguacil
        """
        self.disable_window(True)
        DoS(self)

    def decrypt_cap_file(self):
        """
        Sends a notification and a path to Control to decrypt a .cap file.

        :author: Pablo Sanz Alguacil
        """

        file_path = filedialog.askopenfilename(parent=self.root,
                                      initialdir='/home',
                                      title='Choose wordlist file',
                                      filetypes=[('Capture', '.cap'),
                                                 ('Packet Capture', '.cap'),
                                                 ("All files", "*.*")])

        self.send_notify(Operation.DECRYPT_FILE, file_path)

        command = ['pyrit', '-r', file_path, 'analyze', '|', 'grep', 'AccessPoint', '|', 'awk', '\'{$1=\"\";', 'print',
                   '$0}\'', '|', 'sed', '\"s/[()\']//g;s/.$//\"', '|', 'sort']