/*
 * exporthelper.cpp
 * Copyright 2018, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
 *
 * This file is part of Tiled.
 *
 * 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 2 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/>.
 */

#include "exporthelper.h"

#include "mapobject.h"
#include "objectgroup.h"

namespace Tiled {

/**
 * @return the format options that should be used when writing the file.
 */
FileFormat::Options ExportHelper::formatOptions() const
{
    FileFormat::Options options;
    if (mOptions.testFlag(Preferences::ExportMinimized))
        options |= FileFormat::WriteMinimized;
    return options;
}

/**
 * Prepares a tileset for export.
 *
 * \a savingTileset means that this tileset is being saved to its own file
 * rather saved as part of a map. In this case, we want to apply some export
 * options that we would skip for external tilesets.
 */
SharedTileset ExportHelper::prepareExportTileset(const SharedTileset &tileset,
                                                 bool savingTileset) const
{
    const bool hasExportSettings = !(tileset->exportFileName.isEmpty()
                                     && tileset->exportFormat.isEmpty());

    if (!mOptions && !hasExportSettings)
        return tileset;

    // When the tileset is embedded we're effectively always saving it
    savingTileset |= !tileset->isExternal();

    if (!savingTileset && !mOptions.testFlag(Preferences::EmbedTilesets))
        return tileset; // Leave external tileset alone

    if (savingTileset && !(mOptions & (Preferences::DetachTemplateInstances |
                                       Preferences::ResolveObjectTypesAndProperties))
            && !hasExportSettings) {
        // We're saving this tileset as-is, so leave it alone
        return tileset;
    }

    // Either needs to be embedded or is already embedded and we may need to
    // make other changes to the tileset
    SharedTileset exportTileset = tileset->clone();
    exportTileset->setOriginalTileset(tileset);

    // We don't want to save the export options in the exported file
    if (hasExportSettings) {
        exportTileset->exportFileName.clear();
        exportTileset->exportFormat.clear();
    }

    if (mOptions.testFlag(Preferences::DetachTemplateInstances)) {
        for (Tile *tile : exportTileset->tiles()) {
            if (!tile->objectGroup())
                continue;

            for (MapObject *object : *tile->objectGroup())
                if (object->isTemplateInstance())
                    object->detachFromTemplate();
        }
    }

    if (mOptions.testFlag(Preferences::ResolveObjectTypesAndProperties)) {
        for (Tile *tile : exportTileset->tiles()) {
            if (!tile->objectGroup())
                continue;

            for (MapObject *object : *tile->objectGroup())
                resolveTypeAndProperties(object);
        }
    }

    return exportTileset;
}

const Map *ExportHelper::prepareExportMap(const Map *map, std::unique_ptr<Map> &exportMap) const
{
    const bool hasExportSettings = !(map->exportFileName.isEmpty()
                                     && map->exportFormat.isEmpty());

    // If no export options are active, return the same map
    if (!(mOptions & ~Preferences::ExportMinimized) && !hasExportSettings)
        return map;

    // Make a copy to which export options are applied
    exportMap = map->clone();

    // We don't want to save the export options in the exported file
    if (hasExportSettings) {
        exportMap->exportFileName.clear();
        exportMap->exportFormat.clear();
    }

    if (mOptions.testFlag(Preferences::DetachTemplateInstances)) {
        for (Layer *layer : exportMap->objectGroups()) {
            for (MapObject *object : *static_cast<ObjectGroup*>(layer)) {
                if (object->isTemplateInstance()) {
                    // In case of templated tile objects, the map may not yet
                    // have a reference to the used tileset.
                    if (Tile *tile = object->cell().tile())
                        exportMap->addTileset(tile->tileset()->sharedFromThis());

                    object->detachFromTemplate();
                }
            }
        }
    }

    if (mOptions.testFlag(Preferences::ResolveObjectTypesAndProperties))
        for (Layer *layer : exportMap->objectGroups())
            for (MapObject *object : *static_cast<ObjectGroup*>(layer))
                resolveTypeAndProperties(object);

    const auto tilesets = exportMap->tilesets();    // needs a copy
    for (const SharedTileset &tileset : tilesets) {
        auto exportTileset = prepareExportTileset(tileset, false);
        if (exportTileset != tileset)
            exportMap->replaceTileset(tileset, exportTileset);
    }

    // Return a pointer to the copy
    return exportMap.get();
}

void ExportHelper::resolveTypeAndProperties(MapObject *object) const
{
    Tile *tile = object->cell().tile();

    // Inherit type from tile if not set on object (not inheriting
    // type from tile of tile object template here, for that the
    // "Detach templates" option needs to be used as well)
    if (object->type().isEmpty() && tile &&
            (!object->isTemplateInstance() || object->propertyChanged(MapObject::CellProperty)))
        object->setType(tile->type());

    Properties properties;

    // Inherit properties from type
    if (!object->type().isEmpty()) {
        for (int i = Object::objectTypes().size() - 1; i >= 0; --i) {
            auto const &type = Object::objectTypes().at(i);
            if (type.name == object->type())
                mergeProperties(properties, type.defaultProperties);
        }
    }

    // Inherit properties from tile
    if (tile)
        mergeProperties(properties, tile->properties());

    // Override with own properties
    mergeProperties(properties, object->properties());

    object->setProperties(properties);
}

} // namespace Tiled
