# filename: formats/trimble_are.py
# Copyright 2009 Luca Bianconi <luxetluc@yahoo.it>
# Copyright 2009 Stefano Costa <steko@iosa.it>
# Copyright 2009 Alessandro Bezzi <alessandro.bezzi@arc-team.com>

# This file is part of Total Open Station.

# Total Open Station 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.

# Total Open Station 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 Total Open Station.  If not, see
# <http://www.gnu.org/licenses/>.

import logging

from ...ptopo import Ptopo, PtopoConst
from . import UNKNOWN_POINT, Feature, Parser, Point


class TrimbleConst:
    INFO = "0"
    DATA = "1"
    STATION = "2"
    INSTRUMENT_HEIGHT = "3"
    CODE = "4"
    MATRICULE = "5"
    TARGET_HEIGHT = "6"
    HORIZONTAL_ANGLE = "7"
    VERTICAL_ANGLE = "8"
    SLOPE_DISTANCE = "9"
    VERTICAL_DISTANCE = "10"
    HORIZONTAL_DISTANCE = "11"
    REVERSED_HORIZONTAL_ANGLE = "17"
    REVERSED_VERTICAL_ANGLE = "18"
    REFERENCE_HORIZONTAL_ANGLE = "21"
    STANDARD_DEVIATION = "23"
    PPM = "30"
    PRISM_CONSTANT = "33"
    NORTH = "37"
    EAST = "38"
    ALTITUDE = "39"
    JOB_NUMBER = "50"
    DATE = "51"
    TIME = "52"
    OPERATOR = "53"
    PROJECT = "54"
    SN_INSTRUMENT = "55"
    TEMPERATURE = "56"
    UDS_EMPTY_LINE = "57"
    EARTH_RADIUS = "58"
    REFRACTION_FACTOR = "59"
    REFERENCE_NAME = "62"
    ATMOSPHERIC_PRESSURE = "74"
    EXCENTRICITY_NATURE = "101"
    ENTRICITY_VALUE = "102"
    REM1 = "111"
    REM2 = "112"
    REM3 = "113"
    REM4 = "114"
    NB = "115"


logger = logging.getLogger(__name__)


class FormatParser(Parser):
    def __init__(self, data, **options):
        self._use_all = False
        if "use_all" in options:
            self._use_all = options["use_all"]

        self._new_row_on = [
            TrimbleConst.STATION,
            TrimbleConst.PPM,
            TrimbleConst.INFO,
            TrimbleConst.STANDARD_DEVIATION,
            TrimbleConst.PRISM_CONSTANT,
            TrimbleConst.UDS_EMPTY_LINE,
            TrimbleConst.EARTH_RADIUS,
            TrimbleConst.REFRACTION_FACTOR,
            TrimbleConst.ATMOSPHERIC_PRESSURE,
            TrimbleConst.JOB_NUMBER,
            TrimbleConst.DATE,
            TrimbleConst.TIME,
            TrimbleConst.PROJECT,
            TrimbleConst.OPERATOR,
            TrimbleConst.REFERENCE_NAME,
        ]
        self._point_starts_on = "0"
        if "point_starts_on" in options and options["point_starts_on"] in [
            "0",
            "2",
            "4",
            "5",
            "62",
        ]:
            self._point_starts_on = options["point_starts_on"]
        self._new_row_on.append(self._point_starts_on)
        fields = data.splitlines()
        self.tdict = self.fields_to_dict_array(fields)

    def fields_to_dict_array(self, fields):
        tdict = []
        row = {}
        for field in fields:
            keyvalue = field.split("=")
            if keyvalue[0] in self._new_row_on:
                if len(row):
                    tdict.append(row)
                    row = {}
            row[keyvalue[0]] = keyvalue[1]
        if len(row):
            tdict.append(row)
        return tdict

    def is_point(self, row):
        is_point = False
        if (
            TrimbleConst.MATRICULE in row
            or TrimbleConst.REFERENCE_NAME in row
            or TrimbleConst.STATION in row
        ):
            if TrimbleConst.EAST in row and TrimbleConst.NORTH in row:
                is_point = True
        return is_point

    def extract_attributes(self, row):
        attributes = dict()
        point_type = None
        point_name = None
        if TrimbleConst.MATRICULE in row:
            point_type = "P"
            point_name = row[TrimbleConst.MATRICULE]
        elif TrimbleConst.REFERENCE_NAME in row:
            point_type = "R"
            point_name = row[TrimbleConst.REFERENCE_NAME]
        elif TrimbleConst.STATION in row:
            point_type = "S"
            point_name = row[TrimbleConst.STATION]
        attributes["point_type"] = point_type
        attributes["point_name"] = point_name
        return attributes

    def extract_point(self, row):
        p = UNKNOWN_POINT
        point_type = None
        point_name = None
        if TrimbleConst.MATRICULE in row:
            point_type = "P"
            point_name = row[TrimbleConst.MATRICULE]
        elif TrimbleConst.REFERENCE_NAME in row:
            point_type = "R"
            point_name = row[TrimbleConst.REFERENCE_NAME]
        elif TrimbleConst.STATION in row:
            point_type = "S"
            point_name = row[TrimbleConst.STATION]
        if point_type and point_name:
            if TrimbleConst.EAST in row and TrimbleConst.NORTH in row:
                x = row[TrimbleConst.EAST]
                y = row[TrimbleConst.NORTH]
                if TrimbleConst.ALTITUDE in row:
                    z = row[TrimbleConst.ALTITUDE]
                    p = Point(float(x), float(y), float(z))
                else:
                    p = Point(float(x), float(y), PtopoConst.UNKNOWN_Z)
        return p

    def get_point(self, row):
        f = None
        p = self.extract_point(row)
        a = self.extract_attributes(row)
        f = Feature(p, desc=a["point_type"], id=a["point_name"])
        return f

    def split_points(self):
        splitted_points = self.data.split("0=")
        return splitted_points

    @property
    def raw_line(self):
        """Extract all Trimble ARE data.

        This parser is based on the information in :ref:`if_trimble_are`

        Returns:
            A list of GeoJSON-like Feature object representing raw data
                i.e. polar coordinates and other informations.

        Raises:
            KeyError: An error occured during line read, this line could not be
                computed as the WI does not exist.
            KeyError: An error occured during computation, the data does not exist.

        Notes:
            Information needed are:
                - station : [0], 5, [4]
                - direct point : [0], 5, [4], 7, 8, 9
                - computed point : [0], 5, [4], 37, 38, 39
            Angles are considered as zenithal
        """

        points = []
        # ARE files handles key=value data block.
        station_id = 1
        station_name = None  # type: str

        idx = 0
        for row in self.tdict:
            idx = idx + 1
            point_type = point_name = angle = z_angle = dist = th = ih = None
            z_angle_type = dist_type = prism_constant = ppm = code = hz0 = None
            z_distance = None
            station_name = infos = reference_name = None

            try:
                pid = idx
                if TrimbleConst.MATRICULE in row:
                    point_name = row[TrimbleConst.MATRICULE]
                    point_type = "P"
                elif TrimbleConst.STATION in row:
                    point_name = row[TrimbleConst.STATION]
                    station_name = row[TrimbleConst.STATION]
                    point_type = "S"
                elif TrimbleConst.REFERENCE_NAME in row:
                    point_name = row[TrimbleConst.REFERENCE_NAME]
                    reference_name = row[TrimbleConst.REFERENCE_NAME]
                    point_type = "R"
                if TrimbleConst.INFO in row:
                    inf = row[TrimbleConst.INFO]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.STANDARD_DEVIATION in row:
                    inf = row[TrimbleConst.STANDARD_DEVIATION]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.PRISM_CONSTANT in row:
                    inf = row[TrimbleConst.PRISM_CONSTANT]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.UDS_EMPTY_LINE in row:
                    inf = row[TrimbleConst.UDS_EMPTY_LINE]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.EARTH_RADIUS in row:
                    inf = row[TrimbleConst.EARTH_RADIUS]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.REFRACTION_FACTOR in row:
                    inf = row[TrimbleConst.REFRACTION_FACTOR]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.ATMOSPHERIC_PRESSURE in row:
                    inf = row[TrimbleConst.ATMOSPHERIC_PRESSURE]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.JOB_NUMBER in row:
                    inf = row[TrimbleConst.JOB_NUMBER]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.PROJECT in row:
                    inf = row[TrimbleConst.PROJECT]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.DATE in row:
                    inf = row[TrimbleConst.DATE]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.TIME in row:
                    inf = row[TrimbleConst.TIME]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.OPERATOR in row:
                    inf = row[TrimbleConst.OPERATOR]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.SN_INSTRUMENT in row:
                    inf = row[TrimbleConst.SN_INSTRUMENT]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.TEMPERATURE in row:
                    inf = row[TrimbleConst.TEMPERATURE]
                    if infos:
                        infos = infos + f"/{inf}"
                    else:
                        infos = inf
                if TrimbleConst.INSTRUMENT_HEIGHT in row:
                    ih = row[TrimbleConst.INSTRUMENT_HEIGHT]
                if TrimbleConst.CODE in row:
                    code = row[TrimbleConst.CODE]
                if TrimbleConst.EAST in row and TrimbleConst.NORTH in row:
                    x = row[TrimbleConst.EAST]
                    y = row[TrimbleConst.NORTH]
                    if TrimbleConst.ALTITUDE in row:
                        z = row[TrimbleConst.ALTITUDE]
                if TrimbleConst.HORIZONTAL_ANGLE in row:
                    angle = row[TrimbleConst.HORIZONTAL_ANGLE]
                if TrimbleConst.VERTICAL_ANGLE in row:
                    z_angle = row[TrimbleConst.VERTICAL_ANGLE]
                    z_angle_type = "z"
                if TrimbleConst.VERTICAL_DISTANCE in row:
                    z_distance = row[TrimbleConst.VERTICAL_DISTANCE]
                if TrimbleConst.SLOPE_DISTANCE in row:
                    dist = row[TrimbleConst.SLOPE_DISTANCE]
                    dist_type = "s"
                if TrimbleConst.HORIZONTAL_DISTANCE in row:
                    dist = row[TrimbleConst.HORIZONTAL_DISTANCE]
                    dist_type = "h"
                if TrimbleConst.REVERSED_HORIZONTAL_ANGLE in row:
                    angle = float(row[TrimbleConst.REVERSED_HORIZONTAL_ANGLE])
                    angle = 200.0 + angle
                    angle = angle - 400.0 if angle > 400.0 else angle
                    angle = str(angle)
                if TrimbleConst.REVERSED_VERTICAL_ANGLE in row:
                    z_angle = float(row[TrimbleConst.REVERSED_VERTICAL_ANGLE])
                    z_angle = 400.0 - z_angle
                    z_angle = str(z_angle)
                if TrimbleConst.REFERENCE_HORIZONTAL_ANGLE in row:
                    hz0 = row[TrimbleConst.REFERENCE_HORIZONTAL_ANGLE]
                if TrimbleConst.TARGET_HEIGHT in row:
                    th = row[TrimbleConst.TARGET_HEIGHT]
            except Exception as e:
                print(str(e))
                try:
                    comments = row[TrimbleConst.INFO] + ":" + row[TrimbleConst.VALUE]
                except KeyError:
                    logger.info(
                        f"The line {pid} will not be computed as the code '{row.keys[0]}' is not known"
                    )
            else:
                # Handle polar data
                if self.is_point(row) or self._use_all:
                    p = self.extract_point(row)
                    f = Feature(
                        p,
                        desc=point_type,
                        id=pid,
                        point_name=point_name,
                        z_angle_type=z_angle_type,
                        dist_type=dist_type,
                        angle=angle,
                        z_angle=z_angle,
                        dist=dist,
                        z_distance=z_distance,
                        th=th,
                        ih=ih,
                        ppm=ppm,
                        prism_constant=prism_constant,
                        st_name=station_name,
                        ref_name=reference_name,
                        hz0=hz0,
                        code=code,
                        attrib=infos,
                    )
                    points.append(f)
        return points
