//+----------------------------------------------------------------------------+
//| Description:  Magic Set Editor - Program to make Magic (tm) cards          |
//| Copyright:    (C) Twan van Laarhoven and the other MSE developers          |
//| License:      GNU General Public License 2 or later (see file COPYING)     |
//+----------------------------------------------------------------------------+

// ----------------------------------------------------------------------------- : Includes

#include <util/prec.hpp>
#include <gui/package_update_list.hpp>
#include <gui/thumbnail_thread.hpp>
#include <gui/util.hpp>
#include <gfx/gfx.hpp>
#include <wx/url.h>

// ----------------------------------------------------------------------------- : PackageUpdateList::TreeItem

void PackageUpdateList::TreeItem::add(const InstallablePackageP& package, const String& path, int level) {
  // this node
  this->level = level;
  PackageType new_type = package_type(*package->description);
  int new_hint = package->description->position_hint;
  if (new_type < position_type || (new_type == position_type && new_hint < position_hint)) {
    // this is a lower position hint, use it
    position_type = new_type;
    position_hint = new_hint;
  }
  // end of the path?
  if (path.empty()) {
    assert(!this->package);
    this->package = package;
    return;
  }
  // split path
  size_t pos = path.find_first_of(_('/'));
  String name = path.substr(0,pos);
  String rest = pos == String::npos ? _("") : path.substr(pos+1);
  // find/add child
  for (auto it = children.begin() ; it != children.end() ; ++it) {
    auto const& ti = *it;
    if (ti->label == name) {
      // already have this child
      if (pos == String::npos && ti->package) {
        // two packages with the same path
        TreeItemP ti2(new TreeItem);
        ti2->label = name;
        children.insert(it, ti2);
        ti2->add(package, rest, level + 1);
      } else {
        ti->add(package, rest, level + 1);
      }
      return;
    }
  }
  // don't have this child
  TreeItemP ti(new TreeItem);
  children.push_back(ti);
  ti->label = name;
  ti->add(package, rest, level + 1);
}

bool compare_pos_hint(const PackageUpdateList::TreeItemP& a, const PackageUpdateList::TreeItemP& b) {
  if (a->position_type < b->position_type) return true;
  if (a->position_type > b->position_type) return false;
  if (a->position_hint < b->position_hint) return true;
  if (a->position_hint > b->position_hint) return false;
  return a->label < b->label;
}

void PackageUpdateList::TreeItem::toItems(vector<TreeList::ItemP>& items) {
  sort(children.begin(), children.end(), compare_pos_hint);
  FOR_EACH(c, children) {
    items.push_back(c);
    c->toItems(items);
  }
}

bool PackageUpdateList::TreeItem::highlight() const {
  if (package && package->willBeInstalled()) return true;
  FOR_EACH_CONST(c,children) if (c->highlight()) return true;
  return false;
}

PackageUpdateList::TreeItem::PackageType PackageUpdateList::TreeItem::package_type(const PackageDescription& desc) {
  if (desc.name == mse_package)                           return TYPE_PROG;
  size_t pos = desc.name.find_last_of(_('.'));
  if (pos == String::npos)                                return TYPE_OTHER;
  if (is_substr(desc.name,pos,_(".mse-locale")))          return TYPE_LOCALE;
  if (is_substr(desc.name,pos,_(".mse-game")))            return TYPE_GAME;
  if (is_substr(desc.name,pos,_(".mse-style")))           return TYPE_STYLESHEET;
  if (is_substr(desc.name,pos,_(".mse-export-template"))) return TYPE_EXPORT_TEMPLATE;
  if (is_substr(desc.name,pos,_(".mse-symbol-font")))     return TYPE_SYMBOL_FONT;
  if (is_substr(desc.name,pos,_(".mse-include")))         return TYPE_INCLUDE;
  if (is_substr(desc.name,pos,_(".ttf")))                 return TYPE_FONT;
  return TYPE_OTHER;
}

void PackageUpdateList::TreeItem::setIcon(const Image& img) {
  Image image = img;
  int iw = image.GetWidth(), ih = image.GetHeight();
  if (ih > 107) {
    int w = 107 * iw / ih;
    image = resample(image, w, 107);
  } else if (iw > 107) {
    int h = 107 * ih / iw;
    image = resample(image, 107, h);
  }
  if (package) package->description->icon = image;
  Image resampled = resample_preserve_aspect(image,16,16);
  icon = Bitmap(resampled);
  saturate(resampled, -.75);
  set_alpha(resampled,0.5);
  icon_grey = Bitmap(resampled);
}


// ----------------------------------------------------------------------------- : PackageIconRequest

/// wx doesn't allow seeking on InputStreams from a wxURL
/// The built in buffer class is too stupid to seek, so we must do it ourselfs
class SeekAtStartInputStream : public wxFilterInputStream {
public:
  SeekAtStartInputStream(wxInputStream& stream)
    : wxFilterInputStream(stream)
    , buffer_pos(0)
  {
    m_parent_i_stream->Read(buffer, 1024);
    buffer_size = m_parent_i_stream->LastRead();
  }
  
  bool IsSeekable() const override { return true; }
protected:
  size_t OnSysRead(void *buffer, size_t bufsize) override {
    size_t len = min(buffer_size - buffer_pos, bufsize);
    memcpy(buffer, this->buffer + buffer_pos, len);
    buffer_pos += len;
    m_parent_i_stream->Read((Byte*)buffer + len, bufsize - len);
    return m_parent_i_stream->LastRead() + len;
  }
  wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode) override {
    if      (mode == wxFromStart)   buffer_pos = seek;
    else if (mode == wxFromCurrent) buffer_pos += seek;
    else                            assert(false);
    assert(buffer_pos < buffer_size);
    return buffer_pos;
  }
  wxFileOffset OnSysTell() const override {
    assert(buffer_pos < buffer_size);
    return buffer_pos;
  }
private:
  size_t buffer_size, buffer_pos;
  Byte buffer[1024];
};

/// Retrieve the icon for a package
class PackageIconRequest : public ThumbnailRequest {
public:
  PackageIconRequest(PackageUpdateList* list, PackageUpdateList::TreeItem* ti)
    : ThumbnailRequest(
      list,
      _("package_") + ti->package->description->icon_url + _("_") + ti->package->description->version.toString(),
      wxDateTime(1,wxDateTime::Jan,2000))
    , list(list), ti(ti)
  {}
  
  Image generate() override {
    wxURL url(ti->package->description->icon_url);
    unique_ptr<wxInputStream> isP(url.GetInputStream());
    if (!isP) return wxImage();
    SeekAtStartInputStream is2(*isP);
    Image result(is2);
    return result;
  }
  void store(const Image& image) override {
    if (!image.Ok()) return;
    ti->setIcon(image);
    list->Refresh(false);
  }
private:
  PackageUpdateList* list;
  PackageUpdateList::TreeItem* ti;
};


// ----------------------------------------------------------------------------- : PackageUpdateList : implementation

PackageUpdateList::PackageUpdateList(Window* parent, const InstallablePackages& packages, bool show_only_installable, int id)
  : TreeList(parent, id)
  , packages(packages)
  , show_only_installable(show_only_installable)
{
  item_height = max(item_height,19);
  rebuild();
}
PackageUpdateList::~PackageUpdateList() {
  thumbnail_thread.abort(this);
}

void PackageUpdateList::initItems() {
  // add packages to tree
  TreeItem root;
  FOR_EACH_CONST(ip, packages) {
    String group = ip->description->installer_group;
    if (group.empty()) group = _("custom");
    if (!show_only_installable || ip->installer) {
      root.add(ip, group);
    }
  }
  // tree to treelist items
  items.clear();
  root.toItems(items);
  // init image list
  FOR_EACH(i,items) {
    TreeItem& ti = static_cast<TreeItem&>(*i);
    const InstallablePackageP& p = ti.package;
    // load icon
    Image image;
    if (p && p->description->icon.Ok()) { // it has an icon
      ti.setIcon(p->description->icon);
    } else if (p) {                       // it doesn't have an icon (yet)
      ti.setIcon(load_resource_image(_("installer_package")));
      if (!p->description->icon_url.empty()) {
        // download icon
        thumbnail_thread.request(make_intrusive<PackageIconRequest>(this,&ti));
      }
    } else if (ti.position_type == TreeItem::TYPE_LOCALE) { // locale folder
      ti.setIcon(load_resource_image(_("installer_locales")));
    } else {                                                // other folder
      ti.setIcon(load_resource_image(_("installer_group")));
    }
  }
}

void PackageUpdateList::drawItem(DC& dc, size_t index, size_t column, int x, int y, bool selected) const {
  // offset for drawing
  x += 1;  y += 3;
  // the item
  const TreeItem& ti = static_cast<const TreeItem&>(*items[index]);
  Color color = wxSystemSettings::GetColour(selected ? wxSYS_COLOUR_HIGHLIGHTTEXT : wxSYS_COLOUR_WINDOWTEXT);
  if (column == 0) {
    // Name
    const Bitmap& bmp = ti.highlight() ? ti.icon : ti.icon_grey;
    if (bmp.Ok()) dc.DrawBitmap(bmp, x-1,y-2);
    dc.SetTextForeground(color);
    dc.DrawText(capitalize_sentence(ti.label), x+17, y);
  } else if (column == 1 && ti.package) {
    // Status
    InstallablePackage& package = *ti.package;
    if (package.has(PACKAGE_CONFLICTS)) {
      dc.SetTextForeground(lerp(color,Color(255,0,0),0.8));
      dc.DrawText(_LABEL_("package conflicts"), x,y);
    } else if (package.has(PACKAGE_MODIFIED)) {
      dc.SetTextForeground(lerp(color,Color(255,255,0),0.5));
      dc.DrawText(_LABEL_("package modified"), x,y);
    } else if (package.has(PACKAGE_UPDATES)) {
      dc.SetTextForeground(lerp(color,Color(0,0,255),0.5));
      dc.DrawText(_LABEL_("package updates"), x,y);
    } else if (package.has(PACKAGE_INSTALLED)) {
      dc.SetTextForeground(color);
      dc.DrawText(_LABEL_("package installed"), x,y);
    } else if (package.has(PACKAGE_INSTALLABLE)) {
      dc.SetTextForeground(lerp(color,Color(128,128,128),0.6));
      dc.SetTextForeground(color);
      dc.DrawText(_LABEL_("package installable"), x,y);
    }
  } else if (column == 2 && ti.package) {
    // Action
    InstallablePackage& package = *ti.package;
    if (package.has(PACKAGE_ACT_INSTALL)) {
      if (package.has(PACKAGE_UPDATES)) {
        dc.SetTextForeground(lerp(color,Color(0,0,255),0.6));
        dc.DrawText(_LABEL_("upgrade package"), x,y);
      } else if (package.has(PACKAGE_INSTALLED)) {
        dc.SetTextForeground(lerp(color,Color(0,0,255),0.2));
        dc.DrawText(_LABEL_("reinstall package"), x,y);
      } else {
        dc.SetTextForeground(lerp(color,Color(0,255,0),0.6));
        dc.DrawText(_LABEL_("install package"), x,y);
      }
    } else if (package.has(PACKAGE_ACT_REMOVE)) {
      dc.SetTextForeground(lerp(color,Color(255,0,0),0.7));
      dc.DrawText(_LABEL_("remove package"), x,y);
    }
  }
}

String PackageUpdateList::columnText(size_t column) const {
  if       (column == 0) return _LABEL_("package name");
  else if  (column == 1) return _LABEL_("package status");
  else if  (column == 2) return _LABEL_("package action");
  else                   throw InternalError(_("Unknown column"));
}

int PackageUpdateList::columnWidth(size_t column) const {
  if (column == 0) {
    wxSize cs = GetClientSize();
    return cs.x - 240;
  } else {
    return 120;
  }
}

