"""
@package psmap.instructions

@brief Map feature objects

Classes:
 - dialogs::Instruction
 - dialogs::InstructionObject
 - dialogs::InitMap
 - dialogs::MapFrame
 - dialogs::PageSetup
 - dialogs::Mapinfo
 - dialogs::Text
 - dialogs::Image
 - dialogs::NorthArrow
 - dialogs::Point
 - dialogs::Line
 - dialogs::Rectangle
 - dialogs::Scalebar
 - dialogs::RasterLegend
 - dialogs::VectorLegend
 - dialogs::Raster
 - dialogs::Vector
 - dialogs::VProperties

(C) 2011-2012 by Anna Kratochvilova, and the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Anna Kratochvilova <kratochanna gmail.com> (bachelor's project)
@author Martin Landa <landa.martin gmail.com> (mentor)
"""

import os
import string
from math import ceil
from time import strftime, localtime

import wx
import grass.script as grass
from grass.script.task import cmdlist_to_tuple

from core.gcmd import GError, GMessage, GWarning
from core.utils import GetCmdString
from dbmgr.vinfo import VectorDBInfo
from gui_core.wrap import NewId as wxNewId
from psmap.utils import *


def NewId():
    return int(wxNewId())


class Instruction:
    """Class which represents instruction file"""

    def __init__(self, parent, objectsToDraw, env):
        self.parent = parent
        self.objectsToDraw = objectsToDraw
        # here are kept objects like mapinfo, rasterlegend, etc.
        self.instruction = list()
        self.env = env

    def __str__(self):
        """Returns text for instruction file"""
        comment = "# timestamp: " + strftime("%Y-%m-%d %H:%M", localtime()) + "\n"
        env = grass.gisenv()
        comment += "# location: %s\n" % env["LOCATION_NAME"]
        comment += "# mapset: %s\n" % env["MAPSET"]
        comment += (
            "# page orientation: %s\n"
            % self.FindInstructionByType("page")["Orientation"]
        )
        border = ""
        if not self.FindInstructionByType("map"):
            border = "border n\n"
        text = [str(each) for each in self.instruction]
        return comment + border + "\n".join(text) + "\nend"

    def __getitem__(self, id):
        for each in self.instruction:
            if each.id == id:
                return each
        return None

    def __contains__(self, id):
        """Test if instruction is included"""
        for each in self.instruction:
            if each.id == id:
                return True
        return False

    def __delitem__(self, id):
        """Delete instruction"""
        for each in self.instruction:
            if each.id == id:
                if each.type == "map":
                    # must remove raster, vector layers, labels too
                    vektor = self.FindInstructionByType("vector", list=True)
                    vProperties = self.FindInstructionByType("vProperties", list=True)
                    raster = self.FindInstructionByType("raster", list=True)
                    labels = self.FindInstructionByType("labels", list=True)
                    for item in vektor + vProperties + raster + labels:
                        if item in self.instruction:
                            self.instruction.remove(item)

                self.instruction.remove(each)
                if id in self.objectsToDraw:
                    self.objectsToDraw.remove(id)
                return

    def AddInstruction(self, instruction):
        """Add instruction"""
        # add to instructions
        if instruction.type == "map":
            self.instruction.insert(0, instruction)
        else:
            self.instruction.append(instruction)
        # add to drawable objects
        if instruction.type not in (
            "page",
            "raster",
            "vector",
            "vProperties",
            "initMap",
            "labels",
        ):
            if instruction.type == "map":
                self.objectsToDraw.insert(0, instruction.id)
            else:
                self.objectsToDraw.append(instruction.id)

    def FindInstructionByType(self, type, list=False):
        """Find instruction(s) with the given type"""
        inst = []
        for each in self.instruction:
            if each.type == type:
                inst.append(each)
        if len(inst) == 1 and not list:
            return inst[0]
        return inst

    def Read(self, filename):
        """Reads instruction file and creates instruction objects"""
        self.filename = filename
        # open file
        try:
            file = open(filename, encoding="Latin_1", errors="ignore")
        except OSError:
            GError(message=_("Unable to open file\n%s") % filename)
            return
        # first read file to get information about region and scaletype
        isRegionComment = False
        orientation = "Portrait"
        for line in file:
            if "# g.region" in line:
                self.SetRegion(regionInstruction=line)
                isRegionComment = True
                break
            if "# page orientation" in line:
                orientation = line.split(":")[-1].strip()

        if not isRegionComment:
            self.SetRegion(regionInstruction=None)
        # then run ps.map -b to get information for maploc
        # compute scale and center
        map = self.FindInstructionByType("map")
        region = grass.region(env=self.env)
        map["center"] = (region["n"] + region["s"]) / 2, (region["w"] + region["e"]) / 2
        mapRect = GetMapBounds(
            self.filename, portrait=(orientation == "Portrait"), env=self.env
        )
        map["rect"] = mapRect
        proj = projInfo()
        toM = 1.0
        if proj["units"]:
            toM = float(proj["meters"])
        units = UnitConversion(self.parent)
        w = units.convert(value=mapRect.Get()[2], fromUnit="inch", toUnit="meter") / toM
        map["scale"] = w / abs(region["w"] - region["e"])

        SetResolution(
            dpi=300, width=map["rect"].width, height=map["rect"].height, env=self.env
        )

        # read file again, now with information about map bounds
        isBuffer = False
        buffer = []
        instruction = None
        vectorMapNumber = 1
        file.seek(0)
        for line in file:
            if not line.strip():
                continue
            line = line.strip()
            if isBuffer:
                buffer.append(line)
                if "end" in line:
                    isBuffer = False
                    kwargs = {}
                    if instruction == "scalebar":
                        kwargs["scale"] = map["scale"]
                    elif instruction in ("text", "eps", "point", "line", "rectangle"):
                        kwargs["mapInstruction"] = map
                    elif instruction in ("vpoints", "vlines", "vareas"):
                        kwargs["id"] = NewId()
                        kwargs["vectorMapNumber"] = vectorMapNumber
                        vectorMapNumber += 1
                    elif instruction == "paper":
                        kwargs["Orientation"] = orientation

                    ok = self.SendToRead(instruction, buffer, **kwargs)
                    if not ok:
                        return False
                    buffer = []
                continue

            elif line.startswith("paper"):
                instruction = "paper"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("border"):
                if line.split()[1].lower() in ("n", "no", "none"):
                    ok = self.SendToRead("border", [line])
                    if not ok:
                        return False
                elif line.split()[1].lower() in ("y", "yes"):
                    instruction = "border"
                    isBuffer = True
                    buffer.append(line)

            elif line.startswith("scale "):
                if isBuffer:
                    continue
                ok = self.SendToRead("scale", line, isRegionComment=isRegionComment)
                if not ok:
                    return False

            elif line.startswith("maploc"):
                ok = self.SendToRead(instruction="maploc", text=line)
                if not ok:
                    return False

            elif line.startswith("raster"):
                ok = self.SendToRead(instruction="raster", text=line)
                if not ok:
                    return False

            elif line.startswith("mapinfo"):
                instruction = "mapinfo"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("scalebar"):
                instruction = "scalebar"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("text"):
                instruction = "text"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("eps"):
                instruction = "eps"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("point"):
                instruction = "point"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("line"):
                instruction = "line"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("rectangle"):
                instruction = "rectangle"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("colortable"):
                if len(line.split()) == 2 and line.split()[1].lower() in (
                    "n",
                    "no",
                    "none",
                ):
                    break
                instruction = "colortable"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("vlegend"):
                instruction = "vlegend"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("vpoints"):
                instruction = "vpoints"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("vlines"):
                instruction = "vlines"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("vareas"):
                instruction = "vareas"
                isBuffer = True
                buffer.append(line)

            elif line.startswith("labels"):
                instruction = "labels"
                isBuffer = True
                buffer.append(line)

        rasterLegend = self.FindInstructionByType("rasterLegend")
        raster = self.FindInstructionByType("raster")
        page = self.FindInstructionByType("page")
        vector = self.FindInstructionByType("vector")
        vectorLegend = self.FindInstructionByType("vectorLegend")
        vectorMaps = self.FindInstructionByType("vProperties", list=True)

        # check (in case of scaletype 0) if map is drawn also
        map["drawMap"] = False
        if map["scaleType"] == 0:
            mapForRegion = map["map"]
            if map["mapType"] == "raster" and raster:
                if mapForRegion == raster["raster"]:
                    map["drawMap"] = True
            elif map["mapType"] == "vector" and vector:
                for vmap in vector["list"]:
                    if mapForRegion == vmap[0]:
                        map["drawMap"] = True

        # rasterLegend
        if rasterLegend:
            if rasterLegend["rasterDefault"] and raster:
                rasterLegend["raster"] = raster["raster"]
                if not rasterLegend["discrete"]:
                    rasterType = getRasterType(map=rasterLegend["raster"])
                    if rasterType == "CELL":
                        rasterLegend["discrete"] = "y"
                    else:
                        rasterLegend["discrete"] = "n"

            # estimate size
            height = rasterLegend.EstimateHeight(
                raster=rasterLegend["raster"],
                discrete=rasterLegend["discrete"],
                fontsize=rasterLegend["fontsize"],
                cols=rasterLegend["cols"],
                height=rasterLegend["height"],
            )
            width = rasterLegend.EstimateWidth(
                raster=rasterLegend["raster"],
                discrete=rasterLegend["discrete"],
                fontsize=rasterLegend["fontsize"],
                cols=rasterLegend["cols"],
                width=rasterLegend["width"],
                paperInstr=page,
            )
            rasterLegend["rect"] = Rect2D(
                x=float(rasterLegend["where"][0]),
                y=float(rasterLegend["where"][1]),
                width=width,
                height=height,
            )

        # vectors, vlegend

        if vector:
            for vmap in vectorMaps:
                for i, each in enumerate(vector["list"]):
                    if each[2] == vmap.id:
                        vector["list"][i][4] = vmap["label"]
                        vector["list"][i][3] = vmap["lpos"]
            if vectorLegend:
                size = vectorLegend.EstimateSize(
                    vectorInstr=vector,
                    fontsize=vectorLegend["fontsize"],
                    width=vectorLegend["width"],
                    cols=vectorLegend["cols"],
                )
                vectorLegend["rect"] = Rect2D(
                    x=float(vectorLegend["where"][0]),
                    y=float(vectorLegend["where"][1]),
                    width=size[0],
                    height=size[1],
                )

        page = self.FindInstructionByType("page")
        if not page:
            page = PageSetup(NewId(), env=self.env)
            self.AddInstruction(page)
        else:
            page["Orientation"] = orientation

        #
        return True

    def SendToRead(self, instruction, text, **kwargs):
        psmapInstrDict = dict(
            paper=["page"],
            maploc=["map"],
            scale=["map"],
            border=["map"],
            raster=["raster"],
            mapinfo=["mapinfo"],
            scalebar=["scalebar"],
            text=["text"],
            eps=["image", "northArrow"],
            point=["point"],
            line=["line"],
            rectangle=["rectangle"],
            vpoints=["vector", "vProperties"],
            vlines=["vector", "vProperties"],
            vareas=["vector", "vProperties"],
            colortable=["rasterLegend"],
            vlegend=["vectorLegend"],
            labels=["labels"],
        )

        myInstrDict = dict(
            page=PageSetup,
            map=MapFrame,
            raster=Raster,
            mapinfo=Mapinfo,
            scalebar=Scalebar,
            text=Text,
            image=Image,
            northArrow=NorthArrow,
            point=Point,
            line=Line,
            rectangle=Rectangle,
            rasterLegend=RasterLegend,
            vectorLegend=VectorLegend,
            vector=Vector,
            vProperties=VProperties,
            labels=Labels,
        )

        myInstruction = psmapInstrDict[instruction]

        for i in myInstruction:
            instr = self.FindInstructionByType(i)
            if (
                i
                in (
                    "text",
                    "vProperties",
                    "image",
                    "northArrow",
                    "point",
                    "line",
                    "rectangle",
                )
                or not instr
            ):
                id = NewId()  # !vProperties expect subtype
                if i == "vProperties":
                    id = kwargs["id"]
                    newInstr = myInstrDict[i](
                        id,
                        subType=instruction[1:],
                        env=self.env,
                    )
                elif i in ("image", "northArrow"):
                    commentFound = False
                    for line in text:
                        if line.find("# north arrow") >= 0:
                            commentFound = True
                    if (
                        i == "image"
                        and commentFound
                        or i == "northArrow"
                        and not commentFound
                    ):
                        continue
                    newInstr = myInstrDict[i](id, settings=self, env=self.env)
                else:
                    newInstr = myInstrDict[i](id, env=self.env)
                ok = newInstr.Read(instruction, text, **kwargs)
                if ok:
                    self.AddInstruction(newInstr)
                else:
                    return False

            else:
                ok = instr.Read(instruction, text, **kwargs)

                if not ok:
                    return False
        return True

    def SetRegion(self, regionInstruction):
        """Sets region from file comment or sets current region in case of no comment"""
        map = MapFrame(NewId(), env=self.env)
        self.AddInstruction(map)
        if regionInstruction:
            cmd = cmdlist_to_tuple(regionInstruction.strip("# ").split())

            # define scaleType
            if len(cmd[1]) <= 3:
                if "raster" in cmd[1]:
                    map["scaleType"] = 0
                    map["mapType"] = "raster"
                    map["map"] = cmd[1]["raster"]
                elif "vector" in cmd[1]:
                    map["scaleType"] = 0
                    map["mapType"] = "vector"
                    map["map"] = cmd[1]["vector"]
                elif "region" in cmd[1]:
                    map["scaleType"] = 1
                    map["region"] = cmd[1]["region"]

            else:
                map["scaleType"] = 2
        else:
            map["scaleType"] = 2
            region = grass.region(env=None)
            cmd = ["g.region", region]
        cmdString = GetCmdString(cmd).replace("g.region", "")
        GMessage(
            _("Instruction file will be loaded with following region: %s\n") % cmdString
        )
        try:
            self.env["GRASS_REGION"] = grass.region_env(env=self.env, **cmd[1])

        except grass.ScriptError as e:
            GError(_("Region cannot be set\n%s") % e)
            return False


class InstructionObject:
    """Abstract class representing single instruction"""

    def __init__(self, id, env):
        self.id = id
        self.env = env

        # default values
        self.defaultInstruction = dict()
        # current values
        self.instruction = self.defaultInstruction
        # converting units
        self.unitConv = UnitConversion()

    def __str__(self):
        """Returns particular part of text instruction"""
        return ""

    def __getitem__(self, key):
        for each in self.instruction.keys():
            if each == key:
                return self.instruction[key]
        return None

    def __setitem__(self, key, value):
        self.instruction[key] = value

    def GetInstruction(self):
        """Get current values"""
        return self.instruction

    def SetInstruction(self, instruction):
        """Set default values"""
        self.instruction = instruction

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save them"""
        pass

    def PercentToReal(self, e, n):
        """Converts text coordinates from percent of region to map coordinates"""
        e, n = float(e.strip("%")), float(n.strip("%"))
        region = grass.region(env=self.env)
        N = region["s"] + (region["n"] - region["s"]) / 100 * n
        E = region["w"] + (region["e"] - region["w"]) / 100 * e
        return E, N


class InitMap(InstructionObject):
    """Class representing virtual map"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "initMap"

        # default values
        self.defaultInstruction = dict(rect=None, scale=None)
        # current values
        self.instruction = dict(self.defaultInstruction)


class MapFrame(InstructionObject):
    """Class representing map (instructions maploc, scale, border)"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "map"
        # default values
        self.defaultInstruction = dict(
            map=None,
            mapType=None,
            drawMap=True,
            region=None,
            rect=Rect2D(),
            scaleType=0,
            scale=None,
            center=None,
            resolution=300,
            border="y",
            width=1,
            color="0:0:0",
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = ""
        comment = ""

        # region settings
        region = grass.region(env=self.env)
        if self.instruction["scaleType"] == 0:  # match map
            map = self.instruction["map"]
            if self.instruction["mapType"] == "raster":
                comment = "# g.region raster=%s nsres=%s ewres=%s\n" % (
                    map,
                    region["nsres"],
                    region["ewres"],
                )
            else:
                comment = "# g.region vector=%s\n" % (map)
        elif self.instruction["scaleType"] == 1:  # saved region
            region = self.instruction["region"]
            comment = "# g.region region=%s\n" % region
        # current region, fixed scale
        elif self.instruction["scaleType"] in (2, 3):
            comment = string.Template(
                "# g.region n=$n s=$s e=$e w=$w rows=$rows cols=$cols \n"
            ).substitute(**region)

        instr += comment
        instr += "\n"
        # maploc
        maplocInstruction = "maploc %.3f %.3f" % (
            self.instruction["rect"].x,
            self.instruction["rect"].y,
        )
        if self.instruction["scaleType"] != 3:
            maplocInstruction += "  %.3f %.3f" % (
                self.instruction["rect"].width,
                self.instruction["rect"].height,
            )
        instr += maplocInstruction
        instr += "\n"

        # scale
        if self.instruction["scaleType"] == 3:  # fixed scale
            scaleInstruction = "scale 1:%.0f" % (1 / self.instruction["scale"])
            instr += scaleInstruction
            instr += "\n"
        # border
        borderInstruction = ""
        if self.instruction["border"] == "n":
            borderInstruction = "border n"
        else:
            borderInstruction = "border y\n"
            borderInstruction += string.Template(
                "    width $width\n    color $color\n"
            ).substitute(self.instruction)
            borderInstruction += "    end"
        instr += borderInstruction
        instr += "\n"

        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        if "isRegionComment" in kwargs:
            isRegionComment = kwargs["isRegionComment"]
        instr = {}

        if instruction == "border":
            for line in text:
                if line.startswith("end"):
                    break
                try:
                    if line.split()[1].lower() in ("n", "no", "none"):
                        instr["border"] = "n"
                        break
                    elif line.split()[1].lower() in ("y", "yes"):
                        instr["border"] = "y"
                    elif line.startswith("width"):
                        instr["width"] = line.split()[1]
                    elif line.startswith("color"):
                        instr["color"] = line.split()[1]
                except IndexError:
                    GError(_("Failed to read instruction %s") % instruction)
                    return False

        elif instruction == "scale":
            try:
                scaleText = text.strip("scale ").split(":")[1]
                # when scale instruction given and region comment also, then
                # scaletype is fixed scale
                if not isRegionComment:
                    instr["scaleType"] = 2
                else:
                    instr["scaleType"] = 3

                scale = 1 / float(scaleText)
                if abs(scale - self.instruction["scale"]) > (0.01 * scale):
                    GWarning(
                        _("Scale has changed, old value: %(old)s\nnew value: %(new)s")
                        % {"old": scale, "new": self.instruction["scale"]}
                    )
            except (ValueError, IndexError):
                GError(
                    _("Failed to read instruction %s.\nUse 1:25000 notation.")
                    % instruction
                )
                return False

        elif instruction == "maploc":
            maploc = text.strip("maploc ").split()
            if len(maploc) >= 2:
                if (
                    abs(self.instruction["rect"].Get()[0] - float(maploc[0])) > 0.5
                    or abs(self.instruction["rect"].Get()[1] - float(maploc[1])) > 0.5
                ):
                    GWarning(
                        _(
                            "Map frame position changed, old value: %(old1)s %(old2)s\n"
                            "new value: %(new1)s %(new2)s"
                        )
                        % {
                            "old1": maploc[0],
                            "old2": maploc[1],
                            "new1": self.instruction["rect"].Get()[0],
                            "new2": self.instruction["rect"].Get()[1],
                        }
                    )

                # instr["rect"] = wx.Rect2D(
                #     float(maploc[0]),
                #     float(maploc[1]),
                #     self.instruction["rect"][2],
                #     self.instruction["rect"][3],
                # )
            if len(maploc) == 4:
                if (
                    abs(self.instruction["rect"].Get()[2] - float(maploc[2])) > 0.5
                    or abs(self.instruction["rect"].Get()[3] - float(maploc[3])) > 0.5
                ):
                    GWarning(
                        _(
                            "Map frame size changed, old value: %(old1)s %(old2)s\n"
                            "new value: %(new1)s %(new2)s"
                        )
                        % {
                            "old1": maploc[2],
                            "old2": maploc[3],
                            "new1": self.instruction["rect"].Get()[2],
                            "new2": self.instruction["rect"].Get()[3],
                        }
                    )
                # instr['rect'] = wx.Rect2D(*map(float, maploc))
        self.instruction.update(instr)
        return True


class PageSetup(InstructionObject):
    """Class representing page instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "page"
        # default values
        self.defaultInstruction = dict(
            Units="inch",
            Format="a4",
            Orientation="Portrait",
            Width=8.268,
            Height=11.693,
            Left=0.5,
            Right=0.5,
            Top=1,
            Bottom=1,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        if self.instruction["Format"] == "custom":
            instr = string.Template(
                "paper\n    width $Width\n    height $Height\n"
            ).substitute(self.instruction)
        else:
            instr = string.Template("paper $Format\n").substitute(self.instruction)
        instr += string.Template(
            "    left $Left\n    right $Right\n    bottom $Bottom\n    top $Top\n"
            "    end"
        ).substitute(self.instruction)

        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        instr = {}
        self.cats = ["Width", "Height", "Left", "Right", "Top", "Bottom"]
        self.subInstr = dict(
            zip(["width", "height", "left", "right", "top", "bottom"], self.cats)
        )

        if instruction == "paper":  # just for sure
            for line in text:
                if line.startswith("paper"):
                    if len(line.split()) > 1:
                        pformat = line.split()[1]
                        availableFormats = self._toDict(
                            grass.read_command("ps.map", flags="p", quiet=True)
                        )
                        # e.g. paper a3
                        try:
                            instr["Format"] = pformat
                            for key, value in availableFormats[pformat].items():
                                instr[key] = float(value)
                            break
                        except KeyError:
                            GError(
                                _(
                                    "Failed to read instruction %(file)s.\n"
                                    "Unknown format %(for)s"
                                )
                                % {"file": instruction, "for": format}
                            )
                            return False
                    else:
                        # paper
                        # width ...
                        instr["Format"] = "custom"
                # read subinstructions
                elif instr["Format"] == "custom" and not line.startswith("end"):
                    text = line.split()
                    try:
                        instr[self.subInstr[text[0]]] = float(text[1])
                    except (IndexError, KeyError):
                        GError(_("Failed to read instruction %s.") % instruction)
                        return False

            if "Orientation" in kwargs and kwargs["Orientation"] == "Landscape":
                instr["Width"], instr["Height"] = instr["Height"], instr["Width"]

            self.instruction.update(instr)
        return True

    def _toDict(self, paperStr):
        sizeDict = dict()
        #     cats = self.subInstr[ 'Width', 'Height', 'Left', 'Right', 'Top', 'Bottom']
        for line in paperStr.strip().split("\n"):
            d = dict(zip(self.cats, line.split()[1:]))
            sizeDict[line.split()[0]] = d

        return sizeDict


class Mapinfo(InstructionObject):
    """Class representing mapinfo instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "mapinfo"
        # default values
        self.defaultInstruction = dict(
            unit="inch",
            where=(0, 0),
            font="Helvetica",
            fontsize=10,
            color="0:0:0",
            background="none",
            border="none",
            rect=None,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = "mapinfo\n"
        instr += "    where %.3f %.3f\n" % (
            self.instruction["where"][0],
            self.instruction["where"][1],
        )
        instr += string.Template(
            "    font $font\n    fontsize $fontsize\n    color $color\n"
        ).substitute(self.instruction)
        instr += string.Template(
            "    background $background\n    border $border\n"
        ).substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text):
        """Read instruction and save information"""
        instr = {}
        try:
            for line in text:
                sub = line.split(None, 1)
                if sub[0] == "font":
                    instr["font"] = sub[1]
                elif sub[0] == "fontsize":
                    instr["fontsize"] = int(sub[1])
                elif sub[0] == "color":
                    instr["color"] = sub[1]
                elif sub[0] == "background":
                    instr["background"] = sub[1]
                elif sub[0] == "border":
                    instr["border"] = sub[1]
                elif sub[0] == "where":
                    instr["where"] = float(sub[1].split()[0]), float(sub[1].split()[1])
        except (ValueError, IndexError):
            GError(_("Failed to read instruction %s") % instruction)
            return False
        self.instruction.update(instr)
        self.instruction["rect"] = self.EstimateRect(mapinfoDict=self.instruction)
        return True

    def EstimateRect(self, mapinfoDict):
        """Estimate size to draw mapinfo"""
        w = mapinfoDict["fontsize"] * 20  # any better estimation?
        h = mapinfoDict["fontsize"] * 7
        width = self.unitConv.convert(value=w, fromUnit="point", toUnit="inch")
        height = self.unitConv.convert(value=h, fromUnit="point", toUnit="inch")
        return Rect2D(
            x=float(mapinfoDict["where"][0]),
            y=float(mapinfoDict["where"][1]),
            width=width,
            height=height,
        )


class Text(InstructionObject):
    """Class representing text instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "text"
        # default values
        self.defaultInstruction = dict(
            text="",
            font="Helvetica",
            fontsize=10,
            color="black",
            background="none",
            hcolor="none",
            hwidth=1,
            border="none",
            width="1",
            XY=True,
            where=(0, 0),
            unit="inch",
            rotate=None,
            ref="center center",
            xoffset=0,
            yoffset=0,
            east=None,
            north=None,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        text = self.instruction["text"].replace("\n", "\\n")
        instr = "text %s %s" % (self.instruction["east"], self.instruction["north"])
        instr += " %s\n" % text
        instr += string.Template(
            "    font $font\n    fontsize $fontsize\n    color $color\n"
        ).substitute(self.instruction)
        instr += string.Template("    hcolor $hcolor\n").substitute(self.instruction)
        if self.instruction["hcolor"] != "none":
            instr += string.Template("    hwidth $hwidth\n").substitute(
                self.instruction
            )
        instr += string.Template("    border $border\n").substitute(self.instruction)
        if self.instruction["border"] != "none":
            instr += string.Template("    width $width\n").substitute(self.instruction)
        instr += string.Template("    background $background\n").substitute(
            self.instruction
        )
        if self.instruction["ref"] != "0":
            instr += string.Template("    ref $ref\n").substitute(self.instruction)
        if self.instruction["rotate"]:
            instr += string.Template("    rotate $rotate\n").substitute(
                self.instruction
            )
        if float(self.instruction["xoffset"]) or float(self.instruction["yoffset"]):
            instr += string.Template(
                "    xoffset $xoffset\n    yoffset $yoffset\n"
            ).substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        map = kwargs["mapInstruction"]
        instr = {}
        for line in text:
            try:
                sub = line.split(None, 1)[0]
                if sub == "text":
                    e, n = line.split(None, 3)[1:3]
                    if "%" in e and "%" in n:
                        instr["XY"] = True
                        instr["east"], instr["north"] = self.PercentToReal(e, n)
                    else:
                        instr["XY"] = False
                        instr["east"], instr["north"] = float(e), float(n)

                    instr["text"] = line.split(None, 3)[3]

                elif sub == "font":
                    instr["font"] = line.split(None, 1)[1]
                elif sub == "fontsize":
                    instr["fontsize"] = float(line.split(None, 1)[1])
                elif sub == "color":
                    instr["color"] = line.split(None, 1)[1]
                elif sub == "width":
                    instr["width"] = line.split(None, 1)[1]
                elif sub == "hcolor":
                    instr["hcolor"] = line.split(None, 1)[1]
                elif sub == "hwidth":
                    instr["hwidth"] = line.split(None, 1)[1]
                elif sub == "background":
                    instr["background"] = line.split(None, 1)[1]
                elif sub == "border":
                    instr["border"] = line.split(None, 1)[1]
                elif sub == "ref":
                    instr["ref"] = line.split(None, 1)[1]
                elif sub == "rotate":
                    instr["rotate"] = float(line.split(None, 1)[1])
                elif sub == "xoffset":
                    instr["xoffset"] = int(line.split(None, 1)[1])
                elif sub == "yoffset":
                    instr["yoffset"] = int(line.split(None, 1)[1])
                elif sub == "opaque":
                    if line.split(None, 1)[1].lower() in ("n", "none"):
                        instr["background"] = "none"

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False
        instr["where"] = PaperMapCoordinates(
            mapInstr=map,
            x=instr["east"],
            y=instr["north"],
            paperToMap=False,
            env=self.env,
        )
        self.instruction.update(instr)

        return True


class Image(InstructionObject):
    """Class representing eps instruction - image"""

    def __init__(self, id, settings, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.settings = settings
        self.type = "image"
        # default values
        self.defaultInstruction = dict(
            epsfile="",
            XY=True,
            where=(0, 0),
            unit="inch",
            east=None,
            north=None,
            rotate=None,
            scale=1,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        self.ChangeRefPoint(toCenter=True)
        epsfile = self.instruction["epsfile"].replace(os.getenv("GISBASE"), "$GISBASE")

        instr = "eps %s %s\n" % (self.instruction["east"], self.instruction["north"])
        instr += "    epsfile %s\n" % epsfile
        if self.instruction["rotate"]:
            instr += string.Template("    rotate $rotate\n").substitute(
                self.instruction
            )
        if self.instruction["scale"]:
            instr += string.Template("    scale $scale\n").substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        mapInstr = kwargs["mapInstruction"]
        instr = {}
        for line in text:
            try:
                sub = line.split(None, 1)[0]
                if sub == "eps":
                    e, n = line.split(None, 3)[1:3]
                    if "%" in e and "%" in n:
                        instr["XY"] = True
                        instr["east"], instr["north"] = self.PercentToReal(e, n)
                    else:
                        instr["XY"] = False
                        instr["east"], instr["north"] = float(e), float(n)

                elif sub == "epsfile":
                    epsfile = line.split(None, 1)[1]
                    instr["epsfile"] = epsfile.replace("$GISBASE", os.getenv("GISBASE"))
                elif sub == "rotate":
                    instr["rotate"] = float(line.split(None, 1)[1])
                elif sub == "scale":
                    instr["scale"] = float(line.split(None, 1)[1])

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False
        if not os.path.exists(instr["epsfile"]):
            GError(
                _("Failed to read instruction %(inst)s: " "file %(file)s not found.")
                % {"inst": instruction, "file": instr["epsfile"]}
            )
            return False

        instr["epsfile"] = os.path.abspath(instr["epsfile"])
        instr["size"] = self.GetImageOrigSize(instr["epsfile"])
        if "rotate" in instr:
            instr["size"] = BBoxAfterRotation(
                instr["size"][0], instr["size"][1], instr["rotate"]
            )
        self.instruction.update(instr)
        self.ChangeRefPoint(toCenter=False)
        instr["where"] = PaperMapCoordinates(
            mapInstr=mapInstr,
            x=self.instruction["east"],
            y=self.instruction["north"],
            paperToMap=False,
            env=self.env,
        )
        w = self.unitConv.convert(
            value=instr["size"][0], fromUnit="point", toUnit="inch"
        )
        h = self.unitConv.convert(
            value=instr["size"][1], fromUnit="point", toUnit="inch"
        )
        instr["rect"] = Rect2D(
            x=float(instr["where"][0]),
            y=float(instr["where"][1]),
            width=w * self.instruction["scale"],
            height=h * self.instruction["scale"],
        )
        self.instruction.update(instr)

        return True

    def ChangeRefPoint(self, toCenter):
        """Change reference point (left top x center)"""
        mapInstr = self.settings.FindInstructionByType("map")
        if not mapInstr:
            mapInstr = self.settings.FindInstructionByType("initMap")
        mapId = mapInstr.id
        if toCenter:
            center = self.instruction["rect"].GetCentre()
            ENCenter = PaperMapCoordinates(
                mapInstr=self.settings[mapId],
                x=center[0],
                y=center[1],
                paperToMap=True,
                env=self.env,
            )

            self.instruction["east"], self.instruction["north"] = ENCenter
        else:
            x, y = PaperMapCoordinates(
                mapInstr=self.settings[mapId],
                x=self.instruction["east"],
                y=self.instruction["north"],
                paperToMap=False,
                env=self.env,
            )
            w = self.unitConv.convert(
                value=self.instruction["size"][0], fromUnit="point", toUnit="inch"
            )
            h = self.unitConv.convert(
                value=self.instruction["size"][1], fromUnit="point", toUnit="inch"
            )
            x -= w * self.instruction["scale"] / 2
            y -= h * self.instruction["scale"] / 2
            e, n = PaperMapCoordinates(
                mapInstr=self.settings[mapId], x=x, y=y, paperToMap=True, env=self.env
            )
            self.instruction["east"], self.instruction["north"] = e, n

    def GetImageOrigSize(self, imagePath):
        """Get image size.

        If eps, size is read from image header.
        """
        fileName = os.path.split(imagePath)[1]
        # if eps, read info from header
        if os.path.splitext(fileName)[1].lower() == ".eps":
            bbInfo = "%%BoundingBox"
            file = open(imagePath, "r")
            w = h = 0
            while file:
                line = file.readline()
                if line.find(bbInfo) == 0:
                    w, h = line.split()[3:5]
                    break
            file.close()
            return float(w), float(h)
        else:  # we can use wx.Image
            img = wx.Image(fileName, type=wx.BITMAP_TYPE_ANY)
            return img.GetWidth(), img.GetHeight()


class NorthArrow(Image):
    """Class representing eps instruction -- North Arrow"""

    def __init__(self, id, settings, env):
        Image.__init__(self, id=id, settings=settings, env=env)
        self.type = "northArrow"

    def __str__(self):
        self.ChangeRefPoint(toCenter=True)
        epsfile = self.instruction["epsfile"].replace(os.getenv("GISBASE"), "$GISBASE")

        instr = "eps %s %s\n" % (self.instruction["east"], self.instruction["north"])
        instr += "# north arrow\n"
        instr += "    epsfile %s\n" % epsfile
        if self.instruction["rotate"]:
            instr += string.Template("    rotate $rotate\n").substitute(
                self.instruction
            )
        if self.instruction["scale"]:
            instr += string.Template("    scale $scale\n").substitute(self.instruction)
        instr += "    end"
        return instr


class Point(InstructionObject):
    """Class representing point instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "point"
        # default values
        self.defaultInstruction = dict(
            symbol=os.path.join("basic", "x"),
            color="0:0:0",
            fcolor="200:200:200",
            rotate=0,
            size=10,
            XY=True,
            where=(0, 0),
            unit="inch",
            east=None,
            north=None,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = string.Template("point $east $north\n").substitute(self.instruction)
        instr += string.Template("    symbol $symbol\n").substitute(self.instruction)
        instr += string.Template("    color $color\n").substitute(self.instruction)
        instr += string.Template("    fcolor $fcolor\n").substitute(self.instruction)
        instr += string.Template("    rotate $rotate\n").substitute(self.instruction)
        instr += string.Template("    size $size\n").substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        mapInstr = kwargs["mapInstruction"]
        instr = {}
        for line in text:
            try:
                sub = line.split(None, 1)[0]
                if sub == "point":
                    e, n = line.split(None, 3)[1:3]
                    if "%" in e and "%" in n:
                        instr["XY"] = True
                        instr["east"], instr["north"] = self.PercentToReal(e, n)
                    else:
                        instr["XY"] = False
                        instr["east"], instr["north"] = float(e), float(n)

                elif sub == "symbol":
                    instr["symbol"] = line.split(None, 1)[1]
                elif sub == "rotate":
                    instr["rotate"] = float(line.split(None, 1)[1])
                elif sub == "size":
                    instr["size"] = float(line.split(None, 1)[1])
                elif sub == "color":
                    instr["color"] = line.split(None, 1)[1]
                elif sub == "fcolor":
                    instr["fcolor"] = line.split(None, 1)[1]

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        self.instruction.update(instr)
        instr["where"] = PaperMapCoordinates(
            mapInstr=mapInstr,
            x=self.instruction["east"],
            y=self.instruction["north"],
            paperToMap=False,
            env=self.env,
        )
        w = h = self.unitConv.convert(
            value=instr["size"], fromUnit="point", toUnit="inch"
        )
        instr["rect"] = Rect2D(
            x=float(instr["where"][0]) - w / 2,
            y=float(instr["where"][1] - h / 2),
            width=w,
            height=h,
        )
        self.instruction.update(instr)

        return True


class Line(InstructionObject):
    """Class representing line instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "line"
        # default values
        self.defaultInstruction = dict(
            color="0:0:0",
            width=2,
            where=[wx.Point2D(), wx.Point2D()],
            east1=None,
            north1=None,
            east2=None,
            north2=None,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = string.Template("line $east1 $north1 $east2 $north2\n").substitute(
            self.instruction
        )
        instr += string.Template("    color $color\n").substitute(self.instruction)
        instr += string.Template("    width $width\n").substitute(self.instruction)
        instr += "    end\n"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        mapInstr = kwargs["mapInstruction"]
        instr = {}
        for line in text:
            try:
                sub = line.split(None, 1)[0]
                if sub == "line":
                    e1, n1, e2, n2 = line.split(None, 5)[1:5]
                    if "%" in e1 and "%" in n1 and "%" in e2 and "%" in n2:
                        instr["east1"], instr["north1"] = self.PercentToReal(e1, n1)
                        instr["east2"], instr["north2"] = self.PercentToReal(e2, n2)
                    else:
                        instr["east1"], instr["north1"] = float(e1), float(n1)
                        instr["east2"], instr["north2"] = float(e2), float(n2)

                elif sub == "width":
                    instr["width"] = float(line.split(None, 1)[1])
                elif sub == "color":
                    instr["color"] = line.split(None, 1)[1]

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        self.instruction.update(instr)
        e1, n1 = PaperMapCoordinates(
            mapInstr=mapInstr,
            x=self.instruction["east1"],
            y=self.instruction["north1"],
            paperToMap=False,
            env=self.env,
        )
        e2, n2 = PaperMapCoordinates(
            mapInstr=mapInstr,
            x=self.instruction["east2"],
            y=self.instruction["north2"],
            paperToMap=False,
            env=self.env,
        )
        instr["where"] = [wx.Point2D(e1, n1), wx.Point2D(e2, n2)]
        instr["rect"] = Rect2DPP(instr["where"][0], instr["where"][1])
        self.instruction.update(instr)

        return True


class Rectangle(InstructionObject):
    """Class representing rectangle instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "rectangle"
        # default values
        self.defaultInstruction = dict(
            color="0:0:0",
            fcolor="none",
            width=2,
            east1=None,
            north1=None,
            east2=None,
            north2=None,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = string.Template("rectangle $east1 $north1 $east2 $north2\n").substitute(
            self.instruction
        )
        instr += string.Template("    color $color\n").substitute(self.instruction)
        instr += string.Template("    fcolor $fcolor\n").substitute(self.instruction)
        instr += string.Template("    width $width\n").substitute(self.instruction)
        instr += "    end\n"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        mapInstr = kwargs["mapInstruction"]
        instr = {}
        for line in text:
            try:
                sub = line.split(None, 1)[0]
                if sub == "rectangle":
                    e1, n1, e2, n2 = line.split(None, 5)[1:5]
                    if "%" in e1 and "%" in n1 and "%" in e2 and "%" in n2:
                        instr["east1"], instr["north1"] = self.PercentToReal(e1, n1)
                        instr["east2"], instr["north2"] = self.PercentToReal(e2, n2)
                    else:
                        instr["east1"], instr["north1"] = float(e1), float(n1)
                        instr["east2"], instr["north2"] = float(e2), float(n2)

                elif sub == "width":
                    instr["width"] = float(line.split(None, 1)[1])
                elif sub == "color":
                    instr["color"] = line.split(None, 1)[1]
                elif sub == "fcolor":
                    instr["fcolor"] = line.split(None, 1)[1]

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        self.instruction.update(instr)
        e1, n1 = PaperMapCoordinates(
            mapInstr=mapInstr,
            x=self.instruction["east1"],
            y=self.instruction["north1"],
            paperToMap=False,
            env=self.env,
        )
        e2, n2 = PaperMapCoordinates(
            mapInstr=mapInstr,
            x=self.instruction["east2"],
            y=self.instruction["north2"],
            paperToMap=False,
            env=self.env,
        )
        instr["rect"] = Rect2DPP(wx.Point2D(e1, n1), wx.Point2D(e2, n2))
        self.instruction.update(instr)

        return True


class Scalebar(InstructionObject):
    """Class representing scalebar instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "scalebar"
        # default values
        self.defaultInstruction = dict(
            unit="inch",
            where=(1, 1),
            unitsLength="auto",
            unitsHeight="inch",
            length=None,
            height=0.1,
            rect=None,
            fontsize=10,
            background="y",
            scalebar="f",
            segment=4,
            numbers=1,
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = string.Template("scalebar $scalebar\n").substitute(self.instruction)
        instr += "    where %.3f %.3f\n" % (
            self.instruction["where"][0],
            self.instruction["where"][1],
        )
        instr += string.Template(
            "    length $length\n    units $unitsLength\n"
        ).substitute(self.instruction)
        instr += string.Template("    height $height\n").substitute(self.instruction)
        instr += string.Template(
            "    segment $segment\n    numbers $numbers\n"
        ).substitute(self.instruction)
        instr += string.Template(
            "    fontsize $fontsize\n    background $background\n"
        ).substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        scale = kwargs["scale"]
        instr = {}
        for line in text:
            try:
                if line.startswith("scalebar"):
                    if "scalebar s" in line:
                        instr["scalebar"] = "s"
                    else:
                        instr["scalebar"] = "f"
                elif line.startswith("where"):
                    instr["where"] = list(map(float, line.split()[1:3]))
                elif line.startswith("length"):
                    instr["length"] = float(line.split()[1])
                elif line.startswith("units"):
                    if line.split()[1] in [
                        "auto",
                        "meters",
                        "kilometers",
                        "feet",
                        "miles",
                        "nautmiles",
                    ]:
                        instr["unitsLength"] = line.split()[1]
                elif line.startswith("height"):
                    instr["height"] = float(line.split()[1])
                elif line.startswith("fontsize"):
                    instr["fontsize"] = float(line.split()[1])
                elif line.startswith("numbers"):
                    instr["numbers"] = int(line.split()[1])
                elif line.startswith("segment"):
                    instr["segment"] = int(line.split()[1])
                elif line.startswith("background"):
                    if line.split()[1].strip().lower() in ("y", "yes"):
                        instr["background"] = "y"
                    elif line.split()[1].strip().lower() in ("n", "no", "none"):
                        instr["background"] = "n"
            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        self.instruction.update(instr)
        w, h = self.EstimateSize(scalebarDict=self.instruction, scale=scale)
        x = self.instruction["where"][0] - w / 2
        y = self.instruction["where"][1] - h / 2
        self.instruction["rect"] = Rect2D(x, y, w, h)
        return True

    def EstimateSize(self, scalebarDict, scale):
        """Estimate size to draw scalebar"""
        units = projInfo()["units"]
        if not units or units not in self.unitConv.getAllUnits():
            units = "meters"
        if scalebarDict["unitsLength"] != "auto":
            length = self.unitConv.convert(
                value=scalebarDict["length"],
                fromUnit=scalebarDict["unitsLength"],
                toUnit="inch",
            )
        else:
            length = self.unitConv.convert(
                value=scalebarDict["length"], fromUnit=units, toUnit="inch"
            )

        length *= scale
        length *= 1.1  # for numbers on the edge
        height = scalebarDict["height"] + 2 * self.unitConv.convert(
            value=scalebarDict["fontsize"], fromUnit="point", toUnit="inch"
        )
        return (length, height)


class RasterLegend(InstructionObject):
    """Class representing colortable instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "rasterLegend"
        # default values
        self.defaultInstruction = dict(
            rLegend=False,
            unit="inch",
            rasterDefault=True,
            raster=None,
            discrete=None,
            type=None,
            where=(0, 0),
            width=None,
            height=None,
            cols=1,
            font="Helvetica",
            fontsize=10,
            # color = '0:0:0', tickbar = False,
            # range = False, min = 0, max = 0,
            color="black",
            tickbar="n",
            range=False,
            min=0,
            max=0,
            nodata="n",
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = "colortable y\n"
        instr += string.Template("    raster $raster\n").substitute(self.instruction)
        instr += "    where %.3f %.3f\n" % (
            self.instruction["where"][0],
            self.instruction["where"][1],
        )
        if self.instruction["width"]:
            instr += string.Template("    width $width\n").substitute(self.instruction)
        instr += string.Template("    discrete $discrete\n").substitute(
            self.instruction
        )
        if self.instruction["discrete"] == "n":
            if self.instruction["height"]:
                instr += string.Template("    height $height\n").substitute(
                    self.instruction
                )
            instr += string.Template("    tickbar $tickbar\n").substitute(
                self.instruction
            )
            if self.instruction["range"]:
                instr += string.Template("    range $min $max\n").substitute(
                    self.instruction
                )
        else:
            instr += string.Template("    cols $cols\n").substitute(self.instruction)
            instr += string.Template("    nodata $nodata\n").substitute(
                self.instruction
            )
        instr += string.Template(
            "    font $font\n    fontsize $fontsize\n    color $color\n"
        ).substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        instr = {}
        instr["rLegend"] = True
        for line in text:
            try:
                if line.startswith("where"):
                    instr["where"] = list(map(float, line.split()[1:3]))
                elif line.startswith("font "):
                    instr["font"] = line.split()[1]
                elif line.startswith("fontsize"):
                    instr["fontsize"] = float(line.split()[1])
                elif line.startswith("color "):
                    instr["color"] = line.split()[1]
                elif line.startswith("raster"):
                    instr["raster"] = line.split()[1]
                elif line.startswith("width"):
                    instr["width"] = float(line.split()[1])
                elif line.startswith("height"):
                    instr["height"] = float(line.split()[1])
                elif line.startswith("cols"):
                    instr["cols"] = int(line.split()[1])
                elif line.startswith("range"):
                    instr["range"] = True
                    instr["min"] = float(line.split()[1])
                    instr["max"] = float(line.split()[2])
                elif line.startswith("nodata"):
                    if line.split()[1].strip().lower() in ("y", "yes"):
                        instr["nodata"] = "y"
                    elif line.split()[1].strip().lower() in ("n", "no", "none"):
                        instr["nodata"] = "n"
                elif line.startswith("tickbar"):
                    if line.split()[1].strip().lower() in ("y", "yes"):
                        instr["tickbar"] = "y"
                    elif line.split()[1].strip().lower() in ("n", "no", "none"):
                        instr["tickbar"] = "n"
                elif line.startswith("discrete"):
                    if line.split()[1].strip().lower() in ("y", "yes"):
                        instr["discrete"] = "y"
                    elif line.split()[1].strip().lower() in ("n", "no", "none"):
                        instr["discrete"] = "n"

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        if "raster" in instr:
            instr["rasterDefault"] = False
            if "discrete" not in instr:
                rasterType = getRasterType(map=instr["raster"])
                instr["type"] = rasterType
                if rasterType == "CELL":
                    instr["discrete"] = "y"
                else:
                    instr["discrete"] = "n"

        else:
            instr["rasterDefault"] = True
        self.instruction.update(instr)
        # add 'rect' in the end

        return True

    def EstimateHeight(self, raster, discrete, fontsize, cols=None, height=None):
        """Estimate height to draw raster legend"""
        if discrete == "n":
            if height:
                height = height
            else:
                height = self.unitConv.convert(
                    value=fontsize * 10, fromUnit="point", toUnit="inch"
                )

        if discrete == "y":
            if cols:
                cols = cols
            else:
                cols = 1

            rinfo = grass.raster_info(raster)
            if rinfo["datatype"] in ("DCELL", "FCELL"):
                minim, maxim = rinfo["min"], rinfo["max"]
                rows = ceil(maxim / cols)
            else:
                cat = (
                    grass.read_command("r.category", map=raster, sep=":")
                    .strip()
                    .split("\n")
                )
                rows = ceil(float(len(cat)) / cols)

            height = self.unitConv.convert(
                value=1.5 * rows * fontsize, fromUnit="point", toUnit="inch"
            )

        return height

    def EstimateWidth(
        self, raster, discrete, fontsize, cols=None, width=None, paperInstr=None
    ):
        """Estimate size to draw raster legend"""

        if discrete == "n":
            rinfo = grass.raster_info(raster)
            minim, maxim = rinfo["min"], rinfo["max"]
            if width:
                width = width
            else:
                width = self.unitConv.convert(
                    value=fontsize * 2, fromUnit="point", toUnit="inch"
                )
            text = len(max(str(minim), str(maxim), key=len))
            textPart = self.unitConv.convert(
                value=text * fontsize / 2, fromUnit="point", toUnit="inch"
            )
            width += textPart

        elif discrete == "y":
            if cols:
                cols = cols
            else:
                cols = 1

            if width:
                width = width
            else:
                paperWidth = (
                    paperInstr["Width"] - paperInstr["Right"] - paperInstr["Left"]
                )
                width = (paperWidth / cols) * (cols - 1) + 1

        return width


class VectorLegend(InstructionObject):
    """Class representing colortable instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "vectorLegend"
        # default values
        self.defaultInstruction = dict(
            vLegend=False,
            unit="inch",
            where=(0, 0),
            defaultSize=True,
            width=0.4,
            cols=1,
            span=None,
            font="Helvetica",
            fontsize=10,
            border="none",
        )
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = "vlegend\n"
        instr += "    where %.3f %.3f\n" % (
            self.instruction["where"][0],
            self.instruction["where"][1],
        )
        instr += string.Template("    font $font\n    fontsize $fontsize\n").substitute(
            self.instruction
        )
        instr += string.Template("    width $width\n    cols $cols\n").substitute(
            self.instruction
        )
        if self.instruction["span"]:
            instr += string.Template("    span $span\n").substitute(self.instruction)
        instr += string.Template("    border $border\n").substitute(self.instruction)
        instr += "    end"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        instr = {}
        instr["vLegend"] = True
        for line in text:
            try:
                if line.startswith("where"):
                    instr["where"] = list(map(float, line.split()[1:3]))
                elif line.startswith("font "):
                    instr["font"] = line.split()[1]
                elif line.startswith("fontsize"):
                    instr["fontsize"] = float(line.split()[1])
                elif line.startswith("width"):
                    instr["width"] = float(line.split()[1])
                elif line.startswith("cols"):
                    instr["cols"] = int(line.split()[1])
                elif line.startswith("span"):
                    instr["span"] = float(line.split()[1])
                elif line.startswith("border"):
                    instr["border"] = line.split()[1]

            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        self.instruction.update(instr)

        return True

    def EstimateSize(self, vectorInstr, fontsize, width=None, cols=None):
        """Estimate size to draw vector legend"""
        if width:
            width = width
        else:
            width = fontsize / 24.0

        if cols:
            cols = cols
        else:
            cols = 1

        vectors = vectorInstr["list"]
        labels = [vector[4] for vector in vectors if vector[3] != 0]
        extent = (len(max(labels, key=len)) * fontsize / 2, fontsize)
        wExtent = self.unitConv.convert(
            value=extent[0], fromUnit="point", toUnit="inch"
        )
        hExtent = self.unitConv.convert(
            value=extent[1], fromUnit="point", toUnit="inch"
        )
        w = (width + wExtent) * cols
        h = len(labels) * hExtent / cols
        h *= 1.1
        return (w, h)


class Raster(InstructionObject):
    """Class representing raster instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "raster"
        # default values
        self.defaultInstruction = dict(isRaster=False, raster=None)
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = string.Template("raster $raster").substitute(self.instruction)
        return instr

    def Read(self, instruction, text):
        """Read instruction and save information"""
        instr = {}
        instr["isRaster"] = True
        try:
            map = text.split()[1]
        except IndexError:
            GError(_("Failed to read instruction %s") % instruction)
            return False
        try:
            info = grass.find_file(map, element="cell")
        except grass.ScriptError as e:
            GError(message=e.value)
            return False
        instr["raster"] = info["fullname"]

        self.instruction.update(instr)
        return True


class Vector(InstructionObject):
    """Class keeps vector layers"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "vector"
        # default values
        self.defaultInstruction = dict(list=None)  # [vmap, type, id, lpos, label]
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        return ""

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        instr = {}

        for line in text:
            if (
                line.startswith("vpoints")
                or line.startswith("vlines")
                or line.startswith("vareas")
            ):
                # subtype
                if line.startswith("vpoints"):
                    subType = "points"
                elif line.startswith("vlines"):
                    subType = "lines"
                elif line.startswith("vareas"):
                    subType = "areas"
                # name of vector map
                vmap = line.split()[1]
                try:
                    info = grass.find_file(vmap, element="vector")
                except grass.ScriptError as e:
                    GError(message=e.value)
                    return False
                vmap = info["fullname"]
                # id
                id = kwargs["id"]
                # lpos
                lpos = kwargs["vectorMapNumber"]
                # label
                label = "(".join(vmap.split("@")) + ")"
                break
        instr = [vmap, subType, id, lpos, label]
        if not self.instruction["list"]:
            self.instruction["list"] = []
        self.instruction["list"].append(instr)

        return True


class VProperties(InstructionObject):
    """Class represents instructions vareas, vlines, vpoints"""

    def __init__(self, id, subType, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "vProperties"
        self.subType = subType
        # default values
        if self.subType == "points":
            dd = dict(
                subType="points",
                name=None,
                type="point or centroid",
                connection=False,
                layer="1",
                masked="n",
                color="0:0:0",
                width=1,
                fcolor="255:0:0",
                rgbcolumn=None,
                symbol=os.path.join("basic", "x"),
                eps=None,
                size=5,
                sizecolumn=None,
                scale=None,
                rotation=False,
                rotate=0,
                rotatecolumn=None,
                label=None,
                lpos=None,
            )
        elif self.subType == "lines":
            dd = dict(
                subType="lines",
                name=None,
                type="line or boundary",
                connection=False,
                layer="1",
                masked="n",
                color="0:0:0",
                hwidth=1,
                hcolor="none",
                rgbcolumn=None,
                width=1,
                cwidth=None,
                style="solid",
                linecap="butt",
                label=None,
                lpos=None,
            )
        else:  # areas
            dd = dict(
                subType="areas",
                name=None,
                connection=False,
                layer="1",
                masked="n",
                color="0:0:0",
                width=1,
                fcolor="none",
                rgbcolumn=None,
                pat=None,
                pwidth=1,
                scale=1,
                label=None,
                lpos=None,
            )
        self.defaultInstruction = dd
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        dic = self.instruction
        vInstruction = string.Template("v$subType $name\n").substitute(dic)
        # data selection
        if self.subType in ("points", "lines"):
            vInstruction += string.Template("    type $type\n").substitute(dic)
        if dic["connection"]:
            vInstruction += string.Template("    layer $layer\n").substitute(dic)
            if "cats" in dic:
                vInstruction += string.Template("    cats $cats\n").substitute(dic)
            elif "where" in dic:
                vInstruction += string.Template("    where $where\n").substitute(dic)
        vInstruction += string.Template("    masked $masked\n").substitute(dic)
        # colors
        vInstruction += string.Template("    color $color\n").substitute(dic)
        if self.subType in ("points", "areas"):
            if dic["color"] != "none":
                vInstruction += string.Template("    width $width\n").substitute(dic)
            if dic["rgbcolumn"]:
                vInstruction += string.Template(
                    "    rgbcolumn $rgbcolumn\n"
                ).substitute(dic)
            vInstruction += string.Template("    fcolor $fcolor\n").substitute(dic)
        else:
            if dic["rgbcolumn"]:
                vInstruction += string.Template(
                    "    rgbcolumn $rgbcolumn\n"
                ).substitute(dic)
            elif dic["hcolor"] != "none":
                vInstruction += string.Template("    hwidth $hwidth\n").substitute(dic)
                vInstruction += string.Template("    hcolor $hcolor\n").substitute(dic)

        # size and style
        if self.subType == "points":
            if not dic["eps"]:
                vInstruction += string.Template("    symbol $symbol\n").substitute(dic)
            else:  # eps
                vInstruction += string.Template("    eps $eps\n").substitute(dic)
            if dic["size"]:
                vInstruction += string.Template("    size $size\n").substitute(dic)
            else:  # sizecolumn
                vInstruction += string.Template(
                    "    sizecolumn $sizecolumn\n"
                ).substitute(dic)
                vInstruction += string.Template("    scale $scale\n").substitute(dic)
            if dic["rotation"]:
                if dic["rotate"] is not None:
                    vInstruction += string.Template("    rotate $rotate\n").substitute(
                        dic
                    )
                else:
                    vInstruction += string.Template(
                        "    rotatecolumn $rotatecolumn\n"
                    ).substitute(dic)

        if self.subType == "areas":
            if dic["pat"] is not None:
                patternFile = dic["pat"].replace(os.getenv("GISBASE"), "$GISBASE")
                vInstruction += "    pat %s\n" % patternFile
                vInstruction += string.Template("    pwidth $pwidth\n").substitute(dic)
                vInstruction += string.Template("    scale $scale\n").substitute(dic)

        if self.subType == "lines":
            if dic["width"] is not None:
                vInstruction += string.Template("    width $width\n").substitute(dic)
            else:
                vInstruction += string.Template("    cwidth $cwidth\n").substitute(dic)
            vInstruction += string.Template("    style $style\n").substitute(dic)
            vInstruction += string.Template("    linecap $linecap\n").substitute(dic)
        # position and label in vlegend
        vInstruction += string.Template(
            "    label $label\n    lpos $lpos\n"
        ).substitute(dic)

        vInstruction += "    end"
        return vInstruction

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        instr = {}
        try:
            info = grass.find_file(name=text[0].split()[1], element="vector")
        except grass.ScriptError as e:
            GError(message=e.value)
            return False
        instr["name"] = info["fullname"]
        # connection
        instr["connection"] = True
        self.mapDBInfo = VectorDBInfo(instr["name"])
        self.layers = self.mapDBInfo.layers.keys()
        if not self.layers:
            instr["connection"] = False

        # points
        if text[0].startswith("vpoints"):
            for line in text[1:]:
                if line.startswith("type"):
                    tp = []
                    if line.find("point") != -1:
                        tp.append("point")
                    if line.find("centroid") != -1:
                        tp.append("centroid")
                    instr["type"] = " or ".join(tp)
                elif line.startswith("fcolor"):
                    instr["fcolor"] = line.split()[1]
                elif line.startswith("rgbcolumn"):
                    instr["rgbcolumn"] = line.split()[1]
                elif line.startswith("symbol"):
                    instr["symbol"] = line.split()[1]
                elif line.startswith("eps"):
                    instr["eps"] = line.split()[1]
                elif line.startswith("size "):
                    instr["size"] = line.split()[1]
                elif line.startswith("sizecolumn"):
                    instr["size"] = None
                    instr["sizecolumn"] = line.split()[1]
                elif line.startswith("scale "):
                    instr["scale"] = float(line.split()[1])
                elif line.startswith("rotate "):
                    instr["rotation"] = True
                    instr["rotate"] = line.split()[1]
                elif line.startswith("rotatecolumn"):
                    instr["rotatecolumn"] = line.split()[1]
                    instr["rotation"] = True
                    instr["rotate"] = None

        # lines
        elif text[0].startswith("vlines"):
            for line in text[1:]:
                if line.startswith("type"):
                    tp = []
                    if line.find("line") != -1:
                        tp.append("line")
                    if line.find("boundary") != -1:
                        tp.append("boundary")
                    instr["type"] = " or ".join(tp)
                elif line.startswith("hwidth"):
                    instr["hwidth"] = float(line.split()[1])
                elif line.startswith("hcolor"):
                    instr["hcolor"] = line.split()[1]
                elif line.startswith("rgbcolumn"):
                    instr["rgbcolumn"] = line.split()[1]
                elif line.startswith("cwidth"):
                    instr["cwidth"] = float(line.split()[1])
                    instr["width"] = None
                elif line.startswith("style"):
                    instr["style"] = line.split()[1]
                elif line.startswith("linecap"):
                    instr["linecap"] = line.split()[1]

        elif text[0].startswith("vareas"):
            for line in text[1:]:
                if line.startswith("fcolor"):
                    instr["fcolor"] = line.split()[1]
                elif line.startswith("pat"):
                    patternFile = line.split()[1]
                    instr["pat"] = patternFile.replace("$GISBASE", os.getenv("GISBASE"))
                elif line.startswith("pwidth"):
                    instr["pwidth"] = float(line.split()[1])
                elif line.startswith("scale"):
                    instr["scale"] = float(line.split()[1])

        # same properties for all
        for line in text[1:]:
            if line.startswith("lpos"):
                instr["lpos"] = int(line.split()[1])
            elif line.startswith("label"):
                instr["label"] = line.split(None, 1)[1]
            elif line.startswith("layer"):
                instr["layer"] = line.split()[1]
            elif line.startswith("masked"):
                if line.split()[1].lower() in ("y", "yes"):
                    instr["masked"] = "y"
                else:
                    instr["masked"] = "n"
            elif line.startswith("color"):
                instr["color"] = line.split()[1]
            elif line.startswith("rgbcolumn"):
                instr["rgbcolumn"] = line.split()[1]
            elif line.startswith("width"):
                instr["width"] = float(line.split()[1])

        if "label" not in instr:
            instr["label"] = "(".join(instr["name"].split("@")) + ")"
        if "lpos" not in instr:
            instr["lpos"] = kwargs["vectorMapNumber"]
        self.instruction.update(instr)

        return True


class Labels(InstructionObject):
    """Class representing labels instruction"""

    def __init__(self, id, env):
        InstructionObject.__init__(self, id=id, env=env)
        self.type = "labels"
        # default values
        self.defaultInstruction = dict(labels=[])
        # current values
        self.instruction = dict(self.defaultInstruction)

    def __str__(self):
        instr = ""
        for label in self.instruction["labels"]:
            instr += "labels %s\n" % label
            instr += "end\n"
        return instr

    def Read(self, instruction, text, **kwargs):
        """Read instruction and save information"""
        for line in text:
            try:
                if line.startswith("labels"):
                    labels = line.split(None, 1)[1]
                    self.instruction["labels"].append(labels)
            except (IndexError, ValueError):
                GError(_("Failed to read instruction %s") % instruction)
                return False

        return True
