///////////////////////////////////////////////////////////////////////////
//                   __                _      _   ________               //
//                  / /   ____  ____ _(_)____/ | / / ____/               //
//                 / /   / __ \/ __ `/ / ___/  |/ / / __                 //
//                / /___/ /_/ / /_/ / / /__/ /|  / /_/ /                 //
//               /_____/\____/\__, /_/\___/_/ |_/\____/                  //
//                           /____/                                      //
//                                                                       //
//               The Next Generation Logic Library                       //
//                                                                       //
///////////////////////////////////////////////////////////////////////////
//                                                                       //
//  Copyright 2015-20xx Christoph Zengler                                //
//                                                                       //
//  Licensed under the Apache License, Version 2.0 (the "License");      //
//  you may not use this file except in compliance with the License.     //
//  You may obtain a copy of the License at                              //
//                                                                       //
//  http://www.apache.org/licenses/LICENSE-2.0                           //
//                                                                       //
//  Unless required by applicable law or agreed to in writing, software  //
//  distributed under the License is distributed on an "AS IS" BASIS,    //
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      //
//  implied.  See the License for the specific language governing        //
//  permissions and limitations under the License.                       //
//                                                                       //
///////////////////////////////////////////////////////////////////////////

package org.logicng.io.graphical;

import org.logicng.io.graphical.generators.FormulaAstGraphicalGenerator;
import org.logicng.io.graphical.generators.FormulaDagGraphicalGenerator;
import org.logicng.io.graphical.generators.GraphGraphicalGenerator;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * A graphical representation of a formula, BDD, or graph.  The representation is a graph and holds the nodes and edges
 * for the graph, a background color (only used for DOT output), a flag whether all terminal nodes should
 * be on the same level (only used for DOT output) and a flag whether the graph is directed or undirected.
 * <p>
 * This is a generic representation which can be generated by one of the following generators:
 * <ul>
 *     <li>Formula AST: {@link FormulaAstGraphicalGenerator}
 *     <li>Formula DAG: {@link FormulaDagGraphicalGenerator}
 *     <li>BDD: {@link org.logicng.io.graphical.generators.BddGraphicalGenerator}
 *     <li>Graph: {@link GraphGraphicalGenerator}
 * </ul>
 * This representation can then be written in different output formats.  LogicNG currently supports two formats:
 * <ul>
 *     <li>DOT format: {@link GraphicalDotWriter}
 *     <li>Mermaid.js format: {@link GraphicalMermaidWriter}
 * </ul>
 * @version 2.4.0
 * @since 2.4.0
 */
public class GraphicalRepresentation {
    private final boolean alignTerminals;
    private final boolean directed;
    private final GraphicalColor background;
    private final List<GraphicalNode> nodes;
    private final List<GraphicalEdge> edges;

    /**
     * Generates a new graphical representation with the given values and no background.
     * @param alignTerminals a flag whether all terminal nodes should be aligned on the same level (only possible in DOT output)
     * @param directed       a flag whether the graph of the representation is directed or undirected
     */
    public GraphicalRepresentation(final boolean alignTerminals, final boolean directed) {
        this(alignTerminals, directed, null, new ArrayList<>(), new ArrayList<>());
    }

    /**
     * Generates a new graphical representation with the given values.
     * @param alignTerminals a flag whether all terminal nodes should be aligned on the same level (only possible in DOT output)
     * @param directed       a flag whether the graph of the representation is directed or undirected
     * @param background     the background color (only possible in DOT output)
     */
    public GraphicalRepresentation(final boolean alignTerminals, final boolean directed, final GraphicalColor background) {
        this(alignTerminals, directed, background, new ArrayList<>(), new ArrayList<>());
    }

    /**
     * Generates a new graphical representation with the given values.
     * @param alignTerminals a flag whether all terminal nodes should be aligned on the same level (only possible in DOT output)
     * @param directed       a flag whether the graph of the representation is directed or undirected
     * @param background     the background color (only possible in DOT output)
     * @param nodes          the nodes of the graph
     * @param edges          the edges of the graph
     */
    public GraphicalRepresentation(final boolean alignTerminals, final boolean directed, final GraphicalColor background, final List<GraphicalNode> nodes,
                                   final List<GraphicalEdge> edges) {
        this.alignTerminals = alignTerminals;
        this.directed = directed;
        this.background = background;
        this.nodes = nodes;
        this.edges = edges;
    }

    /**
     * Adds a node to this graph.
     * @param node the new node
     */
    public void addNode(final GraphicalNode node) {
        this.nodes.add(node);
    }

    /**
     * Adds an edge to this graph.
     * @param edge the new edge
     */
    public void addEdge(final GraphicalEdge edge) {
        this.edges.add(edge);
    }

    /**
     * Writes this representation to a file with the given file name using the given writer.
     * @param fileName the file name
     * @param writer   the writer
     * @throws IOException if there is a problem writing the file
     */
    public void write(final String fileName, final GraphicalRepresentationWriter writer) throws IOException {
        writer.write(fileName, this);
    }

    /**
     * Writes this representation to the given file using the given writer.
     * @param file   the file
     * @param writer the writer
     * @throws IOException if there is a problem writing the file
     */
    public void write(final File file, final GraphicalRepresentationWriter writer) throws IOException {
        writer.write(file, this);
    }

    /**
     * Returns this representation as a string using the given writer.
     * @param writer the writer
     * @return this representation as a string
     */
    public String writeString(final GraphicalRepresentationWriter writer) {
        return writer.stringValue(this);
    }

    /**
     * Returns whether all terminal nodes should be aligned on the same level (only possible in DOT output).
     * @return {@code true} if all terminal nodes should be aligned on the same level (only possible in DOT output), otherwise {@code false}
     */
    public boolean isAlignTerminals() {
        return this.alignTerminals;
    }

    /**
     * Returns whether the graph of the representation is directed or undirected.
     * @return {@code true} if the graph of the representation is directed, otherwise {@code false}
     */
    public boolean isDirected() {
        return this.directed;
    }

    /**
     * Returns the background color (only possible in DOT output).
     * @return the background color (only possible in DOT output)
     */
    public GraphicalColor getBackground() {
        return this.background;
    }

    /**
     * Returns the nodes of this graph.
     * @return the nodes of this graph
     */
    public List<GraphicalNode> getNodes() {
        return this.nodes;
    }

    /**
     * Returns the terminal nodes of this graph.
     * @return the terminal nodes of this graph
     */
    public List<GraphicalNode> getTerminalNodes() {
        return this.nodes.stream().filter(GraphicalNode::isTerminal).collect(Collectors.toList());
    }

    /**
     * Returns the non-terminal nodes of this graph.
     * @return the non-terminal nodes of this graph
     */
    public List<GraphicalNode> getNonTerminalNodes() {
        return this.nodes.stream().filter(n -> !n.isTerminal()).collect(Collectors.toList());
    }

    /**
     * Returns the edges of this graph.
     * @return the edges of this graph
     */
    public List<GraphicalEdge> getEdges() {
        return this.edges;
    }

    @Override
    public String toString() {
        return "GraphicalRepresentation{" +
                "alignTerminals=" + this.alignTerminals +
                ", directed=" + this.directed +
                ", background=" + this.background +
                ", nodes=" + this.nodes.stream().map(n -> n.getId() + ":" + n.getLabel()).collect(Collectors.joining(", ")) +
                ", edges=" + this.edges.stream().map(e -> e.getSource().getLabel() + " -- "
                + e.getDestination().getLabel()).collect(Collectors.joining(", ")) +
                '}';
    }
}
