package p.studio.compiler.pbs.lowering;

import p.studio.compiler.models.IRBackendExecutableFunction;
import p.studio.compiler.models.IRReservedMetadata;
import p.studio.compiler.messages.FESurfaceContext;
import p.studio.compiler.pbs.PbsFrontendCompiler;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.ModuleId;
import p.studio.compiler.source.tables.IntrinsicReference;
import p.studio.compiler.source.tables.IntrinsicTable;
import p.studio.compiler.source.tables.NameTable;
import p.studio.utilities.structures.ReadOnlyList;

import java.util.ArrayList;

import static p.studio.compiler.pbs.lowering.PbsExecutableLoweringModels.*;

public final class PbsExecutableLoweringService {
    private final PbsCallableShapeSurfaceService callableShapeSurfaceService = new PbsCallableShapeSurfaceService();
    private final PbsExecutableMetadataIndexFactory metadataIndexFactory = new PbsExecutableMetadataIndexFactory();
    private final PbsExecutableCallableRegistryFactory callableRegistryFactory = new PbsExecutableCallableRegistryFactory();
    private final PbsExecutableBodyLowerer bodyLowerer = new PbsExecutableBodyLowerer();

    public PbsExecutableLoweringResult lower(
            final FileId fileId,
            final PbsAst.File ast,
            final ReadOnlyList<PbsAst.TopDecl> supplementalTopDecls,
            final ModuleId moduleId,
            final IRReservedMetadata reservedMetadata,
            final NameTable nameTable,
            final FESurfaceContext feSurfaceContext,
            final DiagnosticSink diagnostics,
            final ReadOnlyList<PbsFrontendCompiler.ImportedCallableSurface> importedCallables) {
        final var normalizedModuleId = moduleId == null ? ModuleId.none() : moduleId;
        final var metadataIndex = metadataIndexFactory.create(
                ast,
                supplementalTopDecls,
                reservedMetadata,
                nameTable,
                feSurfaceContext,
                diagnostics);
        final var callableRegistry = callableRegistryFactory.create(
                ast,
                normalizedModuleId,
                nameTable,
                metadataIndex.builtinCanonicalBySourceType(),
                importedCallables);
        final var intrinsicIdTable = new IntrinsicTable();
        final var executableFunctions = new ArrayList<IRBackendExecutableFunction>(callableRegistry.localCallables().size());

        for (final var callable : callableRegistry.localCallables()) {
            final var loweredFunction = lowerExecutableFunction(
                    fileId,
                    normalizedModuleId,
                    callable,
                    callableRegistry,
                    diagnostics,
                    nameTable,
                    metadataIndex,
                    intrinsicIdTable);
            if (loweredFunction != null) {
                executableFunctions.add(loweredFunction);
            }
        }

        final var intrinsicPool = new ArrayList<IntrinsicReference>(intrinsicIdTable.size());
        for (final var intrinsicId : intrinsicIdTable.identifiers()) {
            intrinsicPool.add(intrinsicIdTable.get(intrinsicId));
        }
        return new PbsExecutableLoweringResult(
                ReadOnlyList.wrap(executableFunctions),
                callableRegistry.callableSignatures(),
                ReadOnlyList.wrap(intrinsicPool));
    }

    public String callableShapeSurfaceOf(final PbsAst.FunctionDecl functionDecl) {
        return callableShapeSurfaceService.callableShapeSurfaceOf(functionDecl);
    }

    private IRBackendExecutableFunction lowerExecutableFunction(
            final FileId fileId,
            final ModuleId moduleId,
            final PbsLowerableCallable callable,
            final PbsExecutableCallableRegistry callableRegistry,
            final DiagnosticSink diagnostics,
            final NameTable nameTable,
            final PbsExecutableMetadataIndex metadataIndex,
            final IntrinsicTable intrinsicIdTable) {
        final var functionDecl = callable.functionDecl();
        final var functionCallableId = callableRegistry.callableIdByDeclaration().get(callable);
        if (functionCallableId == null) {
            return null;
        }
        final var loweredBody = bodyLowerer.lower(
                functionDecl,
                diagnostics,
                nameTable,
                metadataIndex,
                callableRegistry,
                intrinsicIdTable);
        if (loweredBody == null) {
            return null;
        }
        return new IRBackendExecutableFunction(
                fileId,
                moduleId,
                callable.callableName(),
                functionCallableId,
                safeToInt(functionDecl.span().getStart()),
                safeToInt(functionDecl.span().getEnd()),
                functionDecl.parameters().size(),
                loweredBody.localSlots(),
                callableRegistry.returnSlotsByCallableId().getOrDefault(functionCallableId, 0),
                loweredBody.maxStackSlots(),
                loweredBody.instructions(),
                functionDecl.span());
    }

    private int safeToInt(final long value) {
        if (value > Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        }
        if (value < Integer.MIN_VALUE) {
            return Integer.MIN_VALUE;
        }
        return (int) value;
    }
}
