/*
 * LibrePCB - Professional EDA for everyone!
 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
 * https://librepcb.org/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*******************************************************************************
 *  Includes
 ******************************************************************************/
#include "projectloader.h"

#include "../application.h"
#include "../fileio/versionfile.h"
#include "../library/cmp/component.h"
#include "../library/dev/device.h"
#include "../library/dev/part.h"
#include "../library/pkg/package.h"
#include "../library/sym/symbol.h"
#include "../serialization/fileformatmigration.h"
#include "../types/pcbcolor.h"
#include "board/board.h"
#include "board/boarddesignrules.h"
#include "board/boardfabricationoutputsettings.h"
#include "board/drc/boarddesignrulechecksettings.h"
#include "board/items/bi_device.h"
#include "board/items/bi_footprintpad.h"
#include "board/items/bi_hole.h"
#include "board/items/bi_netline.h"
#include "board/items/bi_netpoint.h"
#include "board/items/bi_netsegment.h"
#include "board/items/bi_plane.h"
#include "board/items/bi_polygon.h"
#include "board/items/bi_stroketext.h"
#include "board/items/bi_via.h"
#include "board/items/bi_zone.h"
#include "circuit/assemblyvariant.h"
#include "circuit/circuit.h"
#include "circuit/componentinstance.h"
#include "circuit/componentsignalinstance.h"
#include "circuit/netclass.h"
#include "circuit/netsignal.h"
#include "erc/electricalrulecheck.h"
#include "project.h"
#include "projectlibrary.h"
#include "schematic/items/si_netlabel.h"
#include "schematic/items/si_netline.h"
#include "schematic/items/si_netpoint.h"
#include "schematic/items/si_netsegment.h"
#include "schematic/items/si_polygon.h"
#include "schematic/items/si_symbol.h"
#include "schematic/items/si_symbolpin.h"
#include "schematic/items/si_text.h"
#include "schematic/schematic.h"

#include <QtCore>

/*******************************************************************************
 *  Namespace
 ******************************************************************************/
namespace librepcb {

/*******************************************************************************
 *  Constructors / Destructor
 ******************************************************************************/

ProjectLoader::ProjectLoader(QObject* parent) noexcept
  : QObject(parent), mAutoAssignDeviceModels(false) {
}

ProjectLoader::~ProjectLoader() noexcept {
}

/*******************************************************************************
 *  General Methods
 ******************************************************************************/

std::unique_ptr<Project> ProjectLoader::open(
    std::unique_ptr<TransactionalDirectory> directory,
    const QString& filename) {
  Q_ASSERT(directory);
  mUpgradeMessages = tl::nullopt;

  QElapsedTimer timer;
  timer.start();
  const FilePath fp = directory->getAbsPath(filename);
  qDebug().nospace() << "Open project " << fp.toNative() << "...";

  // Check if the project file exists.
  if (!directory->fileExists(filename)) {
    throw RuntimeError(__FILE__, __LINE__,
                       tr("File does not exist: '%1'").arg(fp.toNative()));
  }

  // Read the file format version.
  if (!directory->fileExists(".librepcb-project")) {
    throw RuntimeError(__FILE__, __LINE__,
                       tr("Directory does not contain a LibrePCB project: '%1'")
                           .arg(directory->getAbsPath().toNative()));
  }
  const Version fileFormat =
      VersionFile::fromByteArray(directory->read(".librepcb-project"))
          .getVersion();
  qDebug().noquote() << "Detected project file format:" << fileFormat.toStr();

  // Check file format version.
  if (fileFormat > Application::getFileFormatVersion()) {
    throw RuntimeError(
        __FILE__, __LINE__,
        tr("This project was created with a newer application version.\n"
           "You need at least LibrePCB %1 to open it.\n\n%2")
            .arg(fileFormat.toPrettyStr(3))
            .arg(fp.toNative()));
  }

  // Upgrate file format, if needed.
  for (auto migration : FileFormatMigration::getMigrations(fileFormat)) {
    if (!mUpgradeMessages) {
      mUpgradeMessages = QList<FileFormatMigration::Message>();
    }
    qInfo().nospace().noquote()
        << "Project file format is outdated, upgrading from v"
        << migration->getFromVersion().toStr() << " to v"
        << migration->getToVersion().toStr() << "...";
    migration->upgradeProject(*directory, *mUpgradeMessages);
  }

  // Load project.
  std::unique_ptr<Project> p(new Project(std::move(directory), filename));
  loadMetadata(*p);
  loadSettings(*p);
  loadOutputJobs(*p);
  loadLibrary(*p);
  loadCircuit(*p);
  loadErc(*p);
  loadSchematics(*p);
  loadBoards(*p);

  // If the file format was migrated, clean up obsolete ERC messages.
  if (mUpgradeMessages) {
    qInfo() << "Running ERC to clean up obsolete message approvals...";
    ElectricalRuleCheck erc(*p);
    const RuleCheckMessageList msgs = erc.runChecks();
    const QSet<SExpression> approvals = RuleCheckMessage::getAllApprovals(msgs);
    p->setErcMessageApprovals(p->getErcMessageApprovals() & approvals);
  }

  // Make sure the files are formatted correctly. Also handle possible errors
  // during serialization now instead of later.
  if (mUpgradeMessages) {
    p->save();
  }

  // Done!
  qDebug() << "Successfully opened project in" << timer.elapsed() << "ms.";
  return p;
}

/*******************************************************************************
 *  Private Methods
 ******************************************************************************/

void ProjectLoader::loadMetadata(Project& p) {
  qDebug() << "Load project metadata...";
  const QString fp = "project/metadata.lp";
  SExpression root = SExpression::parse(p.getDirectory().read(fp),
                                        p.getDirectory().getAbsPath(fp));

  p.setUuid(deserialize<Uuid>(root.getChild("@0")));
  p.setName(deserialize<ElementName>(root.getChild("name/@0")));
  p.setAuthor(root.getChild("author/@0").getValue());
  p.setVersion(deserialize<FileProofName>(root.getChild("version/@0")));
  p.setCreated(deserialize<QDateTime>(root.getChild("created/@0")));
  p.setAttributes(AttributeList(root));

  qDebug() << "Successfully loaded project metadata.";
}

void ProjectLoader::loadSettings(Project& p) {
  qDebug() << "Load project settings...";
  const QString fp = "project/settings.lp";
  const SExpression root = SExpression::parse(p.getDirectory().read(fp),
                                              p.getDirectory().getAbsPath(fp));

  {
    QStringList l;
    foreach (const SExpression* node,
             root.getChild("library_locale_order").getChildren("locale")) {
      l.append(node->getChild("@0").getValue());
    }
    p.setLocaleOrder(l);
  }

  {
    QStringList l;
    foreach (const SExpression* node,
             root.getChild("library_norm_order").getChildren("norm")) {
      l.append(node->getChild("@0").getValue());
    }
    p.setNormOrder(l);
  }

  {
    QStringList l;
    foreach (const SExpression* node,
             root.getChild("custom_bom_attributes").getChildren("attribute")) {
      l.append(node->getChild("@0").getValue());
    }
    p.setCustomBomAttributes(l);
  }

  p.setDefaultLockComponentAssembly(
      deserialize<bool>(root.getChild("default_lock_component_assembly/@0")));

  qDebug() << "Successfully loaded project settings.";
}

void ProjectLoader::loadOutputJobs(Project& p) {
  qDebug() << "Load output jobs...";
  const QString fp = "project/jobs.lp";
  const SExpression root = SExpression::parse(p.getDirectory().read(fp),
                                              p.getDirectory().getAbsPath(fp));
  p.getOutputJobs() = deserialize<OutputJobList>(root);
  qDebug() << "Successfully loaded output jobs.";
}

void ProjectLoader::loadLibrary(Project& p) {
  qDebug() << "Load project library...";

  loadLibraryElements<Symbol>(p, "sym", "symbols", &ProjectLibrary::addSymbol);
  loadLibraryElements<Package>(p, "pkg", "packages",
                               &ProjectLibrary::addPackage);
  loadLibraryElements<Component>(p, "cmp", "components",
                                 &ProjectLibrary::addComponent);
  loadLibraryElements<Device>(p, "dev", "devices", &ProjectLibrary::addDevice);

  qDebug() << "Successfully loaded project library.";
}

template <typename ElementType>
void ProjectLoader::loadLibraryElements(
    Project& p, const QString& dirname, const QString& type,
    void (ProjectLibrary::*addFunction)(ElementType&)) {
  // Search all subdirectories which have a valid UUID as directory name.
  int count = 0;
  foreach (const QString& sub, p.getLibrary().getDirectory().getDirs(dirname)) {
    std::unique_ptr<TransactionalDirectory> dir(new TransactionalDirectory(
        p.getLibrary().getDirectory(), dirname % "/" % sub));

    // Check if directory is a valid library element.
    if (!LibraryBaseElement::isValidElementDirectory<ElementType>(*dir, "")) {
      qWarning() << "Invalid directory in project library, ignoring it:"
                 << dir->getAbsPath().toNative();
      continue;
    }

    // Load the library element.
    ElementType* element =
        ElementType::open(std::move(dir)).release();  // can throw
    (p.getLibrary().*addFunction)(*element);
    ++count;
  }

  qDebug().nospace().noquote()
      << "Successfully loaded " << count << " " << type << ".";
}

void ProjectLoader::loadCircuit(Project& p) {
  qDebug() << "Load circuit...";
  const QString fp = "circuit/circuit.lp";
  SExpression root = SExpression::parse(p.getDirectory().read(fp),
                                        p.getDirectory().getAbsPath(fp));

  // Load assembly variants.
  foreach (const SExpression* node, root.getChildren("variant")) {
    auto av = std::make_shared<AssemblyVariant>(*node);
    p.getCircuit().addAssemblyVariant(av);
  }
  if (p.getCircuit().getAssemblyVariants().isEmpty()) {
    throw RuntimeError(__FILE__, __LINE__, "Project has no assembly variants.");
  }

  // Load net classes.
  foreach (const SExpression* node, root.getChildren("netclass")) {
    NetClass* netclass =
        new NetClass(p.getCircuit(), deserialize<Uuid>(node->getChild("@0")),
                     deserialize<ElementName>(node->getChild("name/@0")));
    p.getCircuit().addNetClass(*netclass);
  }

  // Load net signals.
  foreach (const SExpression* node, root.getChildren("net")) {
    const Uuid netclassUuid = deserialize<Uuid>(node->getChild("netclass/@0"));
    NetClass* netclass = p.getCircuit().getNetClasses().value(netclassUuid);
    if (!netclass) {
      throw RuntimeError(
          __FILE__, __LINE__,
          QString("Inexistent net class: '%1'").arg(netclassUuid.toStr()));
    }
    NetSignal* netsignal = new NetSignal(
        p.getCircuit(), deserialize<Uuid>(node->getChild("@0")), *netclass,
        deserialize<CircuitIdentifier>(node->getChild("name/@0")),
        deserialize<bool>(node->getChild("auto/@0")));
    p.getCircuit().addNetSignal(*netsignal);
  }

  // Load component instances.
  foreach (const SExpression* node, root.getChildren("component")) {
    const Uuid cmpUuid = deserialize<Uuid>(node->getChild("lib_component/@0"));
    const Component* libCmp = p.getLibrary().getComponent(cmpUuid);
    if (!libCmp) {
      throw RuntimeError(
          __FILE__, __LINE__,
          QString("The component '%1' does not exist in the project's library.")
              .arg(cmpUuid.toStr()));
    }
    ComponentInstance* cmp = new ComponentInstance(
        p.getCircuit(), deserialize<Uuid>(node->getChild("@0")), *libCmp,
        deserialize<Uuid>(node->getChild("lib_variant/@0")),
        deserialize<CircuitIdentifier>(node->getChild("name/@0")));
    cmp->setValue(node->getChild("value/@0").getValue());
    cmp->setAttributes(AttributeList(*node));
    cmp->setAssemblyOptions(ComponentAssemblyOptionList(*node));
    cmp->setLockAssembly(deserialize<bool>(node->getChild("lock_assembly/@0")));
    p.getCircuit().addComponentInstance(*cmp);

    QSet<Uuid> loadedSignals;
    foreach (const SExpression* child, node->getChildren("signal")) {
      const Uuid cmpSigUuid = deserialize<Uuid>(child->getChild("@0"));
      ComponentSignalInstance* cmpSig = cmp->getSignalInstance(cmpSigUuid);
      if (!cmpSig) {
        throw RuntimeError(__FILE__, __LINE__,
                           QString("Inexistent component signal: '%1'")
                               .arg(cmpSigUuid.toStr()));
      }
      if (loadedSignals.contains(cmpSigUuid)) {
        throw RuntimeError(__FILE__, __LINE__,
                           QString("The signal '%1' is defined multiple times.")
                               .arg(cmpSigUuid.toStr()));
      }
      loadedSignals.insert(cmpSigUuid);
      if (const tl::optional<Uuid> netSignalUuid =
              deserialize<tl::optional<Uuid>>(child->getChild("net/@0"))) {
        NetSignal* netSignal =
            p.getCircuit().getNetSignals().value(*netSignalUuid);
        if (!netSignal) {
          throw RuntimeError(__FILE__, __LINE__,
                             QString("Inexistent net signal: '%1'")
                                 .arg(netSignalUuid->toStr()));
        }
        cmpSig->setNetSignal(netSignal);
      }
    }
    if (loadedSignals.count() != cmp->getSignals().count()) {
      qCritical() << "Signal count mismatch:" << loadedSignals.count()
                  << "!=" << cmp->getSignals().count();
      throw RuntimeError(
          __FILE__, __LINE__,
          QString("The signal count of the component instance '%1' does "
                  "not match with the signal count of the component '%2'.")
              .arg(cmp->getUuid().toStr())
              .arg(libCmp->getUuid().toStr()));
    }
  }

  qDebug() << "Successfully loaded circuit.";
}

void ProjectLoader::loadErc(Project& p) {
  qDebug() << "Load ERC approvals...";
  const QString fp = "circuit/erc.lp";
  const SExpression root = SExpression::parse(p.getDirectory().read(fp),
                                              p.getDirectory().getAbsPath(fp));

  // Load approvals.
  QSet<SExpression> approvals;
  foreach (const SExpression* node, root.getChildren("approved")) {
    approvals.insert(*node);
  }
  p.setErcMessageApprovals(approvals);

  qDebug() << "Successfully loaded ERC approvals.";
}

void ProjectLoader::loadSchematics(Project& p) {
  qDebug() << "Load schematics...";
  const QString fp = "schematics/schematics.lp";
  const SExpression indexRoot = SExpression::parse(
      p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
  foreach (const SExpression* indexNode, indexRoot.getChildren("schematic")) {
    loadSchematic(p, indexNode->getChild("@0").getValue());
  }
  qDebug() << "Successfully loaded" << p.getSchematics().count()
           << "schematics.";
}

void ProjectLoader::loadSchematic(Project& p, const QString& relativeFilePath) {
  const FilePath fp = FilePath::fromRelative(p.getPath(), relativeFilePath);
  std::unique_ptr<TransactionalDirectory> dir(new TransactionalDirectory(
      p.getDirectory(), fp.getParentDir().toRelative(p.getPath())));
  const SExpression root = SExpression::parse(dir->read(fp.getFilename()), fp);

  Schematic* schematic =
      new Schematic(p, std::move(dir), fp.getParentDir().getFilename(),
                    deserialize<Uuid>(root.getChild("@0")),
                    deserialize<ElementName>(root.getChild("name/@0")));
  schematic->setGridInterval(
      deserialize<PositiveLength>(root.getChild("grid/interval/@0")));
  schematic->setGridUnit(
      deserialize<LengthUnit>(root.getChild("grid/unit/@0")));
  p.addSchematic(*schematic);

  foreach (const SExpression* node, root.getChildren("symbol")) {
    loadSchematicSymbol(*schematic, *node);
  }
  foreach (const SExpression* node, root.getChildren("netsegment")) {
    loadSchematicNetSegment(*schematic, *node);
  }
  foreach (const SExpression* node, root.getChildren("polygon")) {
    SI_Polygon* polygon = new SI_Polygon(*schematic, Polygon(*node));
    schematic->addPolygon(*polygon);
  }
  foreach (const SExpression* node, root.getChildren("text")) {
    SI_Text* text = new SI_Text(*schematic, Text(*node));
    schematic->addText(*text);
  }
}

void ProjectLoader::loadSchematicSymbol(Schematic& s, const SExpression& node) {
  const Uuid cmpUuid = deserialize<Uuid>(node.getChild("component/@0"));
  ComponentInstance* cmp =
      s.getProject().getCircuit().getComponentInstanceByUuid(cmpUuid);
  if (!cmp) {
    throw RuntimeError(
        __FILE__, __LINE__,
        QString("The component '%1' does not exist in the circuit.")
            .arg(cmpUuid.toStr()));
  }
  SI_Symbol* symbol =
      new SI_Symbol(s, deserialize<Uuid>(node.getChild("@0")), *cmp,
                    deserialize<Uuid>(node.getChild("lib_gate/@0")),
                    Point(node.getChild("position")),
                    deserialize<Angle>(node.getChild("rotation/@0")),
                    deserialize<bool>(node.getChild("mirror/@0")), false);
  foreach (const SExpression* child, node.getChildren("text")) {
    symbol->addText(*new SI_Text(s, Text(*child)));
  }
  s.addSymbol(*symbol);
}

void ProjectLoader::loadSchematicNetSegment(Schematic& s,
                                            const SExpression& node) {
  const Uuid netSignalUuid = deserialize<Uuid>(node.getChild("net/@0"));
  NetSignal* netSignal =
      s.getProject().getCircuit().getNetSignals().value(netSignalUuid);
  if (!netSignal) {
    throw RuntimeError(
        __FILE__, __LINE__,
        QString("Inexistent net signal: '%1'").arg(netSignalUuid.toStr()));
  }
  SI_NetSegment* netSegment =
      new SI_NetSegment(s, deserialize<Uuid>(node.getChild("@0")), *netSignal);
  s.addNetSegment(*netSegment);

  // Load net points.
  QList<SI_NetPoint*> netPoints;
  foreach (const SExpression* child, node.getChildren("junction")) {
    SI_NetPoint* netpoint =
        new SI_NetPoint(*netSegment, deserialize<Uuid>(child->getChild("@0")),
                        Point(child->getChild("position")));
    netPoints.append(netpoint);
  }

  // Load net lines.
  QList<SI_NetLine*> netLines;
  foreach (const SExpression* child, node.getChildren("line")) {
    auto parseAnchor = [&s, &netPoints](const SExpression& aNode) {
      SI_NetLineAnchor* anchor = nullptr;
      if (const SExpression* junctionNode = aNode.tryGetChild("junction")) {
        const Uuid netPointUuid =
            deserialize<Uuid>(junctionNode->getChild("@0"));
        foreach (SI_NetPoint* netPoint, netPoints) {
          if (netPoint->getUuid() == netPointUuid) {
            anchor = netPoint;
            break;
          }
        }
        if (!anchor) {
          throw RuntimeError(
              __FILE__, __LINE__,
              QString("Net point '%1' does not exist in schematic.")
                  .arg(netPointUuid.toStr()));
        }
      } else {
        const Uuid symbolUuid = deserialize<Uuid>(aNode.getChild("symbol/@0"));
        SI_Symbol* symbol = s.getSymbols().value(symbolUuid);
        if (!symbol) {
          throw RuntimeError(__FILE__, __LINE__,
                             QString("Symbol '%1' does not exist in schematic.")
                                 .arg(symbolUuid.toStr()));
        }
        const Uuid pinUuid = deserialize<Uuid>(aNode.getChild("pin/@0"));
        anchor = symbol->getPin(pinUuid);
        if (!anchor) {
          throw RuntimeError(
              __FILE__, __LINE__,
              QString("Symbol pin '%1' does not exist in schematic.")
                  .arg(pinUuid.toStr()));
        }
      }
      return anchor;
    };
    SI_NetLine* netLine = new SI_NetLine(
        *netSegment, deserialize<Uuid>(child->getChild("@0")),
        *parseAnchor(child->getChild("from")),
        *parseAnchor(child->getChild("to")),
        deserialize<UnsignedLength>(child->getChild("width/@0")));
    netLines.append(netLine);
  }

  // Add net points & net lines.
  netSegment->addNetPointsAndNetLines(netPoints, netLines);

  // Load net labels.
  foreach (const SExpression* child, node.getChildren("label")) {
    SI_NetLabel* netLabel = new SI_NetLabel(*netSegment, NetLabel(*child));
    netSegment->addNetLabel(*netLabel);
  }
}

void ProjectLoader::loadBoards(Project& p) {
  qDebug() << "Load boards...";
  const QString fp = "boards/boards.lp";
  const SExpression indexRoot = SExpression::parse(
      p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
  foreach (const SExpression* node, indexRoot.getChildren("board")) {
    loadBoard(p, node->getChild("@0").getValue());
  }
  qDebug() << "Successfully loaded" << p.getBoards().count() << "boards.";
}

void ProjectLoader::loadBoard(Project& p, const QString& relativeFilePath) {
  const FilePath fp = FilePath::fromRelative(p.getPath(), relativeFilePath);
  std::unique_ptr<TransactionalDirectory> dir(new TransactionalDirectory(
      p.getDirectory(), fp.getParentDir().toRelative(p.getPath())));
  const SExpression root = SExpression::parse(dir->read(fp.getFilename()), fp);

  Board* board = new Board(p, std::move(dir), fp.getParentDir().getFilename(),
                           deserialize<Uuid>(root.getChild("@0")),
                           deserialize<ElementName>(root.getChild("name/@0")));
  board->setGridInterval(
      deserialize<PositiveLength>(root.getChild("grid/interval/@0")));
  board->setGridUnit(deserialize<LengthUnit>(root.getChild("grid/unit/@0")));
  board->setDefaultFontName(root.getChild("default_font/@0").getValue());
  board->setInnerLayerCount(
      deserialize<uint>(root.getChild("layers/inner/@0")));
  board->setPcbThickness(
      deserialize<PositiveLength>(root.getChild("thickness/@0")));
  board->setSolderResist(
      deserialize<const PcbColor*>(root.getChild("solder_resist/@0")));
  board->setSilkscreenColor(
      deserialize<const PcbColor&>(root.getChild("silkscreen/@0")));
  {
    QVector<const Layer*> layers;
    foreach (const SExpression* child,
             root.getChild("silkscreen_layers_top")
                 .getChildren(SExpression::Type::Token)) {
      layers.append(deserialize<const Layer*>(*child));
    }
    board->setSilkscreenLayersTop(layers);
  }
  {
    QVector<const Layer*> layers;
    foreach (const SExpression* child,
             root.getChild("silkscreen_layers_bot")
                 .getChildren(SExpression::Type::Token)) {
      layers.append(deserialize<const Layer*>(*child));
    }
    board->setSilkscreenLayersBot(layers);
  }
  board->setDesignRules(BoardDesignRules(root.getChild("design_rules")));
  {
    const SExpression& node = root.getChild("design_rule_check");
    const Version approvalsVersion =
        deserialize<Version>(node.getChild("approvals_version/@0"));
    QSet<SExpression> approvals;
    foreach (const SExpression* child, node.getChildren("approved")) {
      approvals.insert(*child);
    }
    board->setDrcSettings(BoardDesignRuleCheckSettings(node));
    board->loadDrcMessageApprovals(approvalsVersion, approvals);
  }
  board->getFabricationOutputSettings() = BoardFabricationOutputSettings(
      root.getChild("fabrication_output_settings"));
  p.addBoard(*board);

  foreach (const SExpression* node, root.getChildren("device")) {
    loadBoardDeviceInstance(*board, *node);
  }
  foreach (const SExpression* node, root.getChildren("netsegment")) {
    loadBoardNetSegment(*board, *node);
  }
  foreach (const SExpression* node, root.getChildren("plane")) {
    loadBoardPlane(*board, *node);
  }
  foreach (const SExpression* node, root.getChildren("zone")) {
    BI_Zone* zone = new BI_Zone(*board, BoardZoneData(*node));
    board->addZone(*zone);
  }
  foreach (const SExpression* node, root.getChildren("polygon")) {
    BI_Polygon* polygon = new BI_Polygon(*board, BoardPolygonData(*node));
    board->addPolygon(*polygon);
  }
  foreach (const SExpression* node, root.getChildren("stroke_text")) {
    BI_StrokeText* text = new BI_StrokeText(*board, BoardStrokeTextData(*node));
    board->addStrokeText(*text);
  }
  foreach (const SExpression* node, root.getChildren("hole")) {
    BI_Hole* hole = new BI_Hole(*board, BoardHoleData(*node));
    board->addHole(*hole);
  }

  // Load user settings.
  loadBoardUserSettings(*board);
}

void ProjectLoader::loadBoardDeviceInstance(Board& b, const SExpression& node) {
  const Uuid cmpUuid = deserialize<Uuid>(node.getChild("@0"));
  ComponentInstance* cmp =
      b.getProject().getCircuit().getComponentInstanceByUuid(cmpUuid);
  if (!cmp) {
    throw RuntimeError(
        __FILE__, __LINE__,
        QString("The component instace '%1' does not exist in the circuit.")
            .arg(cmpUuid.toStr()));
  }
  BI_Device* device =
      new BI_Device(b, *cmp, deserialize<Uuid>(node.getChild("lib_device/@0")),
                    deserialize<Uuid>(node.getChild("lib_footprint/@0")),
                    Point(node.getChild("position")),
                    deserialize<Angle>(node.getChild("rotation/@0")),
                    deserialize<bool>(node.getChild("flip/@0")),
                    deserialize<bool>(node.getChild("lock/@0")), false);
  device->setAttributes(AttributeList(node));
  {
    // Load 3D package model. But after upgrading the project library, assign
    // the default package model if no valid model is set on this device.
    tl::optional<Uuid> model =
        deserialize<tl::optional<Uuid>>(node.getChild("lib_3d_model/@0"));
    if (mAutoAssignDeviceModels &&
        ((!model) || (!device->getLibPackage().getModels().contains(*model)) ||
         (!device->getLibFootprint().getModels().contains(*model)))) {
      model = device->getDefaultLibModelUuid();
    }
    device->setModel(model);
  }
  foreach (const SExpression* child, node.getChildren("stroke_text")) {
    device->addStrokeText(*new BI_StrokeText(b, BoardStrokeTextData(*child)));
  }
  b.addDeviceInstance(*device);
}

void ProjectLoader::loadBoardNetSegment(Board& b, const SExpression& node) {
  const tl::optional<Uuid> netSignalUuid =
      deserialize<tl::optional<Uuid>>(node.getChild("net/@0"));
  NetSignal* netSignal = netSignalUuid
      ? b.getProject().getCircuit().getNetSignals().value(*netSignalUuid)
      : nullptr;
  if (netSignalUuid && (!netSignal)) {
    throw RuntimeError(
        __FILE__, __LINE__,
        QString("Inexistent net signal: '%1'").arg(netSignalUuid->toStr()));
  }
  BI_NetSegment* netSegment =
      new BI_NetSegment(b, deserialize<Uuid>(node.getChild("@0")), netSignal);
  b.addNetSegment(*netSegment);

  // Load vias.
  QList<BI_Via*> vias;
  foreach (const SExpression* child, node.getChildren("via")) {
    BI_Via* via = new BI_Via(*netSegment, Via(*child));
    vias.append(via);
  }

  // Load net points.
  QList<BI_NetPoint*> netPoints;
  foreach (const SExpression* child, node.getChildren("junction")) {
    BI_NetPoint* netPoint =
        new BI_NetPoint(*netSegment, deserialize<Uuid>(child->getChild("@0")),
                        Point(child->getChild("position")));
    netPoints.append(netPoint);
  }

  // Load net lines.
  QList<BI_NetLine*> netLines;
  foreach (const SExpression* child, node.getChildren("trace")) {
    auto parseAnchor = [&b, &vias, &netPoints](const SExpression& aNode) {
      BI_NetLineAnchor* anchor = nullptr;
      if (const SExpression* junctionNode = aNode.tryGetChild("junction")) {
        const Uuid netPointUuid =
            deserialize<Uuid>(junctionNode->getChild("@0"));
        foreach (BI_NetPoint* netPoint, netPoints) {
          if (netPoint->getUuid() == netPointUuid) {
            anchor = netPoint;
            break;
          }
        }
        if (!anchor) {
          throw RuntimeError(
              __FILE__, __LINE__,
              QString("Net point '%1' does not exist in schematic.")
                  .arg(netPointUuid.toStr()));
        }
      } else if (const SExpression* viaNode = aNode.tryGetChild("via")) {
        const Uuid viaUuid = deserialize<Uuid>(viaNode->getChild("@0"));
        foreach (BI_Via* via, vias) {
          if (via->getUuid() == viaUuid) {
            anchor = via;
            break;
          }
        }
        if (!anchor) {
          throw RuntimeError(__FILE__, __LINE__,
                             QString("Via '%1' does not exist in board.")
                                 .arg(viaUuid.toStr()));
        }
      } else {
        const Uuid deviceUuid = deserialize<Uuid>(aNode.getChild("device/@0"));
        BI_Device* device = b.getDeviceInstanceByComponentUuid(deviceUuid);
        if (!device) {
          throw RuntimeError(
              __FILE__, __LINE__,
              QString("Device instance '%1' does not exist in board.")
                  .arg(deviceUuid.toStr()));
        }
        const Uuid padUuid = deserialize<Uuid>(aNode.getChild("pad/@0"));
        anchor = device->getPad(padUuid);
        if (!anchor) {
          throw RuntimeError(
              __FILE__, __LINE__,
              QString("Footprint pad '%1' does not exist in board.")
                  .arg(padUuid.toStr()));
        }
      }
      return anchor;
    };
    BI_NetLine* netLine = new BI_NetLine(
        *netSegment, deserialize<Uuid>(child->getChild("@0")),
        *parseAnchor(child->getChild("from")),
        *parseAnchor(child->getChild("to")),
        deserialize<const Layer&>(child->getChild("layer/@0")),
        deserialize<PositiveLength>(child->getChild("width/@0")));
    netLines.append(netLine);
  }

  // Add vias, net points & net lines.
  netSegment->addElements(vias, netPoints, netLines);
}

void ProjectLoader::loadBoardPlane(Board& b, const SExpression& node) {
  const tl::optional<Uuid> netSignalUuid =
      deserialize<tl::optional<Uuid>>(node.getChild("net/@0"));
  NetSignal* netSignal = netSignalUuid
      ? b.getProject().getCircuit().getNetSignals().value(*netSignalUuid)
      : nullptr;
  if (netSignalUuid && (!netSignal)) {
    throw RuntimeError(
        __FILE__, __LINE__,
        QString("Inexistent net signal: '%1'").arg(netSignalUuid->toStr()));
  }
  BI_Plane* plane =
      new BI_Plane(b, deserialize<Uuid>(node.getChild("@0")),
                   deserialize<const Layer&>(node.getChild("layer/@0")),
                   netSignal, Path(node));
  plane->setMinWidth(
      deserialize<UnsignedLength>(node.getChild("min_width/@0")));
  plane->setMinClearance(
      deserialize<UnsignedLength>(node.getChild("min_clearance/@0")));
  plane->setKeepIslands(deserialize<bool>(node.getChild("keep_islands/@0")));
  plane->setPriority(deserialize<int>(node.getChild("priority/@0")));
  plane->setConnectStyle(
      deserialize<BI_Plane::ConnectStyle>(node.getChild("connect_style/@0")));
  plane->setThermalGap(
      deserialize<PositiveLength>(node.getChild("thermal_gap/@0")));
  plane->setThermalSpokeWidth(
      deserialize<PositiveLength>(node.getChild("thermal_spoke/@0")));
  b.addPlane(*plane);
}

void ProjectLoader::loadBoardUserSettings(Board& b) {
  try {
    const QString fp = "settings.user.lp";
    const SExpression root = SExpression::parse(
        b.getDirectory().read(fp), b.getDirectory().getAbsPath(fp));

    // Layers.
    QMap<QString, bool> layersVisibility;
    for (const SExpression* node : root.getChildren("layer")) {
      const QString name = node->getChild("@0").getValue();
      layersVisibility[name] = deserialize<bool>(node->getChild("visible/@0"));
    }
    b.setLayersVisibility(layersVisibility);

    // Planes visibility.
    foreach (const SExpression* node, root.getChildren("plane")) {
      const Uuid uuid = deserialize<Uuid>(node->getChild("@0"));
      if (BI_Plane* plane = b.getPlanes().value(uuid)) {
        plane->setVisible(deserialize<bool>(node->getChild("visible/@0")));
      } else {
        qWarning() << "Plane" << uuid.toStr()
                   << "doesn't exist, could not restore its visibility.";
      }
    }
  } catch (const Exception&) {
    // Project user settings are normally not put under version control and
    // thus the likelyhood of parse errors is higher (e.g. when switching to
    // an older, now incompatible revision). To avoid frustration, we just
    // ignore these errors and load the default settings instead...
    qCritical() << "Could not load board user settings, defaults will be "
                   "used instead.";
  }
}

/*******************************************************************************
 *  End of File
 ******************************************************************************/

}  // namespace librepcb
