InternCell.java

/**
 * Copyright (c) 2019, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.powsybl.sld.model.cells;

import com.powsybl.commons.PowsyblException;
import com.powsybl.sld.model.blocks.*;
import com.powsybl.sld.model.coordinate.Direction;
import com.powsybl.sld.model.coordinate.Orientation;
import com.powsybl.sld.model.coordinate.Position;
import com.powsybl.sld.model.coordinate.Side;
import com.powsybl.sld.model.nodes.BusNode;
import com.powsybl.sld.model.nodes.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static com.powsybl.sld.model.coordinate.Position.Dimension.H;
import static com.powsybl.sld.model.coordinate.Position.Dimension.V;

/**
 * @author Benoit Jeanson {@literal <benoit.jeanson at rte-france.com>}
 * @author Nicolas Duchene
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 * @author Franck Lecuyer {@literal <franck.lecuyer at rte-france.com>}
 */
public class InternCell extends AbstractBusCell {

    public enum Shape {
        /**
         * Initial state
         */
        UNDEFINED,

        /**
         * Pattern not handled (more than two legs)
         */
        UNHANDLED_PATTERN,

        /**
         * Intermediary state:
         * <ul>
         *     <li>if, due to some extern cells, the bus nodes are not in the same {@link com.powsybl.sld.layout.LegBusSet}, the intern cell is {@link #ONE_LEG}</li>
         *     <li>if not, the shape is put to either {@link #MAYBE_FLAT} if connecting only two bus nodes, or {@link #UNDEFINED}</li>
         * </ul>
         */
        MAYBE_ONE_LEG,

        /**
         * Intermediary state: the intern cell has only one BusNode on each side and therefore might be {@link #FLAT}.
         * If the corresponding bus nodes positions make it impossible, it could be either {@link #CROSSOVER} or
         * {@link #VERTICAL}.
         */
        MAYBE_FLAT,

        /**
         * Final state: the corresponding intern cell is displayed as a single straight line between two busbar sections
         */
        FLAT,

        /**
         * Final state: the corresponding intern cell is connecting two subsections, it hops over a subsections gap and
         * might hop over some extern cells
         */
        CROSSOVER,

        /**
         * Final state: the corresponding intern cell is in a single subsection
         */
        VERTICAL,

        /**
         * Final state: <i>impaired</i> vertical intern cell, that is, with no equipments.
         * Calling it uni-leg might be misleading as it's one LegParallelBlock but two (or more) LegBlocks,
         * drawn as a single line only if stacked.
         */
        ONE_LEG;

        public boolean checkIsShape(Shape... shapes) {
            return Arrays.stream(shapes).anyMatch(s -> s == this);
        }

        public boolean checkIsNotShape(Shape... shapes) {
            return Arrays.stream(shapes).noneMatch(s -> s == this);
        }
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(InternCell.class);

    private Shape shape;
    private final Map<Side, LegBlock> legs;
    private Block body;

    public InternCell(int cellNumber, Collection<Node> nodes) {
        super(cellNumber, CellType.INTERN, nodes);
        legs = new EnumMap<>(Side.class);
        setDirection(Direction.UNDEFINED);
        shape = Shape.UNDEFINED;
    }

    @Override
    public void accept(CellVisitor cellVisitor) {
        cellVisitor.visit(this);
    }

    public void organizeBlocks(boolean exceptionIfPatternNotHandled) {
        List<LegBlock> candidateLegs = searchLegs();
        if (getRootBlock().getType() == Block.Type.SERIAL && candidateLegs.size() == 2) {
            SerialBlock serialRootBlock = (SerialBlock) getRootBlock();
            assignLeg(serialRootBlock, candidateLegs.get(0));
            assignLeg(serialRootBlock, candidateLegs.get(1));
            body = serialRootBlock.extractBody(new ArrayList<>(legs.values()));
            body.setOrientation(Orientation.RIGHT);

            // if one bus on each side, the intern cell is either flat or vertical
            // if more than one bus on one side, the intern cell is
            //  - either crossover (two sections): detected later in Subsection::identifyCrossOverAndCheckOrientation
            //  - or vertical (one section): detected later in Subsection::identifyVerticalInternCells
            if (candidateLegs.stream().map(LegBlock::getBusNodes).allMatch(bn -> bn.size() == 1)) {
                shape = Shape.MAYBE_FLAT;
            }
        } else {
            if (candidateLegs.size() == 1) {
                shape = Shape.MAYBE_ONE_LEG;
                LegBlock leg = candidateLegs.get(0);
                legs.put(Side.UNDEFINED, leg);
                leg.setOrientation(Orientation.UP);
            } else {
                if (exceptionIfPatternNotHandled) {
                    throw new PowsyblException("InternCell pattern not recognized");
                } else {
                    shape = Shape.UNHANDLED_PATTERN;
                    LOGGER.error("InternCell pattern not handled");
                    LegBlock leg = candidateLegs.get(0);
                    legs.put(Side.UNDEFINED, candidateLegs.get(0));
                    leg.setOrientation(Orientation.UP);
                }
            }
        }
    }

    public void replaceOneLegByMultiLeg(LegBlock left, LegBlock right) {
        body = BodyPrimaryBlock.createBodyPrimaryBlockInBusCell(List.of(left.getEndingNode()));
        body.setOrientation(Orientation.RIGHT);
        SerialBlock serialRootBlock = new SerialBlock(List.of(left, body, right));
        setRootBlock(serialRootBlock);
        legs.remove(Side.UNDEFINED);
        assignLeg(serialRootBlock, left);
        assignLeg(serialRootBlock, right);
        if (left.getBusNodes().size() == 1 && right.getBusNodes().size() == 1) {
            shape = Shape.MAYBE_FLAT;
        } else {
            shape = Shape.UNDEFINED;
        }
    }

    public void replaceBackMultiLegByOneLeg() {
        body = null;
        LegParallelBlock rootBlock = new LegParallelBlock(getLegPrimaryBlocks(), true);
        rootBlock.setOrientation(Orientation.UP);
        setRootBlock(rootBlock);
        legs.clear();
        legs.put(Side.UNDEFINED, rootBlock);
        shape = Shape.ONE_LEG;
    }

    public void setFlat() {
        shape = Shape.FLAT;
        setDirection(Direction.MIDDLE);
        legs.values().forEach(l -> l.setOrientation(Orientation.RIGHT));
    }

    public void setOneLeg() {
        shape = Shape.ONE_LEG;
    }

    private void assignLeg(SerialBlock sb, LegBlock candidateLeg) {
        Optional<Block.Extremity> extremity = sb.whichExtremity(candidateLeg);
        if (extremity.isPresent()) {
            legs.put(extremityToSide(extremity.get()), candidateLeg);
            candidateLeg.setOrientation(Orientation.UP);
        } else {
            throw new PowsyblException("Unable to identify legs of internCell");
        }
    }

    private Side extremityToSide(Block.Extremity extremity) {
        switch (extremity) {
            case START: return Side.LEFT;
            case END: return Side.RIGHT;
            default: return Side.UNDEFINED;
        }
    }

    private List<LegBlock> searchLegs() {
        return getLegPrimaryBlocks().stream()
                .map(lpb -> lpb.getParentBlock() instanceof LegParallelBlock ? (LegParallelBlock) lpb.getParentBlock() : lpb)
                .distinct()
                .collect(Collectors.toList());
    }

    public boolean checkIsShape(Shape... shape) {
        return this.shape.checkIsShape(shape);
    }

    public boolean checkIsNotShape(Shape... shapes) {
        return shape.checkIsNotShape(shapes);
    }

    public void reverseCell() {
        body.reverseBlock();
        legs.computeIfPresent(Side.LEFT, (leftSide, leftLeg) -> {
            LegBlock rightLeg = legs.get(Side.RIGHT);
            legs.put(Side.RIGHT, leftLeg);
            return rightLeg;
        });
    }

    public int getSideHPos(Side side) {
        return getSideToLeg(side).getPosition().get(H);
    }

    @Override
    public void blockSizing() {
        legs.values().forEach(Block::sizing);
        if (shape.checkIsNotShape(Shape.ONE_LEG, Shape.UNDEFINED, Shape.UNHANDLED_PATTERN)) {
            body.sizing();
        }
    }

    public void crossOverBlockSizing() {
        int hLeft = legs.get(Side.LEFT).getPosition().get(H);
        int hRight = legs.get(Side.RIGHT).getPosition().get(H);
        body.getPosition().setSpan(H, hRight - hLeft);
    }

    @Override
    public int newHPosition(int hPosition) {
        int h = hPosition;
        if (shape == Shape.ONE_LEG) {
            legs.get(Side.UNDEFINED).getPosition().set(H, h);
            h += legs.get(Side.UNDEFINED).getPosition().getSpan(H);
        } else {
            legs.get(Side.LEFT).getPosition().set(H, h);
            h += legs.get(Side.LEFT).getPosition().getSpan(H);
            Position pos = body.getPosition();
            if (shape == Shape.FLAT) {
                pos.set(H, h);
                pos.set(V, legs.get(Side.LEFT).getBusNodes().get(0).getBusbarIndex());
            } else {
                h -= 2;
                pos.set(H, h);
                pos.set(V, 0);
            }
            h += pos.getSpan(H);
            legs.get(Side.RIGHT).getPosition().set(H, h);
            h += legs.get(Side.RIGHT).getPosition().getSpan(H);
        }
        return h;
    }

    public int newHPosition(int hPosition, Side side) {
        if (side == Side.LEFT) {
            Position pos = body.getPosition();
            pos.set(H, hPosition);
            pos.set(V, 1);
        }
        legs.get(side).getPosition().set(H, hPosition);
        return hPosition + 2;
    }

    @Override
    public void setDirection(Direction direction) {
        super.setDirection(direction);
        for (LegBlock leg : legs.values()) {
            leg.setOrientation(direction.toOrientation());
        }
    }

    public Map<Side, LegBlock> getLegs() {
        return legs;
    }

    public LegBlock getSideToLeg(Side side) {
        return legs.get(side);
    }

    public List<BusNode> getSideBusNodes(Side side) {
        return legs.get(side).getBusNodes();
    }

    public Block getBodyBlock() {
        return body;
    }

    public Shape getShape() {
        return shape;
    }

    public void setShape(Shape shape) {
        this.shape = shape;
    }
}