#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

from django import forms
from django.core import validators
from django.utils.translation import ugettext_lazy as _, ugettext_lazy

from plinth import cfg
from plinth import network
from plinth.utils import format_lazy, import_from_gi
nm = import_from_gi('NM', '1.0')


class ConnectionTypeSelectForm(forms.Form):
    """Form to select type for new connection."""
    connection_type = forms.ChoiceField(
        label=_('Connection Type'),
        choices=[(key, value)
                 for key, value in network.CONNECTION_TYPE_NAMES.items()])


class ConnectionForm(forms.Form):
    """Base form to create/edit a connection."""
    name = forms.CharField(label=_('Connection Name'))
    interface = forms.ChoiceField(
        label=_('Physical Interface'),
        choices=(),
        help_text=_('The network device that this connection should be bound '
                    'to.'))
    zone = forms.ChoiceField(
        label=_('Firewall Zone'),
        help_text=_('The firewall zone will control which services are '
                    'available over this interfaces. Select Internal only '
                    'for trusted networks.'),
        choices=[('external', _('External')),
                 ('internal', _('Internal'))])
    ipv4_method = forms.ChoiceField(
        label=_('IPv4 Addressing Method'),
        help_text=format_lazy(
            ugettext_lazy(
                '"Automatic" method will make {box_name} acquire '
                'configuration from this network making it a client. "Shared" '
                'method will make {box_name} act as a router, configure '
                'clients on this network and share its Internet connection.'),
            box_name=ugettext_lazy(cfg.box_name)),
        choices=[('auto', _('Automatic (DHCP)')),
                 ('shared', _('Shared')),
                 ('manual', _('Manual')),
                 ('disabled', _('Disabled'))])
    ipv4_address = forms.CharField(
        label=_('Address'),
        validators=[validators.validate_ipv4_address],
        required=False)
    ipv4_netmask = forms.CharField(
        label=_('Netmask'),
        help_text=_('Optional value. If left blank, a default netmask '
                    'based on the address will be used.'),
        validators=[validators.validate_ipv4_address],
        required=False)
    ipv4_gateway = forms.CharField(
        label=_('Gateway'),
        help_text=_('Optional value.'),
        validators=[validators.validate_ipv4_address],
        required=False)
    ipv4_dns = forms.CharField(
        label=_('DNS Server'),
        help_text=_('Optional value. If this value is given and IPv4 '
                    'addressing method is "Automatic", the DNS Servers '
                    'provided by a DHCP server will be ignored.'),
        validators=[validators.validate_ipv4_address],
        required=False)
    ipv4_second_dns = forms.CharField(
        label=_('Second DNS Server'),
        help_text=_('Optional value. If this value is given and IPv4 '
                    'Addressing Method is "Automatic", the DNS Servers '
                    'provided by a DHCP server will be ignored.'),
        validators=[validators.validate_ipv4_address],
        required=False)
    ipv6_method = forms.ChoiceField(
        label=_('IPv6 Addressing Method'),
        help_text=format_lazy(
            ugettext_lazy(
                '"Automatic" methods will make {box_name} acquire '
                'configuration from this network making it a client.'),
            box_name=ugettext_lazy(cfg.box_name)),
        choices=[('auto', _('Automatic')),
                 ('dhcp', _('Automatic, DHCP only')),
                 ('manual', _('Manual')),
                 ('ignore', _('Ignore'))])
    ipv6_address = forms.CharField(
        label=_('Address'),
        validators=[validators.validate_ipv6_address],
        required=False)
    ipv6_prefix = forms.IntegerField(
        label=_('Prefix'),
        help_text=_('Value between 1 and 128.'),
        min_value=1,
        max_value=128,
        required=False)
    ipv6_gateway = forms.CharField(
        label=_('Gateway'),
        help_text=_('Optional value.'),
        validators=[validators.validate_ipv6_address],
        required=False)
    ipv6_dns = forms.CharField(
        label=_('DNS Server'),
        help_text=_('Optional value. If this value is given and IPv6 '
                    'addressing method is "Automatic", the DNS Servers '
                    'provided by a DHCP server will be ignored.'),
        validators=[validators.validate_ipv6_address],
        required=False)
    ipv6_second_dns = forms.CharField(
        label=_('Second DNS Server'),
        help_text=_('Optional value. If this value is given and IPv6 '
                    'Addressing Method is "Automatic", the DNS Servers '
                    'provided by a DHCP server will be ignored.'),
        validators=[validators.validate_ipv6_address],
        required=False)

    @staticmethod
    def _get_interface_choices(device_type):
        """Return a list of choices for a given device type."""
        interfaces = network.get_interface_list(device_type)
        choices = [('', _('-- select --'))]
        for interface, mac in interfaces.items():
            display_string = '{interface} ({mac})'.format(interface=interface,
                                                          mac=mac)
            choices.append((interface, display_string))

        return choices

    def get_settings(self):
        """Return settings dict from cleaned data."""
        settings = {}
        settings['common'] = {
            'name': self.cleaned_data['name'],
            'interface': self.cleaned_data['interface'],
            'zone': self.cleaned_data['zone'],
        }
        settings['ipv4'] = self.get_ipv4_settings()
        settings['ipv6'] = self.get_ipv6_settings()
        return settings

    def get_ipv4_settings(self):
        """Return IPv4 dict from cleaned data."""
        ipv4 = {
            'method': self.cleaned_data['ipv4_method'],
            'address': self.cleaned_data['ipv4_address'],
            'netmask': self.cleaned_data['ipv4_netmask'],
            'gateway': self.cleaned_data['ipv4_gateway'],
            'dns': self.cleaned_data['ipv4_dns'],
            'second_dns': self.cleaned_data['ipv4_second_dns'],
        }
        return ipv4

    def get_ipv6_settings(self):
        """Return IPv6 dict from cleaned data."""
        ipv6 = {
            'method': self.cleaned_data['ipv6_method'],
            'address': self.cleaned_data['ipv6_address'],
            'prefix': self.cleaned_data['ipv6_prefix'],
            'gateway': self.cleaned_data['ipv6_gateway'],
            'dns': self.cleaned_data['ipv6_dns'],
            'second_dns': self.cleaned_data['ipv6_second_dns'],
        }
        return ipv6


class GenericForm(ConnectionForm):
    """Form to create/edit a generic connection."""
    def __init__(self, *args, **kwargs):
        """Initialize the form, populate interface choices."""
        super(GenericForm, self).__init__(*args, **kwargs)
        choices = self._get_interface_choices(nm.DeviceType.GENERIC)
        self.fields['interface'].choices = choices

    def get_settings(self):
        """Return settings dict from cleaned data."""
        settings = super().get_settings()
        settings['common']['type'] = 'generic'
        return settings


class EthernetForm(ConnectionForm):
    """Form to create/edit a ethernet connection."""
    def __init__(self, *args, **kwargs):
        """Initialize the form, populate interface choices."""
        super(EthernetForm, self).__init__(*args, **kwargs)
        choices = self._get_interface_choices(nm.DeviceType.ETHERNET)
        self.fields['interface'].choices = choices

    def get_settings(self):
        """Return settings dict from cleaned data."""
        settings = super().get_settings()
        settings['common']['type'] = '802-3-ethernet'
        return settings


class PPPoEForm(EthernetForm):
    """Form to create a new PPPoE connection."""
    ipv4_method = None
    ipv4_address = None
    ipv4_netmask = None
    ipv4_gateway = None
    ipv4_dns = None
    ipv4_second_dns = None
    ipv6_method = None
    ipv6_address = None
    ipv6_prefix = None
    ipv6_gateway = None
    ipv6_dns = None
    ipv6_second_dns = None

    username = forms.CharField(label=_('Username'))
    password = forms.CharField(label=_('Password'),
                               widget=forms.PasswordInput(render_value=True))
    show_password = forms.BooleanField(label=_('Show password'),
                                       required=False)

    def get_settings(self):
        """Return setting dict from cleaned data."""
        settings = super().get_settings()
        settings['common']['type'] = 'pppoe'
        settings['pppoe'] = {
            'username': self.cleaned_data['username'],
            'password': self.cleaned_data['password'],
        }
        return settings

    def get_ipv4_settings(self):
        """Return IPv4 settings from cleaned data."""
        return None

    def get_ipv6_settings(self):
        """Return IPv6 settings from cleaned data."""
        return None


class WifiForm(ConnectionForm):
    """Form to create/edit a Wi-Fi connection."""
    field_order = ['name', 'interface', 'zone', 'ssid', 'mode', 'band',
                   'channel', 'bssid', 'auth_mode', 'passphrase',
                   'ipv4_method', 'ipv4_address', 'ipv4_netmask',
                   'ipv4_gateway', 'ipv4_dns', 'ipv4_second_dns',
                   'ipv6_method', 'ipv6_address', 'ipv6_prefix',
                   'ipv6_gateway', 'ipv6_dns', 'ipv6_second_dns']

    ssid = forms.CharField(
        label=_('SSID'),
        help_text=_('The visible name of the network.'))
    mode = forms.ChoiceField(
        label=_('Mode'),
        choices=[('infrastructure', _('Infrastructure')),
                 ('ap', _('Access Point')),
                 ('adhoc', _('Ad-hoc'))])
    band = forms.ChoiceField(
        label=_('Frequency Band'),
        choices=[('auto', _('Automatic')),
                 ('a', _('A (5 GHz)')),
                 ('bg', _('B/G (2.4 GHz)'))])
    channel = forms.IntegerField(
        label=_('Channel'),
        help_text=_('Optional value. Wireless channel in the selected '
                    'frequency band to restrict to. Blank or 0 value means '
                    'automatic selection.'),
        min_value=0,
        max_value=255,
        required=False)
    bssid = forms.RegexField(
        label=_('BSSID'),
        help_text=_('Optional value. Unique identifier for the access point. '
                    'When connecting to an access point, connect only if the '
                    'BSSID of the access point matches the one provided. '
                    'Example: 00:11:22:aa:bb:cc.'),
        regex=r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$',
        required=False)
    auth_mode = forms.ChoiceField(
        label=_('Authentication Mode'),
        help_text=_('Select WPA if the wireless network is secured and \
requires clients to have the password to connect.'),
        choices=[('wpa', _('WPA')),
                 ('open', _('Open'))])
    passphrase = forms.CharField(
        label=_('Passphrase'),
        validators=[validators.MinLengthValidator(8)],
        required=False)

    def __init__(self, *args, **kwargs):
        """Initialize the form, populate interface choices."""
        super(WifiForm, self).__init__(*args, **kwargs)
        choices = self._get_interface_choices(nm.DeviceType.WIFI)
        self.fields['interface'].choices = choices

    def get_settings(self):
        """Return setting dict from cleaned data."""
        settings = super().get_settings()
        settings['common']['type'] = '802-11-wireless'
        settings['wireless'] = {
            'ssid': self.cleaned_data['ssid'],
            'mode': self.cleaned_data['mode'],
            'band': self.cleaned_data['band'],
            'channel': self.cleaned_data['channel'],
            'bssid': self.cleaned_data['bssid'],
            'auth_mode': self.cleaned_data['auth_mode'],
            'passphrase': self.cleaned_data['passphrase'],
        }
        return settings
