//+----------------------------------------------------------------------------+
//| 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/print_window.hpp>
#include <gui/card_select_window.hpp>
#include <gui/util.hpp>
#include <data/set.hpp>
#include <data/card.hpp>
#include <data/stylesheet.hpp>
#include <render/card/viewer.hpp>
#include <wx/print.h>

DECLARE_POINTER_TYPE(PageLayout);

// ----------------------------------------------------------------------------- : Layout

PageLayout::PageLayout()
  : margin_left(0), margin_right(0), margin_top(0), margin_bottom(0)
  , rows(0), cols(0), card_landscape(false)
{}

void PageLayout::init(const StyleSheet& stylesheet, PageLayoutType type, const RealSize& page_size) {
  this->page_size = page_size;
  margin_left = margin_right = margin_top = margin_bottom = 0;
  card_size.width  = stylesheet.card_width  * 25.4 / stylesheet.card_dpi;
  card_size.height = stylesheet.card_height * 25.4 / stylesheet.card_dpi;
  card_landscape = card_size.width > card_size.height;
  cols = int(floor(page_size.width  / card_size.width));
  rows = int(floor(page_size.height / card_size.height));
  // spacing
  double hspace = (page_size.width  - (cols * card_size.width ));
  double vspace = (page_size.height - (rows * card_size.height));
  if (type == LAYOUT_NO_SPACE) {
    // no space between cards
    card_spacing.width = card_spacing.height = 0;
    margin_left = margin_right  = hspace / 2;
    margin_top = vspace * 1./3; margin_bottom = vspace * 2./3; // most printers have more margin at the bottom
  } else {
    // distribute whitespace evenly
    margin_left = margin_right  = card_spacing.width  = hspace / (cols + 1);
    margin_top  = margin_bottom = card_spacing.height = vspace / (rows + 1);
  }
}

// ----------------------------------------------------------------------------- : Printout

/// A printout object specifying how to print a specified set of cards
class CardsPrintout : public wxPrintout {
public:
  CardsPrintout(PrintJobP const& job);
  /// Number of pages, and something else I don't understand...
  void GetPageInfo(int* pageMin, int* pageMax, int* pageFrom, int* pageTo) override;
  /// Again, 'number of pages', strange wx interface
  bool HasPage(int page) override;
  /// Determine the layout
  void OnPreparePrinting() override;
  /// Print a page
  bool OnPrintPage(int page) override;
  
private:
  PrintJobP job; ///< Cards to print
  DataViewer viewer;
  double scale_x, scale_y; // priter pixel per mm
  
  int pageCount() {
    return job->num_pages();
  }
  
  /// Draw a card, that is card_nr on this page, find the postion by asking the layout
  void drawCard(DC& dc, const CardP& card, int card_nr);
};

CardsPrintout::CardsPrintout(PrintJobP const& job)
  : job(job)
{
  viewer.setSet(job->set);
}

void CardsPrintout::GetPageInfo(int* page_min, int* page_max, int* page_from, int* page_to) {
  *page_from = *page_min = 1;
  *page_to   = *page_max = pageCount();
}

bool CardsPrintout::HasPage(int page) {
  return page <= pageCount(); // page number is 1 based
}

void CardsPrintout::OnPreparePrinting() {
  if (job->layout.empty()) {
    int pw_mm, ph_mm;
    GetPageSizeMM(&pw_mm, &ph_mm);
    job->layout.init(*job->set->stylesheet, job->layout_type, RealSize(pw_mm, ph_mm));
  }
}


bool CardsPrintout::OnPrintPage(int page) {
  DC& dc = *GetDC();
  // scale factors
  int pw_mm, ph_mm;
  GetPageSizeMM(&pw_mm, &ph_mm);
  int pw_px, ph_px;
  dc.GetSize(&pw_px, &ph_px);
  scale_x = (double)pw_px / pw_mm;
  scale_y = (double)ph_px / ph_mm;
  // print the cards that belong on this page
  int start = (page - 1) * job->layout.cards_per_page();
  int end   = min((int)job->cards.size(), start + job->layout.cards_per_page());
  for (int i = start ; i < end ; ++i) {
    drawCard(dc, job->cards.at(i), i - start);
  }
  return true;
}

void CardsPrintout::drawCard(DC& dc, const CardP& card, int card_nr) {
  // determine position
  int col = card_nr % job->layout.cols;
  int row = card_nr / job->layout.cols;
  RealPoint pos( job->layout.margin_left + (job->layout.card_size.width  + job->layout.card_spacing.width)  * col
               , job->layout.margin_top  + (job->layout.card_size.height + job->layout.card_spacing.height) * row);
  // determine rotation
  const StyleSheet& stylesheet = job->set->stylesheetFor(card);
  Radians rotation = 0;
  if ((stylesheet.card_width > stylesheet.card_height) != job->layout.card_landscape) {
    rotation = rad90;
  }
  /*
  // size of this particular card (in mm)
  RealSize card_size( stylesheet.card_width  * 25.4 / stylesheet.card_dpi
                    , stylesheet.card_height * 25.4 / stylesheet.card_dpi);
  if (is_rad90(rotation)) swap(card_size.width, card_size.height);
  // adjust card size, to center card in the available space (from job->layout.card_size)?
  // TODO: deal with different sized cards in general
  */
  
  // create buffers
  int w = int(stylesheet.card_width), h = int(stylesheet.card_height); // in pixels
  if (is_rad90(rotation)) swap(w,h);
  // Draw using text buffer
  double zoom = IsPreview() ? 1 : 4;
  wxBitmap buffer(w*zoom,h*zoom,32);
  wxMemoryDC bufferDC;
  bufferDC.SelectObject(buffer);
  clearDC(bufferDC,*wxWHITE_BRUSH);
  RotatedDC rdc(bufferDC, rotation, stylesheet.getCardRect(), zoom, QUALITY_AA, ROTATION_ATTACH_TOP_LEFT);
  // render card to dc
  viewer.setCard(card);
  viewer.draw(rdc, *wxWHITE);
  // render buffer to device
  double px_per_mm = zoom * stylesheet.card_dpi / 25.4;
  dc.SetUserScale(scale_x / px_per_mm, scale_y / px_per_mm);
  dc.SetDeviceOrigin(int(scale_x * pos.x), int(scale_y * pos.y));
  bufferDC.SelectObject(wxNullBitmap);
  dc.DrawBitmap(buffer, 0, 0);
}

// ----------------------------------------------------------------------------- : PrintWindow

PrintJobP make_print_job(Window* parent, const SetP& set, const ExportCardSelectionChoices& choices) {
  // Let the user choose cards
  // controls
  ExportWindowBase wnd(parent, _TITLE_("select cards print"), set, choices);
  wxCheckBox* space = new wxCheckBox(&wnd, wxID_ANY, L"Put space between cards");
  space->SetValue(settings.print_layout);
  // layout
  wxSizer* s = new wxBoxSizer(wxVERTICAL);
    wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
      wxSizer* s3 = wnd.Create();
      s2->Add(s3, 1, wxEXPAND | wxALL, 8);
      wxSizer* s4 = new wxStaticBoxSizer(wxVERTICAL, &wnd, L"Settings");
        s4->Add(space, 1, wxALL | wxALIGN_TOP, 8);
      s2->Add(s4, 1, wxEXPAND | (wxALL & ~wxLEFT), 8);
    s->Add(s2, 1, wxEXPAND);
    s->Add(wnd.CreateButtonSizer(wxOK | wxCANCEL) , 0, wxEXPAND | wxALL, 8);
  s->SetSizeHints(&wnd);
  wnd.SetSizer(s);
  wnd.SetMinSize(wxSize(300,-1));
  // show window
  if (wnd.ShowModal() != wxID_OK) {
    return PrintJobP(); // cancel
  } else {
    // make print job
    PrintJobP job = make_intrusive<PrintJob>(set);
    job->layout_type = settings.print_layout = space->GetValue() ? LAYOUT_EQUAL_SPACE : LAYOUT_NO_SPACE;
    job->cards = wnd.getSelection();
    return job;
  }
}

void print_preview(Window* parent, const PrintJobP& job) {
  if (!job) return;
  // Show the print preview
  wxPreviewFrame* frame = new wxPreviewFrame(
    new wxPrintPreview(
      new CardsPrintout(job),
      new CardsPrintout(job)
    ), parent, _TITLE_("print preview"));
  frame->Initialize();
  frame->Maximize(true);
  frame->Show();
}

void print_set(Window* parent, const PrintJobP& job) {
  if (!job) return;
  // Print the cards
  wxPrinter p;
  CardsPrintout pout(job);
  p.Print(parent, &pout, true);
}

void print_preview(Window* parent, const SetP& set, const ExportCardSelectionChoices& choices) {
  print_preview(parent, make_print_job(parent, set, choices));
}
void print_set(Window* parent, const SetP& set, const ExportCardSelectionChoices& choices) {
  print_set(parent, make_print_job(parent, set, choices));
}
