#include "Palette.hpp"
#include <fstream>
#include <cmath>

#define COLOR_EPSILON (1./512.)

namespace util
{

  Palette::Palette(std::string filename)
    : FileObject(filename)
    {
      reread();
    }

  Palette::Palette()
    : FileObject()
    {
      for(int i = 0 ; i < 255 ; ++i)
      {
        colors[i] = 0.0f;
      }
    }

  void Palette::reread()
  {
    std::ifstream in(filename().c_str(), std::ios::binary);

    unsigned int index = 0;
    while (!in.eof() || !in.good() || !in)
    {
      if (index > 255) break;
      colors[index].r(GLfloat(in.get())/255.f);
      colors[index].g(GLfloat(in.get())/255.f);
      colors[index].b(GLfloat(in.get())/255.f);
      index++;
    }
  }

  Palette::Color Palette::getColor(unsigned int index, double alpha) const
  {
    if (index > 255)
      index = 255;
    Color color = colors[index];
    color.a(GLfloat(alpha));
    return color;
  }

  void Palette::useColor(unsigned int index, double alpha) const
  {
    glColor4fv(getColor(index, alpha).c_data());
  }

  Palette::Color Palette::getColor(unsigned int ind1, unsigned int ind2, double w, double alpha ) const
  {
    if (ind1 > 255)
      ind1 = 255;
    if (ind2 > 255)
      ind2 = 255;
    if (w > 1.0)
      w = 1.0;
    else if (w < 0.0)
      w = 0.0;

    if(ind1 == ind2)
      return getColor(ind1, alpha);
    else if(w < COLOR_EPSILON)
      return getColor(ind1, alpha);
    else if(w > 1-COLOR_EPSILON)
      return getColor(ind2, alpha);
    else
    {
      Color a = colors[ind1], b = colors[ind2];
      Color result( GLfloat(a.r() * (1.0 - w) + b.r() * w),
                    GLfloat(a.g() * (1.0 - w) + b.g() * w),
                    GLfloat(a.b() * (1.0 - w) + b.b() * w),
                    GLfloat(alpha));
      return result;
    }
  }

  void Palette::blend(unsigned int ind1, unsigned int ind2, double w, double alpha ) const
  {
    glColor4fv( getColor(ind1, ind2, w, alpha).c_data() );
  }

  Palette::Color Palette::blend(const Color& a, const Color& b, double w)
  {
    Color result( GLfloat(a.r() * (1.0 - w) + b.r() * w),
                  GLfloat(a.g() * (1.0 - w) + b.g() * w),
                  GLfloat(a.b() * (1.0 - w) + b.b() * w),
                  GLfloat(a.a() * (1.0 - w) + b.a() * w));
    return result;
  }

  Palette::Color Palette::selectColor(unsigned int start, unsigned int end, double w, double alpha) const
  {
    if (start > 255)
      start = 255;
    if (end > 255)
      end = 255;
    if (w > 1.0)
      w = 1.0;
    else if (w < 0.0)
      w = 0.0;

    int delta_color = (int)end-(int)start;
    double pos_w = delta_color*w + start;
    int before = (int)std::floor(pos_w);
    int after = before+1;
    double ratio = pos_w - (double)before;
    if(ratio < COLOR_EPSILON)
      return getColor(before, alpha);
    if(ratio > 1-COLOR_EPSILON)
      return getColor(after, alpha);
    return getColor(before, after, ratio, alpha);
  }

  void Palette::select(unsigned int start, unsigned int end, double w, double alpha) const
  {
    glColor4fv(selectColor(start, end, w, alpha).c_data());
  }
}
