// Aseprite
// Copyright (C) 2018-2022  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "app/context.h"
#include "app/doc.h"
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "app/file/file_formats_manager.h"
#include "base/fs.h"
#include "base/string.h"
#include "dio/detect_format.h"
#include "doc/cel.h"
#include "doc/file/act_file.h"
#include "doc/file/col_file.h"
#include "doc/file/gpl_file.h"
#include "doc/file/hex_file.h"
#include "doc/file/pal_file.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/sprite.h"

#include <cstring>

namespace app {

using namespace doc;

static const char* palExts[] = { "act", "col", "gpl", "hex", "pal" };

base::paths get_readable_palette_extensions()
{
  base::paths paths = get_readable_extensions();
  for (const char* s : palExts)
    paths.push_back(s);
  return paths;
}

base::paths get_writable_palette_extensions()
{
  base::paths paths = get_writable_extensions(FILE_SUPPORT_INDEXED);
  for (const char* s : palExts)
    paths.push_back(s);
  return paths;
}

Palette* load_palette(const char* filename,
                      const FileOpConfig* config)
{
  dio::FileFormat dioFormat = dio::detect_format(filename);
  Palette* pal = nullptr;

  switch (dioFormat) {

    case dio::FileFormat::ACT_PALETTE:
      pal = doc::file::load_act_file(filename);
      break;

    case dio::FileFormat::COL_PALETTE:
      pal = doc::file::load_col_file(filename);
      break;

    case dio::FileFormat::GPL_PALETTE:
      pal = doc::file::load_gpl_file(filename);
      break;

    case dio::FileFormat::HEX_PALETTE:
      pal = doc::file::load_hex_file(filename);
      break;

    case dio::FileFormat::PAL_PALETTE:
      pal = doc::file::load_pal_file(filename);
      break;

    default: {
      FileFormat* ff = FileFormatsManager::instance()->getFileFormat(dioFormat);
      if (!ff || !ff->support(FILE_SUPPORT_LOAD))
        break;

      std::unique_ptr<FileOp> fop(
        FileOp::createLoadDocumentOperation(
          nullptr, filename,
          FILE_LOAD_CREATE_PALETTE |
          FILE_LOAD_SEQUENCE_NONE |
          FILE_LOAD_ONE_FRAME,
          config));

      if (fop && !fop->hasError()) {
        fop->operate(nullptr);
        fop->postLoad();

        if (fop->document() &&
            fop->document()->sprite() &&
            fop->document()->sprite()->palette(frame_t(0))) {
          pal = new Palette(
            *fop->document()->sprite()->palette(frame_t(0)));
        }

        delete fop->releaseDocument();
        fop->done();
      }
      break;
    }
  }

  if (pal)
    pal->setFilename(filename);

  return pal;
}

bool save_palette(const char* filename, const Palette* pal, int columns,
                  const gfx::ColorSpaceRef& cs)
{
  dio::FileFormat dioFormat = dio::detect_format_by_file_extension(filename);
  bool success = false;

  switch (dioFormat) {

    case dio::FileFormat::ACT_PALETTE:
      success = doc::file::save_act_file(pal, filename);
      break;

    case dio::FileFormat::COL_PALETTE:
      success = doc::file::save_col_file(pal, filename);
      break;

    case dio::FileFormat::GPL_PALETTE:
      success = doc::file::save_gpl_file(pal, filename);
      break;

    case dio::FileFormat::HEX_PALETTE:
      success = doc::file::save_hex_file(pal, filename);
      break;

    case dio::FileFormat::PAL_PALETTE:
      success = doc::file::save_pal_file(pal, filename);
      break;

    default: {
      FileFormat* ff = FileFormatsManager::instance()->getFileFormat(dioFormat);
      if (!ff || !ff->support(FILE_SUPPORT_SAVE))
        break;

      int w = (columns > 0 ? std::clamp(columns, 0, pal->size()): pal->size());
      int h = (pal->size() / w) + (pal->size() % w > 0 ? 1: 0);

      Context tmpContext;
      gfx::ColorSpaceRef colorSpace = (cs ? cs: gfx::ColorSpace::MakeNone());
      Doc* doc = tmpContext.documents().add(
        new Doc(Sprite::MakeStdSprite(
                  ImageSpec((pal->size() <= 256 ? doc::ColorMode::INDEXED:
                                                  doc::ColorMode::RGB),
                            w, h, 0, colorSpace), pal->size())));

      Sprite* sprite = doc->sprite();
      doc->sprite()->setPalette(pal, false);

      LayerImage* layer = static_cast<LayerImage*>(sprite->root()->firstLayer());
      layer->configureAsBackground();

      Image* image = layer->cel(frame_t(0))->image();
      image->clear(0);

      int x, y, c;
      for (y=c=0; y<h; ++y) {
        for (x=0; x<w; ++x) {
          if (doc->colorMode() == doc::ColorMode::INDEXED)
            image->putPixel(x, y, c);
          else
            image->putPixel(x, y, pal->entry(c));

          if (++c == pal->size())
            goto done;
        }
      }
    done:;

      doc->setFilename(filename);
      success = (save_document(&tmpContext, doc) == 0);

      doc->close();
      delete doc;
      break;
    }
  }

  return success;
}

}
