// Aseprite Render Library
// Copyright (c) 2020-2022  Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

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

#include "base/debug.h"
#include "render/zoom.h"

#include <algorithm>

namespace render {

static int scales[][2] = {
  { 1, 64 },
  { 1, 48 },
  { 1, 32 },
  { 1, 24 },
  { 1, 16 },
  { 1, 12 },
  { 1, 8 },
  { 1, 6 },
  { 1, 5 },
  { 1, 4 },
  { 1, 3 },
  { 1, 2 },
  { 1, 1 }, // 100%
  { 2, 1 },
  { 3, 1 },
  { 4, 1 },
  { 5, 1 },
  { 6, 1 },
  { 8, 1 },
  { 12, 1 },
  { 16, 1 },
  { 24, 1 },
  { 32, 1 },
  { 48, 1 },
  { 64, 1 },
};

static int scales_size = sizeof(scales) / sizeof(scales[0]);

Zoom::Zoom(int num, int den)
  : m_num(num)
  , m_den(den)
{
  ASSERT(m_num > 0);
  ASSERT(m_den > 0);
  m_internalScale = scale();
}

bool Zoom::in()
{
  int i = linearScale();
  if (i < scales_size-1) {
    ++i;
    m_num = scales[i][0];
    m_den = scales[i][1];
    m_internalScale = scale();
    return true;
  }
  else
    return false;
}

bool Zoom::out()
{
  int i = linearScale();
  if (i > 0) {
    --i;
    m_num = scales[i][0];
    m_den = scales[i][1];
    m_internalScale = scale();
    return true;
  }
  else
    return false;
}

int Zoom::linearScale() const
{
  for (int i=0; i<scales_size; ++i) {
    // Exact match
    if (scales[i][0] == m_num &&
        scales[i][1] == m_den) {
      return i;
    }
  }
  return findClosestLinearScale(scale());
}

// static
Zoom Zoom::fromScale(double scale)
{
  Zoom zoom = fromLinearScale(findClosestLinearScale(scale));
  zoom.m_internalScale = scale;
  return zoom;
}

// static
Zoom Zoom::fromLinearScale(int i)
{
  i = std::clamp(i, 0, scales_size-1);
  return Zoom(scales[i][0], scales[i][1]);
}

// static
int Zoom::findClosestLinearScale(double scale)
{
  for (int i=1; i<scales_size-1; ++i) {
    double min = double(scales[i-1][0]) / double(scales[i-1][1]);
    double mid = double(scales[i  ][0]) / double(scales[i  ][1]);
    double max = double(scales[i+1][0]) / double(scales[i+1][1]);

    if (scale >= (min+mid)/2.0 &&
        scale <= (mid+max)/2.0)
      return i;
  }
  if (scale < 1.0)
    return 0;
  else
    return scales_size-1;
}

int Zoom::linearValues()
{
  return scales_size;
}

} // namespace render
