import * as M from '../meta';
import { block, brackets, Item, parens, seq } from './block';
import { FunctionContext } from "./context";

export function unconverterForDefinition(
    ctx: FunctionContext,
    def: M.Definition,
    src: string): Item[]
{
    switch (def._variant) {
        case 'or':
            return [seq(`switch (${src}._variant) `, block(
                ... [def.pattern0, def.pattern1, ... def.patternN].map(p =>
                    seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => {
                        const hasValueField = p.pattern._variant === 'SimplePattern';
                        return [seq(`return `, unconverterFor(
                            ctx, p.pattern, hasValueField ? `${src}.value` : src))];
                    })))))];
        case 'and':
            return [seq(`return _.merge`, parens(
                `(a, b) => (a === b) ? a : void 0`,
                ... [def.pattern0, def.pattern1, ... def.patternN].flatMap(p => {
                    if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern') {
                        return [];
                    } else {
                        return [unconverterForNamed(ctx, p, src)];
                    }
                })))];
        case 'Pattern':
            return [seq(`return `, unconverterFor(ctx, def.value, `${src}`))];
    }
}

function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
    switch (p._variant) {
        case 'SimplePattern':
            return ((p: M.SimplePattern) => {
                switch (p._variant) {
                    case 'any':
                        return `${src}`;
                    case 'atom':
                        switch (p.atomKind._variant) {
                            case 'Float': return `_.Single(${src})`;
                            case 'Double': return `_.Double(${src})`;
                            default: return `${src}`;
                        }
                    case 'lit':
                        return ctx.mod.literal(p.value);
                    case 'embedded':
                        return `_.embed(${src})`;
                    case 'seqof':
                        return seq(`${src}.map(v => `,
                                   unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v'),
                                   `)`);
                    case 'setof':
                        return seq(`new _.Set<_embedded>`, parens(seq(
                            `_.Array.from(${src}.values()).map(v => `,
                            unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v'),
                            `)`)));
                    case 'dictof':
                        return seq(`new _.Dictionary<_embedded>`, parens(seq(
                            `_.Array.from(${src}.entries()).map(([k, v]) => `,
                            brackets(
                                unconverterFor(ctx, M.Pattern.SimplePattern(p.key), 'k'),
                                unconverterFor(ctx, M.Pattern.SimplePattern(p.value), 'v')),
                            `)`)));
                    case 'Ref':
                        return ctx.mod.lookup(
                            p.value,
                            (_p, _t) => `from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`,
                            (modPath, modId, modFile, modExpr, _p, _t) => {
                                ctx.mod.imports.add([modPath, modId, modFile, modExpr]);
                                return `${modId}${modExpr}.from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`;
                            });
                }
            })(p.value);
        case 'CompoundPattern':
            return ((p: M.CompoundPattern) => {
                switch (p._variant) {
                    case 'rec':
                        return seq(`_.Record`, parens(
                            unconverterForNamed(ctx, p.label, src),
                            unconverterForNamed(ctx, p.fields, src)));
                    case 'tuple':
                        return brackets(... p.patterns.map(pp =>
                            unconverterForNamed(ctx, pp, src)));
                    case 'tuplePrefix': {
                        const varExp =
                            unconverterForNamed(ctx, M.promoteNamedSimplePattern(p.variable), src);
                        if (p.fixed.length === 0) {
                            return varExp;
                        } else {
                            return brackets(
                                ... p.fixed.map(pp => unconverterForNamed(ctx, pp, src)),
                                seq(`... `, varExp));
                        }
                    }
                    case 'dict':
                        return seq(`new _.Dictionary<_embedded>`, parens(
                            brackets(... Array.from(p.entries.entries()).map(([k, n]) =>
                                brackets(
                                    ctx.mod.literal(k),
                                    unconverterForNamedSimple(ctx, n, src))))));
                }
            })(p.value);
    }
}

function stepSource(src: string, key: string): string
{
    return `${src}[${JSON.stringify(key)}]`;
}

function unconverterForNamed(ctx: FunctionContext, p: M.NamedPattern, src: string): Item {
    if (p._variant === 'named') {
        const steppedSrc = stepSource(src, p.value.name.description!);
        return unconverterFor(ctx, M.Pattern.SimplePattern(p.value.pattern), steppedSrc);
    } else {
        return unconverterFor(ctx, p.value, src);
    }
}

function unconverterForNamedSimple(ctx: FunctionContext, p: M.NamedSimplePattern, src: string): Item {
    if (p._variant === 'named') {
        const steppedSrc = stepSource(src, p.value.name.description!);
        return unconverterFor(ctx, M.Pattern.SimplePattern(p.value.pattern), steppedSrc);
    } else {
        return unconverterFor(ctx, M.Pattern.SimplePattern(p.value), src);
    }
}
