# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2016 Entr'ouvert
#
# 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.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from .models import Query, get_code, identifier_re


class QueryForm(forms.ModelForm):
    class Meta:
        model = Query
        widgets = {
            'resource': forms.HiddenInput(),
            'filters': forms.Textarea(attrs={'rows': 2}),
            'projections': forms.Textarea(attrs={'rows': 2}),
            'order': forms.Textarea(attrs={'rows': 2}),
            'distinct': forms.Textarea(attrs={'rows': 2}),
        }
        fields = '__all__'

    def clean_lines_of_expressions(self, lines, named=False):
        if not lines:
            return lines
        errors = []
        for i, line in enumerate(lines.splitlines()):
            if named:
                line = line.split(':', 1)
                if len(line) != 2:
                    errors.append(
                        ValidationError(
                            _(
                                'Syntax error line %d: each line must be prefixed '
                                'with an identifier followed by a colon.'
                            )
                            % (i + 1)
                        )
                    )
                    continue
                name, line = line
                if not identifier_re.match(name):
                    errors.append(
                        ValidationError(
                            _(
                                'Syntax error line %d: invalid identifier, '
                                'it must starts with a letter and only '
                                'contains letters, digits and _.'
                            )
                            % (i + 1)
                        )
                    )
                    continue
            try:
                get_code(line)
            except SyntaxError as e:
                errors.append(
                    ValidationError(
                        _('Syntax error line %(line)d at character %(character)d')
                        % {'line': i + 1, 'character': e.offset}
                    )
                )
        if errors:
            raise ValidationError(errors)
        return lines

    def clean_filters(self):
        return self.clean_lines_of_expressions(self.data.get('filters'))

    def clean_projections(self):
        return self.clean_lines_of_expressions(self.data.get('projections'), named=True)

    def clean_order(self):
        return self.clean_lines_of_expressions(self.data.get('order'))

    def clean_distinct(self):
        return self.clean_lines_of_expressions(self.data.get('distinct'))

    def clean_structure(self):
        if self.data.get('structure') == 'keyed-distinct':
            if not self.cleaned_data.get('distinct'):
                raise ValidationError(_('Keyed dictionary requires a single distinct column.'))
            if len(self.cleaned_data.get('distinct').strip().splitlines()) > 1:
                raise ValidationError(_('Keyed dictionary requires a single distinct column.'))
        return self.data.get('structure')
