/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */

/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/

#include <sstream>
#include <string>

#include "MWAWEntry.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWInputStream.hxx"

#include "FullWrtStruct.hxx"

namespace FullWrtStruct
{
bool getColor(int color, MWAWColor &col)
{
  if (color==0xFFFF) return false;
  if (color&0x8000) // true color
    col=MWAWColor(static_cast<unsigned char>(((color>>10)&0x1F)<<3),
                  static_cast<unsigned char>(((color>>5)&0x1F)<<3),
                  static_cast<unsigned char>((color&0x1F)<<3));
  else if ((color&0x6000)==0x6000) // black
    col=MWAWColor(0,0,0);
  else if ((color&0x4000) || (color>=0&&color <= 100)) { // gray in %
    int val = ((color&0x7F)*255)/100;
    if (val > 255) val = 0;
    else val = 255-val;
    auto c = static_cast<unsigned char>(val);
    col=MWAWColor(c,c,c);
  }
  else
    return false;
  return true;
}

std::string getTypeName(int type)
{
  switch (type) {
  case 0:
    return "columns,";
  case 1:
    return "tabs,";
  case 2:
    return "item,";
  case 3:
    return "style,";
  case 0xa:
    return "main,";
  case 0xb:
    return "comment,";
  case 0xc:
    return "footnote,";
  case 0xd:
    return "endnote,";
  case 0x10: // checkme
    return "index,";
  case 0x11: // checkme
    return "header,";
  case 0x13:
    return "sidebar,";
  case 0x14:
    return "sidebar[simple],";
  case 0x15:
    return "graphic,";
  case 0x18: // in general empty
    return "variableText,";
  // 13-14: always find with child
  // 0xb, 11-12 can also have child...
  case 0x19:
    return "reference,";
  case 0x1a:
    return "referenceRedirect,";
  case 0x1e:
    return "variableRedirect,";
  case 0x1f:
    return "dataMod,";
  default:
    break;
  }
  std::stringstream s;
  s << "type=" << std::hex << type << std::dec << ",";
  return s.str();
}

////////////////////////////////////////////////////////////
// Border
////////////////////////////////////////////////////////////
MWAWBorder Border::getBorder(int type)
{
  MWAWBorder res;
  if ((type%2)==0)
    res.m_type=MWAWBorder::Double;
  res.m_width=double(type/2);
  return res;
}

void Border::addTo(MWAWGraphicStyle &style) const
{
  if (!m_backColor.isWhite())
    style.setBackgroundColor(m_backColor);
  if (hasShadow()) {
    style.m_shadowOffset=MWAWVec2f(m_shadowDepl);
    style.setShadowColor(m_shadowColor);
  }
  if (m_frameBorder.isEmpty())
    return;
  MWAWBorder bord=m_frameBorder;
  bord.m_color = m_color[0];
  style.setBorders(15, bord);
}

bool Border::read(std::shared_ptr<FullWrtStruct::Entry> zone, int fSz)
{
  *this=FullWrtStruct::Border();
  if (fSz < 26) {
    MWAW_DEBUG_MSG(("FullWrtStruct::Border::read: find unexpected size\n"));
    return false;
  }
  MWAWInputStreamPtr input = zone->m_input;
  libmwaw::DebugStream f;
  long pos = input->tell();

  int width[3];
  int totalW = 0;
  for (auto &w : width) totalW += (w=static_cast<int>(input->readLong(1)));
  if (width[0]&&width[2]) {
    m_frameBorder.m_style=MWAWBorder::Simple;
    m_frameBorder.m_type=MWAWBorder::Double;
    m_frameBorder.m_width=0.5*double(totalW);
    m_frameBorder.m_widthsList.resize(3);
    for (size_t i=0; i < 3; ++i)
      m_frameBorder.m_widthsList[i]=0.5*double(width[i]);
  }
  else if (!width[0] && !width[1] && width[2]) {
    m_frameBorder.m_style=MWAWBorder::Simple;
    m_frameBorder.m_width=0.5*double(totalW);
  }
  else if (totalW) {
    MWAW_DEBUG_MSG(("FullWrtStruct::Border::read: frame border width seems odd\n"));
    f << "###frame[w]=[";
    for (auto w : width) f << w << ",";
    f << "],";
  }
  auto val = static_cast<int>(input->readLong(1));
  if (val)
    m_shadowDepl=MWAWVec2i(val,val);
  val = static_cast<int>(input->readLong(1));
  if (val) f << "frame[rectCorner]=" << val << ",";
  m_type[0] = static_cast<int>(input->readLong(1));
  MWAWColor col;
  for (int j = 0; j < 7; j++) {
    val = static_cast<int>(input->readULong(2));
    if (getColor(val,col)) {
      switch (j) {
      case 1: // border
        m_color[0] = col;
        break;
      case 2:
        m_shadowColor=col;
        break;
      case 3: // separator
        m_color[1] = col;
        break;
      case 4: // = border?
        if (m_color[0] != col)
          f << "#col[border2]=" << col << ",";
        break;
      case 5:
        m_frontColor=col;
        break;
      case 6:
        m_backColor=col;
        break;
      default:
        if (!col.isBlack())
          f << "col" << j << "=" << col << ",";
      }
    }
    else
      f << "#col" << j << "=" << std::hex << val << std::dec << ",";
  }
  for (int j = 0; j < 2; j++) { // g0=g1=0
    val = static_cast<int>(input->readLong(1));
    if (val) f << "g" << j << "=" << val << ",";
  }
  m_type[1] = static_cast<int>(input->readLong(1)); // sepH
  m_type[2] = static_cast<int>(input->readLong(1)); // sepV
  m_flags = static_cast<int>(input->readULong(2));
  m_extra = f.str();
  input->seek(pos+fSz, librevenge::RVNG_SEEK_SET);
  return true;
}

std::vector<MWAWVariable<MWAWBorder> > Border::getParagraphBorders() const
{
  std::vector<MWAWVariable<MWAWBorder> > res;
  int wh=-1;
  if (m_type[0]>0 && m_type[0]<=8) wh=0;
  else if (m_type[1]>0 && m_type[1]<=8) wh=1;
  if (wh == -1)
    return res;
  MWAWVariable<MWAWBorder> border(getBorder(m_type[wh]));
  border->m_color=m_color[wh];
  if (wh==0)
    res.resize(4,border);
  else {
    res.resize(4);
    res[libmwaw::Bottom]=border;
  }
  return res;
}

std::ostream &operator<<(std::ostream &o, Border const &p)
{
  if (!p.m_frontColor.isBlack())
    o << "frontColor=" << p.m_frontColor << ",";
  if (!p.m_backColor.isWhite())
    o << "backColor=" << p.m_backColor << ",";
  if (p.hasShadow())
    o << "shadow=" << p.m_shadowDepl << "[" << p.m_shadowColor << "],";
  for (int w=0; w < 3; w++) {
    if (!p.m_type[w]) continue;
    if (w==0)
      o << "border=";
    else if (w==1)
      o << "sep[H]=";
    else
      o << "sep[V]=";
    switch (p.m_type[w]) {
    case 0: // none
      break;
    case 1:
      o << "hairline:";
      break;
    case 2:
      o << "hairline double:";
      break;
    case 3:
      o << "normal:";
      break;
    case 4:
      o << "normal double:";
      break;
    case 5:
      o << "2pt:";
      break;
    case 7:
      o << "3pt:";
      break;
    default:
      o << "#type[" << p.m_type[w] << "]:";
    }
    if (w!=2 && !p.m_color[w].isBlack())
      o << "col=" << p.m_color[w] << ",";
    else
      o << ",";
  }
  if (!p.m_frameBorder.isEmpty())
    o << "border[frame]=" << p.m_frameBorder << ",";
  if (p.m_flags & 0x4000)
    o << "setBorder,";
  if (p.m_flags & 0x8000)
    o << "setSeparator,";
  if (p.m_flags & 0x3FFF)
    o << "flags=" << std::hex << (p.m_flags & 0x3FFF) << std::dec << ",";
  o << p.m_extra;
  return o;
}
////////////////////////////////////////////////////////////
// Entry
////////////////////////////////////////////////////////////
Entry::Entry(MWAWInputStreamPtr const &input)
  : MWAWEntry()
  , m_input(input)
  , m_nextId(-2)
  , m_type(-1)
  , m_typeId(-3)
  , m_data()
  , m_asciiFile()
{
  for (auto &val : m_values) val=0;
}
Entry::~Entry()
{
  closeDebugFile();
}

std::ostream &operator<<(std::ostream &o, Entry const &entry)
{
  if (entry.type().length()) {
    o << entry.type();
    if (entry.id() >= 0) o << "[" << entry.id() << "]";
    o << ",";
  }
  if (entry.m_id != -1) {
    o << "fId=" << entry.m_id << ",";
  }
  switch (entry.m_type) {
  case -1:
    break;
  case 0xa:
    o << "main,";
    break;
  case 0x11:
    o << "header,";
    break;
  case 0x12:
    o << "footer,";
    break;
  case 0x13:
    o << "textbox,";
    break;
  default:
    o << "zType=" << std::hex << entry.m_type << std::dec << ",";
  }
  if (entry.m_typeId != -3) {
    if (entry.m_typeId >= 0) o << "text/graphic,";
    else if (entry.m_typeId == -2)
      o << "null,";
    else if (entry.m_typeId == -1)
      o << "main,";
    else
      o << "#type=" << entry.m_typeId << ",";
  }
  for (int i = 0; i < 3; i++)
    if (entry.m_values[i])
      o << "e" << i << "=" << entry.m_values[i] << ",";
  if (entry.m_extra.length())
    o << entry.m_extra << ",";
  return o;
}

bool Entry::valid() const
{
  return m_input && MWAWEntry::valid() && m_input->checkPosition(begin()) && m_input->checkPosition(end());
}

void Entry::update()
{
  if (!m_data.size()) return;

  setBegin(0);
  setLength(long(m_data.size()));
  m_input=MWAWInputStream::get(m_data, false);
  if (!m_input) {
    MWAW_DEBUG_MSG(("Entry::update: problem the input size is bad!!!\n"));
    return;
  }
  m_asciiFile.reset(new libmwaw::DebugFile(m_input));
  std::stringstream s;
  if (m_typeId == -1)
    s << "MainZoneM" << m_id;
  else
    s << "DataZone" << m_id;
  m_asciiFile->open(s.str());
}

void Entry::closeDebugFile()
{
  if (!m_data.size()) return;
  m_asciiFile->reset();
}

libmwaw::DebugFile &Entry::getAsciiFile()
{
  return *m_asciiFile;
}

bool Entry::operator==(const Entry &a) const
{
  if (MWAWEntry::operator!=(a)) return false;
  if (m_input.get() != a.m_input.get()) return false;
  if (id() != a.id()) return false;
  if (m_nextId != a.m_nextId) return false;
  if (m_type != a.m_type) return false;
  if (m_typeId != a.m_typeId) return false;
  if (m_id != a.m_id) return false;
  for (int i  = 0; i < 3; i++)
    if (m_values[i] != a.m_values[i]) return false;
  return true;
}

////////////////////////////////////////////////////////////
// read the zone data header
ZoneHeader::~ZoneHeader()
{
}

bool ZoneHeader::read(std::shared_ptr<FullWrtStruct::Entry> zone)
{
  MWAWInputStreamPtr input = zone->m_input;
  libmwaw::DebugFile &asciiFile = zone->getAsciiFile();
  libmwaw::DebugStream f;
  bool typedDoc = m_type > 0;
  long pos = input->tell();
  if (pos+73 > zone->end())
    return false;

  auto val = static_cast<int>(input->readULong(1));
  if (!typedDoc && val)
    return false;
  if (val) f << "#type[high]" << std::hex << val << std::dec << ",";
  auto type = static_cast<int>(input->readULong(1));
  if (!(type >= 0x18 && type <=0x1f) && !(type >= 0xc && type <= 0xe)
      &&!(typedDoc && type==0x5a))
    return false;
  f << "type=" << std::hex << type << std::dec << ",";

  val = static_cast<int>(input->readULong(2));
  if (val) {
    if (!typedDoc) return false;
    f << "#f0=" << val << ",";
  }
  val = static_cast<int>(input->readULong(1)); // 0, 6 or 0x10, 0x1e
  if (val) f << "f1=" << std::hex << val << std::dec << ",";
  val = static_cast<int>(input->readLong(1)); // 0 or  0x1 or -10
  if (val != 1) f << "f2=" << val << ",";
  auto N = static_cast<int>(input->readLong(2));
  if (N) // can be a big number, but some time 0, 1, 3, 4, ...
    f << "N0=" << N << ",";
  // small number between 1 and 0x1f
  val = static_cast<int>(input->readLong(2));
  if (val) f << "N1=" << val << ",";

  val = static_cast<int>(input->readLong(1)); // 0, 1, 2, -1, -2
  if (val) f << "f3=" << val << ",";
  val = static_cast<int>(input->readULong(1)); // 12, 1f, 22, 23, 25, 2d, 32, 60, 62, 66, 67, ...
  if (val) f << "f4=" << std::hex << val << std::dec << ",";

  // small number, g0, g2 often negative
  for (int i = 0; i < 4; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val) f << "g" << i << "=" << val << ",";
  }

  val = static_cast<int>(input->readLong(2)); // alway -2
  if (val != -2) {
    if (val > 0 || val < -2) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    f << "#g4=" << val << ",";
  }
  for (int i = 0; i < 3; i++) {
    // first a small number < 3e1, g6,g7 almost always 0 expected one time g6=-1a9
    val = static_cast<int>(input->readLong(4));
    if (!val) continue;
    if (i==2 && !typedDoc)
      return false;
    f << "g" << i+5 << "=" << val << ",";
  }
  m_fileId = static_cast<int>(input->readULong(2));
  m_docId = static_cast<int>(input->readULong(2));
  for (int i=0; i < 3; ++i) { // h0: a small number 0..be, h1=0|1, h2=0..7
    val = static_cast<int>(input->readLong(2));
    if (val)
      f << "h" << i << "=" << val << ",";
  }
  // now probably dependent of the type
  switch (m_type) {
  case 0x13: // sidebar
  case 0x14: // sidebar simple
    for (int i=0; i < 3; ++i) { // h3=0..e, h4=0..d, h5=0..a8
      val = static_cast<int>(input->readLong(2));
      if (val)
        f << "h" << i+3 << "=" << val << ",";
    }
    f << "PTR=[";
    for (int i=0; i < 2; ++i)
      f << std::hex << input->readULong(4) << std::dec << ",";
    f << "],";
    m_wrapping=static_cast<int>(input->readLong(1));
    val = static_cast<int>(input->readLong(1));
    if (val) f << "#h6=" << val << ",";
    for (int i=0; i<6; ++i) { // 0
      val = static_cast<int>(input->readLong(2));
      if (val)
        f << "h" << i+7 << "=" << val << ",";
    }
    break;
  default:
    break;
  }
  m_extra = f.str();
  if (input->tell()!=pos+72)
    asciiFile.addDelimiter(input->tell(),'|');
  asciiFile.addPos(pos);
  input->seek(pos+72, librevenge::RVNG_SEEK_SET);
  f.str("");
  return true;
}

std::ostream &operator<<(std::ostream &o, ZoneHeader const &dt)
{
  if (dt.m_type >= 0) o << FullWrtStruct::getTypeName(dt.m_type);
  if (dt.m_fileId >= 0) o << "fileId=" << dt.m_fileId << ",";
  if (dt.m_docId >= 0) o << "docId=" << dt.m_docId << ",";
  switch (dt.m_wrapping) {
  case -1:
    break;
  case 0:
    o << "wrapToShape,";
    break;
  case 1:
    o << "wrap[rect],";
    break;
  case 2:
    o << "wrap[shrinkToFit],";
    break;
  case 3:
    o << "wrap[background],";
    break;
  default:
    o << "#wrap=" << dt.m_wrapping << ",";
    break;
  }
  o << dt.m_extra;
  return o;
}

}

// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab:
