/**********************************************************************************************
    Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

**********************************************************************************************/

#ifndef CMAPIMG_H
#define CMAPIMG_H

#include <QMap>

#include "map/IMap.h"
#include "map/garmin/CGarminPoint.h"
#include "map/garmin/CGarminPolygon.h"
#include "map/garmin/CGarminTyp.h"
#include "map/garmin/Garmin.h"

class CMapDraw;
class CFileExt;
class IGarminStrTbl;
class BLContext;

using polytype_t = QVector<CGarminPolygon>;
using pointtype_t = QVector<CGarminPoint>;

class CMapIMG : public IMap {
  Q_OBJECT
 public:
  struct maplevel_t {
    bool inherited;
    quint8 level;
    quint8 bits;
  };

  /// subfile part (TRE, RGN, ...) location information
  struct subfile_part_t {
    quint32 offset = 0;  //< file offset of subfile part
    quint32 size = 0;    //< size of the subfile part
  };

  /// subdivision  information
  struct subdiv_desc_t {
    quint32 n;

    quint16 next;       //< section of next level
    bool terminate;     //< end of section group
    quint32 rgn_start;  //< offset into the subfile's RGN part
    quint32 rgn_end;    //< end of section in RGN part (last offset = rgn_end - 1)

    bool hasPoints;     //< there are points stored in the RGN subsection
    bool hasIdxPoints;  //< there are index points stored in the RGN subsection
    bool hasPolylines;  //< there are polylines stored in the RGN subsection
    bool hasPolygons;   //< there are polygons stored in the RGN subsection

    qint32 iCenterLng;  //< the center longitude of the area covered by this subdivision
    qint32 iCenterLat;  //< the center latitude  of the area covered by this subdivision

    qreal north;  //< north boundary of area covered by this subsection []
    qreal east;   //< east  boundary of area covered by this subsection []
    qreal south;  //< south boundary of area covered by this subsection []
    qreal west;   //< west  boundary of area covered by this subsection []

    /// area in meter coordinates covered by this subdivision []
    QRectF area;

    /// number of left shifts for RGN data
    quint32 shift;
    /// map level this subdivision is shown
    quint32 level;

    quint32 offsetPoints2;
    qint32 lengthPoints2;
    quint32 offsetPolylines2;
    qint32 lengthPolylines2;
    quint32 offsetPolygons2;
    qint32 lengthPolygons2;
  };

  struct subfile_desc_t {
    /// the name of the subfile (not really needed)
    QString name;
    /// location information of all parts
    QMap<QString, subfile_part_t> parts;

    qreal north = 0.0;  //< north boundary of area covered by this subfile [rad]
    qreal east = 0.0;   //< east  boundary of area covered by this subfile [rad]
    qreal south = 0.0;  //< south boundary of area covered by this subfile [rad]
    qreal west = 0.0;   //< west  boundary of area covered by this subfile [rad]

    /// area in [] covered by this subfile
    QRectF area;

    /// list of subdivisions
    QVector<subdiv_desc_t> subdivs;
    /// used maplevels
    QVector<maplevel_t> maplevels;
    /// bit 1 of POI_flags (TRE header @ 0x3F)
    bool isTransparent = false;
    /// object to manage the string tables
    IGarminStrTbl* strtbl = nullptr;
  };

  CMapIMG(const QString& filename, CMapDraw* parent);
  virtual ~CMapIMG() = default;

  void loadConfig(QSettings& cfg) override;

  void draw(IDrawContext::buffer_t& buf) override;

  void getToolTip(const QPoint& px, QString& infotext) const override;

  void findPOICloseBy(const QPoint&, IPoiItem& poi) const override;

  /**
     @brief Find a matching street polyline

     The polyline must be close enough in terms of pixel to point 1 and 2. "Close enough" is defined by
     the threshold. The returned polyline uses lon/lat as coordinates.

     @param pt1           first point in [rad]
     @param pt2           second point in [rad]
     @param threshold     the "close enough" threshold in [pixel]
     @param polyline      the resulting polyline, if any, in [rad]
     @return              Return true if a line has been found.
   */
  bool findPolylineCloseBy(const QPointF& pt1, const QPointF& pt2, qint32 threshold, QPolygonF& polyline) override;

 public slots:
  void slotSetTypeFile(const QString& filename) override;

 private:
  enum exce_e { eErrOpen, eErrAccess, errFormat, errLock, errAbort };
  struct exce_t {
    exce_t(exce_e err, const QString& msg) : err(err), msg(msg) {}
    exce_e err;
    QString msg;
  };
  struct strlbl_t {
    QPoint pt;
    QRect rect;
    QString str;
    CGarminTyp::point_property property;
    bool isNight = false;
  };

  quint8 scale2bits(const QPointF& scale);
  void setupTyp();
  void readBasics();
  void readSubfileBasics(subfile_desc_t& subfile, CFileExt& file);
  void processPrimaryMapData();
  void readFile(CFileExt& file, quint32 offset, quint32 size, QByteArray& data);
  void loadVisibleData(bool fast, polytype_t& polygons, polytype_t& polylines, pointtype_t& points, pointtype_t& pois,
                       unsigned level, const QRectF& viewport, BLContext& ctx);
  void loadSubDiv(CFileExt& file, const subdiv_desc_t& subdiv, IGarminStrTbl* strtbl, const QByteArray& rgndata,
                  bool fast, const QRectF& viewport, polytype_t& polylines, polytype_t& polygons, pointtype_t& points,
                  pointtype_t& pois);
  bool intersectsWithExistingLabel(const QRect& rect) const;
  void addLabel(const CGarminPoint& pt, const QRect& rect, const CGarminTyp::point_property& property, bool isDay);
  void drawPolygons(BLContext& ctx, polytype_t& lines);
  void drawPolylines(BLContext& ctx, polytype_t& lines, const QPointF& scale);
  void drawPoints(BLContext& ctx, pointtype_t& pts, QVector<QRectF>& rectPois);
  void drawPois(BLContext& ctx, pointtype_t& pts, QVector<QRectF>& rectPois);
  // Text and labels are rendered through Blend2D as well: glyph outlines come from
  // QRawFont (cmap-only mapping, no complex shaping) and are filled in the shared context.
  void drawLabels(BLContext& ctx, const QVector<strlbl_t>& lbls);
  void drawText(BLContext& ctx);

  void drawLine(BLContext& ctx, CGarminPolygon& l, bool stroke, int lineWidth,
                const CGarminTyp::polyline_property& property, const QFont& font, const QPointF& scale);
  void drawLine(BLContext& ctx, const CGarminPolygon& l);

  void collectText(const CGarminPolygon& item, const QPolygonF& line, const QFont& font, qint32 lineWidth,
                   const QColor& color);

  void getInfoPoints(const pointtype_t& points, const QPoint& pt, QMultiMap<QString, QString>& dict) const;
  void getInfoPolylines(const QPoint& pt, QMultiMap<QString, QString>& dict) const;
  void getInfoPolygons(const QPoint& pt, QMultiMap<QString, QString>& dict) const;

#pragma pack(1)
  // Garmin IMG file header structure, to the start of the FAT blocks
  struct hdr_img_t {
    quint8 xorByte;  ///< 0x00000000
    quint8 byte0x00000001_0x0000000F[15];
    char signature[7];  ///< 0x00000010 .. 0x00000016
    quint8 byte0x00000017_0x00000040[42];
    ///< 0x00000041 .. 0x00000047
    char identifier[7];
    quint8 byte0x00000048;
    char desc1[20];  ///< 0x00000049 .. 0x0000005C
    quint8 byte0x0000005D_0x00000060[4];
    quint8 e1;  ///< 0x00000061
    quint8 e2;  ///< 0x00000062
    quint8 byte0x00000063_0x00000064[2];
    char desc2[31];  ///< 0x00000065 .. 0x00000083
    quint8 byte0x00000084_0x0000040B[904];
    quint32 dataoffset;  ///< 0x0000040C .. 0x0000040F
    quint8 byte0x00000410_0x0000041F[16];
    quint16 blocks[240];  ///< 0x00000420 .. 0x000005FF

    quint32 blocksize() { return 1 << (e1 + e2); }
  };
  struct FATblock_t {
    quint8 flag;   ///< 0x00000000
    char name[8];  ///< 0x00000001 .. 0x00000008
    char type[3];  ///< 0x00000009 .. 0x0000000B
    quint32 size;  ///< 0x0000000C .. 0x0000000F
    quint16 part;  ///< 0x00000010 .. 0x00000011
    quint8 byte0x00000012_0x0000001F[14];
    quint16 blocks[240];  ///< 0x00000020 .. 0x000001FF
  };

  // common header of the RGN, TRE, LBL, NET, ... parts of the IMG file
  struct hdr_subfile_part_t {
    quint16 length;  ///< 0x00000000 .. 0x00000001
    char type[10];   ///< 0x00000002 .. 0x0000000B
    quint8 byte0x0000000C;
    quint8 flag;  ///< 0x0000000D
    quint8 byte0x0000000E_0x00000014[7];
  };

  // TRE part header, to 0xB7
  struct hdr_tre_t : public hdr_subfile_part_t {
    quint24 northbound;   ///< 0x00000015 .. 0x00000017
    quint24 eastbound;    ///< 0x00000018 .. 0x0000001A
    quint24 southbound;   ///< 0x0000001B .. 0x0000001D
    quint24 westbound;    ///< 0x0000001E .. 0x00000020
    quint32 tre1_offset;  ///< 0x00000021 .. 0x00000024
    quint32 tre1_size;    ///< 0x00000025 .. 0x00000028
    quint32 tre2_offset;  ///< 0x00000029 .. 0x0000002C
    quint32 tre2_size;    ///< 0x0000002D .. 0x00000030
    quint32 tre3_offset;  ///< 0x00000031 .. 0x00000034
    quint32 tre3_size;    ///< 0x00000035 .. 0x00000038
    ///< 0x00000039 .. 0x0000003A
    quint16 tre3_rec_size;
    quint8 byte0x0000003B_0x0000003E[4];
    quint8 POI_flags;  ///< 0x0000003F
    quint8 byte0x00000040_0x00000049[10];
    quint32 tre4_offset;  ///< 0x0000004A .. 0x0000004D
    quint32 tre4_size;    ///< 0x0000004E .. 0x00000051
    ///< 0x00000052 .. 0x00000053
    quint16 tre4_rec_size;
    quint8 byte0x00000054_0x00000057[4];
    quint32 tre5_offset;  ///< 0x00000058 .. 0x0000005B
    quint32 tre5_size;    ///< 0x0000005C .. 0x0000005F
    ///< 0x00000060 .. 0x00000061
    quint16 tre5_rec_size;
    quint8 byte0x00000062_0x00000065[4];
    quint32 tre6_offset;  ///< 0x00000066 .. 0x00000069
    quint32 tre6_size;    ///< 0x0000006A .. 0x0000006D
    ///< 0x0000006E .. 0x0000006F
    quint16 tre6_rec_size;
    quint8 byte0x00000070_0x00000073[4];
    /*-----------------------------------------------------*/
    quint8 byte0x00000074_0x0000007B[8];
    // Object groups V2 (CTreGroup2).
    quint32 tre7_offset;  ///< 0x0000007C .. 0x0000007F //Groups2Offset
    quint32 tre7_size;    ///< 0x00000080 .. 0x00000083  //dwGroups2Length
    ///< 0x00000084 .. 0x00000085 //wGroup2RecSize
    quint16 tre7_rec_size;
    quint8 byte0x00000086_0x00000089[4];
    // Order: polyline, polygon, POI; each sorted by type (1 type 1 levels 1 subtype)
    quint32 tre8_offset;  ///< 0x0000008A .. 0x0000008D
    quint32 tre8_size;    ///< 0x0000008E .. 0x00000091
    ///< 0x00000092 .. 0x00000093
    quint16 tre8_rec_size;

    ///< 0x00000094 .. 0x00000095
    quint16 polyl2_types_num;
    ///< 0x00000096 .. 0x00000097
    quint16 polyg2_types_num;
    ///< 0x00000098 .. 0x00000099
    quint16 poi2_types_num;

    /*-----------------------------------------------------*/
    quint8 key[20];       ///< 0x0000009A .. 0x000000AD
    quint32 tre9_offset;  ///< 0x000000AE .. 0x000000B1
    quint32 tre9_size;    ///< 0x000000B2 .. 0x000000B5
    ///< 0x000000B6 .. 0x000000B7
    quint16 tre9_rec_size;
  };

  // RGN part header
  struct hdr_rgn_t : public hdr_subfile_part_t {
    quint32 offset;  ///< 0x00000015 .. 0x00000018
    quint32 length;  ///< 0x00000019 .. 0x0000001C
    ///< 0x0000001D .. 0x00000020
    quint32 offset_polyg2;
    ///< 0x00000021 .. 0x00000024
    quint32 length_polyg2;
    quint8 byte0x00000025_0x00000038[20];
    ///< 0x00000039 .. 0x0000003C
    quint32 offset_polyl2;
    ///< 0x0000003D .. 0x00000040
    quint32 length_polyl2;
    quint8 byte0x00000041_0x00000054[20];
    ///< 0x00000055 .. 0x00000058
    quint32 offset_point2;
    ///< 0x00000059 .. 0x0000005C
    quint32 length_point2;
  };

  // LBL part header
  struct hdr_lbl_t : public hdr_subfile_part_t {
    quint32 lbl1_offset;  ///< 0x00000015 .. 0x00000018
    quint32 lbl1_length;  ///< 0x00000019 .. 0x0000001C
    quint8 addr_shift;    ///< 0x0000001D
    quint8 coding;        ///< 0x0000001E
    quint32 lbl2_offset;  ///< 0x0000001F .. 0x00000022
    quint32 lbl2_length;  ///< 0x00000023 .. 0x00000026
    ///< 0x00000027 .. 0x00000028
    quint16 lbl2_rec_size;
    quint8 byte0x00000029_0x0000002C[4];
    quint32 lbl3_offset;  ///< 0x0000002D .. 0x00000030
    quint32 lbl3_length;  ///< 0x00000031 .. 0x00000034
    ///< 0x00000035 .. 0x00000036
    quint16 lbl3_rec_size;
    quint8 byte0x00000037_0x0000003A[4];
    quint32 lbl4_offset;  ///< 0x0000003B .. 0x0000003E
    quint32 lbl4_length;  ///< 0x0000003F .. 0x00000042
    ///< 0x00000043 .. 0x00000044
    quint16 lbl4_rec_size;
    quint8 byte0x00000045_0x00000048[4];
    quint32 lbl5_offset;  ///< 0x00000049 .. 0x0000004C
    quint32 lbl5_length;  ///< 0x0000004D .. 0x00000050
    ///< 0x00000051 .. 0x00000052
    quint16 lbl5_rec_size;
    quint8 byte0x00000053_0x00000056[4];
    quint32 lbl6_offset;  ///< 0x00000057 .. 0x0000005A
    quint32 lbl6_length;  ///< 0x0000005B .. 0x0000005E
    ///< 0x0000005F
    quint8 lbl6_addr_shift;
    ///< 0x00000060
    quint8 lbl6_glob_mask;
    quint8 byte0x00000061_0x00000063[3];
    quint32 lbl7_offset;  ///< 0x00000064 .. 0x00000067
    quint32 lbl7_length;  ///< 0x00000068 .. 0x0000006B
    ///< 0x0000006C .. 0x0000006D
    quint16 lbl7_rec_size;
    quint8 byte0x0000006E_0x00000071[4];
    quint32 lbl8_offset;  ///< 0x00000072 .. 0x00000075
    quint32 lbl8_length;  ///< 0x00000076 .. 0x00000079
    ///< 0x0000007A .. 0x0000007B
    quint16 lbl8_rec_size;
    quint8 byte0x0000007C_0x0000007F[4];
    quint32 lbl9_offset;  ///< 0x00000080 .. 0x00000083
    quint32 lbl9_length;  ///< 0x00000084 .. 0x00000087
    ///< 0x00000088 .. 0x00000089
    quint16 lbl9_rec_size;
    quint8 byte0x0000008A_0x0000008D[4];
    quint32 lbl10_offset;  ///< 0x0000008E .. 0x00000091
    quint32 lbl10_length;  ///< 0x00000092 .. 0x00000095
    ///< 0x00000096 .. 0x00000097
    quint16 lbl10_rec_size;
    quint8 byte0x00000098_0x0000009B[4];
    quint32 lbl11_offset;  ///< 0x0000009C .. 0x0000009F
    quint32 lbl11_length;  ///< 0x000000A0 .. 0x000000A3
    ///< 0x000000A4 .. 0x000000A5
    quint16 lbl11_rec_size;
    quint8 byte0x000000A6_0x000000A9[4];
    quint16 codepage;  ///< 0x000000AA .. 0x000000AB  optional check length
  };

  // NET part header
  struct hdr_net_t : public hdr_subfile_part_t {
    quint32 net1_offset;  ///< 0x00000015 .. 0x00000018
    quint32 net1_length;  ///< 0x00000019 .. 0x0000001C
    ///< 0x0000001D
    quint8 net1_addr_shift;
    quint32 net2_offset;  ///< 0x0000001E .. 0x00000021
    quint32 net2_length;  ///< 0x00000022 .. 0x00000025
    ///< 0x00000026
    quint8 net2_addr_shift;
    quint32 net3_offset;  ///< 0x00000027 .. 0x0000002A
    quint32 net3_length;  ///< 0x0000002B .. 0x0000002E
  };

#define TRE_MAP_LEVEL(r) ((r)->zoom & 0x0f)
#define TRE_MAP_INHER(r) (((r)->zoom & 0x80) != 0)

  // map level definition
  struct tre_map_level_t {
    quint8 zoom;
    quint8 bits;
    quint16 nsubdiv;
  };

  // map subdivision definition, without pointer to the lower level subparts
  struct tre_subdiv_t {
    quint24 rgn_offset;
    quint8 elements;
    quint24 center_lng;
    quint24 center_lat;
    quint16 width_trm;
#define TRE_SUBDIV_WIDTH(r) (gar_load(uint16_t, (r)->width_trm) & 0x7FFF)
#define TRE_SUBDIV_TERM(r) ((gar_load(uint16_t, (r)->width_trm) & 0x8000) != 0)
    quint16 height;
  };

  // pointer to the lower level subparts
  struct tre_subdiv_next_t : public tre_subdiv_t {
    quint16 next;
  };

  struct tre_subdiv2_t {
    quint32 offsetPolygons;
    quint32 offsetPolyline;
    quint32 offsetPoints;
    quint8 btObjects;
  };

#ifdef Q_OS_WIN64
#pragma pack()
#else
#pragma pack(0)
#endif
  struct map_level_t {
    quint8 bits;
    quint8 level;
    bool useBaseMap;

    bool operator==(const map_level_t& ml) const {
      return ml.bits == bits && ml.level == level && ml.useBaseMap == useBaseMap;
    }

    static bool GreaterThan(const map_level_t& ml1, const map_level_t& ml2) { return ml1.bits < ml2.bits; }
  };

  QString filename;
  quint8 mask;
  quint32 mask32;
  quint64 mask64;
  QString mapdesc;
  /// hold all subfile descriptors
  /**
      In a normal *.img file there is only one subfile. However
      gmapsupp.img files can hold several subfiles each with it's
      own subfile parts.
   */
  QMap<QString, subfile_desc_t> subfiles;
  /// relay the transparent flags from the subfiles
  bool transparent = false;

  QRectF maparea;

  QFontMetrics fm;

  /// combined maplevels of all tiles
  QVector<map_level_t> maplevels;

  QMap<quint32, CGarminTyp::polyline_property> polylineProperties;
  QMap<quint32, CGarminTyp::polygon_property> polygonProperties;
  QList<quint32> polygonDrawOrder;
  QMap<quint32, CGarminTyp::point_property> pointProperties;
  QMap<quint8, QString> languages;

  polytype_t polygons;
  polytype_t polylines;
  pointtype_t points;
  pointtype_t pois;

  QVector<strlbl_t> labels;

  struct textpath_t {
    // QPainterPath path;
    QPolygonF polyline;
    QString text;
    QFont font;
    QVector<qreal> lengths;
    qint32 lineWidth;
    QColor color;
  };

  QVector<textpath_t> textpaths;
  qint8 selectedLanguage;
  QSet<QString> copyrights;
};

#endif  // CMAPIMG_H
