import { Injectable } from '@angular/core';
import { FormFieldComponent } from '../components/form-field.component';

@Injectable()
export class OrderingService {
    parse(orderString: string): Orderer {
        const testRegex = /^ *(\*|(:?[a-zA-Z_0-9]+)|(:?group:[a-zA-Z_0-9]+)) *(:?, *(\*|(:?[a-zA-Z_0-9]+)|(:?group:[a-zA-Z_0-9]+)) *)*$/;

        if (!testRegex.test(orderString)) {
            throw new Error(`Invalid ordering string provided: ${orderString}`);
        }

        const orderConcept = orderString.split(',')
            .map(section => section.trim());

        return new Orderer(this.parseOrderConcept(orderConcept));
    }

    parseOrderConcept(orderConcept: Array<string>): Array<Array<string>> {
        let lastRule;
        let currentGroup = [];
        const groups: Array<Array<string>> = [];

        orderConcept.forEach((rule, index) => {
            if (rule !== '*') {
                if (lastRule === undefined) {
                    currentGroup.push('$start');
                }

                currentGroup.push(rule);

                if (index === orderConcept.length - 1) {
                    currentGroup.push('$end');
                    groups.push(currentGroup);
                    currentGroup = [];
                }

            } else {
                if (currentGroup.length > 0) {
                    groups.push(currentGroup);
                    currentGroup = [];
                }
            }

            lastRule = rule;
        });

        return groups;
    }
}

export class Orderer {
    ordered: Array<FormFieldComponent> = [];
    constructor(private _order: Array<Array<string>>) { }

    order(source: Array<FormFieldComponent>): Array<FormFieldComponent> {
        const ordered = [];
        this._order.forEach(rule => {
            if (rule[0] === '$start') {
                for (let i = 1; i < rule.length; i++) {
                    const foundIndex = source.findIndex(({ baseField: { name: fallbackName }, name = fallbackName }) => name === rule[i]);

                    if (foundIndex >= 0) {
                        ordered.push(...source.splice(foundIndex, 1));
                    }
                }
            } else if (rule[rule.length - 1] === '$end') {
                const last = [];

                for (let i = 0; i < rule.length - 1; i++) {
                    const foundIndex = source.findIndex(({ baseField: { name: fallbackName }, name = fallbackName }) => name === rule[i]);

                    if (foundIndex >= 0) {
                        last.push(...source.splice(foundIndex, 1));
                    }
                }

                ordered.push(...source.splice(0, source.length)); // copy all remaining elements into ordered
                ordered.push(...last);
            } else {
                let lastToCopyIndex = source.findIndex(({ baseField: { name: fallbackName }, name = fallbackName }) => name === rule[0]);

                for (let i = 0; i < lastToCopyIndex; i++) {
                    if (!this._order.some(existingRule => existingRule.includes(source[i].name || source[i].baseField && source[i].baseField.name))) {
                        ordered.push(...source.splice(i, 1)); // copy into ordored, delete from concat

                        lastToCopyIndex--; // decrease index, we deleted an element
                        i--; // we must check the same index again
                    }
                }

                for (const member of rule) {
                    const foundIndex = source.findIndex(({ baseField: { name: fallbackName }, name = fallbackName }) => name === member);

                    if (foundIndex >= 0) {
                        ordered.push(...source.splice(foundIndex, 1));
                    }
                }
            }
        });

        ordered.push(...source);

        this.ordered = ordered;

        return ordered;
    }
}
