/*
 * 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 "schematicclipboarddatabuilder.h"

#include "schematicgraphicsscene.h"
#include "schematicselectionquery.h"

#include <librepcb/core/fileio/transactionalfilesystem.h>
#include <librepcb/core/library/cmp/component.h>
#include <librepcb/core/library/sym/symbol.h>
#include <librepcb/core/project/circuit/bus.h>
#include <librepcb/core/project/circuit/circuit.h>
#include <librepcb/core/project/circuit/componentinstance.h>
#include <librepcb/core/project/circuit/componentsignalinstance.h>
#include <librepcb/core/project/circuit/netsignal.h>
#include <librepcb/core/project/project.h>
#include <librepcb/core/project/schematic/items/si_busjunction.h>
#include <librepcb/core/project/schematic/items/si_buslabel.h>
#include <librepcb/core/project/schematic/items/si_bussegment.h>
#include <librepcb/core/project/schematic/items/si_image.h>
#include <librepcb/core/project/schematic/items/si_netlabel.h>
#include <librepcb/core/project/schematic/items/si_netpoint.h>
#include <librepcb/core/project/schematic/items/si_netsegment.h>
#include <librepcb/core/project/schematic/items/si_polygon.h>
#include <librepcb/core/project/schematic/items/si_symbol.h>
#include <librepcb/core/project/schematic/items/si_symbolpin.h>
#include <librepcb/core/project/schematic/items/si_text.h>
#include <librepcb/core/project/schematic/schematic.h>
#include <librepcb/core/project/schematic/schematicnetsegmentsplitter.h>

#include <QtCore>
#include <QtWidgets>

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

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

SchematicClipboardDataBuilder::SchematicClipboardDataBuilder(
    SchematicGraphicsScene& scene) noexcept
  : mScene(scene) {
}

SchematicClipboardDataBuilder::~SchematicClipboardDataBuilder() noexcept {
}

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

std::unique_ptr<SchematicClipboardData> SchematicClipboardDataBuilder::generate(
    const Point& cursorPos) const noexcept {
  std::unique_ptr<SchematicClipboardData> data(new SchematicClipboardData(
      mScene.getSchematic().getUuid(), cursorPos,
      mScene.getSchematic().getProject().getCircuit().getAssemblyVariants()));

  // Get all selected items
  SchematicSelectionQuery query(mScene);
  query.addSelectedSymbols();
  query.addSelectedBusLines();
  query.addSelectedBusLabels();
  query.addSelectedNetLines();
  query.addSelectedNetLabels();
  query.addSelectedPolygons();
  query.addSelectedSchematicTexts();
  query.addSelectedImages();
  query.addJunctionsOfBusLines();
  query.addNetPointsOfNetLines();

  // Add components
  foreach (SI_Symbol* symbol, query.getSymbols()) {
    // Components with multiple symbols (gates) shall be added only once.
    if (data->getComponentInstances().contains(
            symbol->getComponentInstance().getUuid())) {
      continue;
    }
    std::unique_ptr<TransactionalDirectory> dir = data->getDirectory(
        "cmp/" %
        symbol->getComponentInstance().getLibComponent().getUuid().toStr());
    if (dir->getFiles().isEmpty()) {
      symbol->getComponentInstance().getLibComponent().getDirectory().copyTo(
          *dir);
    }
    data->getComponentInstances().append(
        std::make_shared<SchematicClipboardData::ComponentInstance>(
            symbol->getComponentInstance().getUuid(),
            symbol->getComponentInstance().getLibComponent().getUuid(),
            symbol->getComponentInstance().getSymbolVariant().getUuid(),
            symbol->getComponentInstance().getName(),
            symbol->getComponentInstance().getValue(),
            symbol->getComponentInstance().getAttributes(),
            symbol->getComponentInstance().getAssemblyOptions(),
            symbol->getComponentInstance().getLockAssembly()));
  }

  // Add symbols
  foreach (SI_Symbol* symbol, query.getSymbols()) {
    std::unique_ptr<TransactionalDirectory> dir =
        data->getDirectory("sym/" % symbol->getLibSymbol().getUuid().toStr());
    if (dir->getFiles().isEmpty()) {
      symbol->getLibSymbol().getDirectory().copyTo(*dir);
    }
    TextList texts;
    foreach (const SI_Text* t, symbol->getTexts()) {
      texts.append(std::make_shared<Text>(t->getTextObj()));
    }
    data->getSymbolInstances().append(
        std::make_shared<SchematicClipboardData::SymbolInstance>(
            symbol->getUuid(), symbol->getComponentInstance().getUuid(),
            symbol->getCompSymbVarItem().getUuid(), symbol->getPosition(),
            symbol->getRotation(), symbol->getMirrored(), texts));
  }

  // Add (splitted) bus segments including junctions, lines and labels
  QHash<SI_BusSegment*, SchematicSelectionQuery::BusSegmentItems>
      busSegmentItems = query.getBusSegmentItems();
  for (auto it = busSegmentItems.constBegin(); it != busSegmentItems.constEnd();
       ++it) {
    const Bus& bus = it.key()->getBus();
    if (!data->getBuses().contains(bus.getUuid())) {
      data->getBuses().append(std::make_shared<SchematicClipboardData::Bus>(
          bus.getUuid(), bus.getName(), bus.getPrefixNetNames(),
          bus.getMaxTraceLengthDifference()));
    }

    SchematicNetSegmentSplitter splitter;
    foreach (SI_BusJunction* junction, it.value().junctions) {
      splitter.addJunction(junction->getJunction());
    }
    foreach (SI_BusLine* line, it.value().lines) {
      splitter.addNetLine(line->getNetLine());
    }
    foreach (SI_BusLabel* label, it.value().labels) {
      splitter.addNetLabel(label->getNetLabel());
    }
    foreach (const SchematicNetSegmentSplitter::Segment& segment,
             splitter.split()) {
      std::shared_ptr<SchematicClipboardData::BusSegment> newSegment =
          std::make_shared<SchematicClipboardData::BusSegment>(
              it.key()->getUuid(), it.key()->getBus().getUuid());
      newSegment->junctions = segment.junctions;
      newSegment->lines = segment.netlines;
      newSegment->labels = segment.netlabels;
      data->getBusSegments().append(newSegment);
    }
  }

  // Add (splitted) net segments including netpoints, netlines and netlabels
  QHash<SI_NetSegment*, SchematicSelectionQuery::NetSegmentItems>
      netSegmentItems = query.getNetSegmentItems();
  for (auto it = netSegmentItems.constBegin(); it != netSegmentItems.constEnd();
       ++it) {
    SchematicNetSegmentSplitter splitter;
    foreach (SI_SymbolPin* pin, it.key()->getAllConnectedPins()) {
      const bool replacePin = !query.getSymbols().contains(&pin->getSymbol());
      splitter.addFixedAnchor(pin->toNetLineAnchor(), pin->getPosition(),
                              replacePin);
    }
    foreach (SI_BusJunction* bj, it.key()->getAllConnectedBusJunctions()) {
      const bool replaceJunction = !query.getBusJunctions().contains(bj);
      splitter.addFixedAnchor(bj->toNetLineAnchor(), bj->getPosition(),
                              replaceJunction);
    }
    foreach (SI_NetPoint* netpoint, it.value().netpoints) {
      splitter.addJunction(netpoint->getJunction());
    }
    foreach (SI_NetLine* netline, it.value().netlines) {
      splitter.addNetLine(netline->getNetLine());
    }
    foreach (SI_NetLabel* netlabel, it.value().netlabels) {
      splitter.addNetLabel(netlabel->getNetLabel());
    }
    foreach (const SchematicNetSegmentSplitter::Segment& segment,
             splitter.split()) {
      std::shared_ptr<SchematicClipboardData::NetSegment> newSegment =
          std::make_shared<SchematicClipboardData::NetSegment>(
              it.key()->getNetSignal().getName());
      newSegment->junctions = segment.junctions;
      newSegment->lines = segment.netlines;
      newSegment->labels = segment.netlabels;
      data->getNetSegments().append(newSegment);
    }
  }

  // Add polygons
  foreach (SI_Polygon* polygon, query.getPolygons()) {
    data->getPolygons().append(
        std::make_shared<Polygon>(polygon->getPolygon()));
  }

  // Add texts
  foreach (SI_Text* text, query.getTexts()) {
    data->getTexts().append(std::make_shared<Text>(text->getTextObj()));
  }

  // Add images
  std::unique_ptr<TransactionalDirectory> dir = data->getDirectory();
  foreach (SI_Image* image, query.getImages()) {
    const QString fileName = *image->getImage()->getFileName();
    const QByteArray content =
        mScene.getSchematic().getDirectory().readIfExists(fileName);
    if (!content.isNull()) {
      dir->write(fileName, content);
    }
    data->getImages().append(std::make_shared<Image>(*image->getImage()));
  }

  return data;
}

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

}  // namespace editor
}  // namespace librepcb
