"""
Lake component I/O handlers for IWFM model files.

This module provides functions for reading and writing IWFM lake
component files including lake definitions, lake elements, rating
curves, and outflows.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import TextIO

import numpy as np

from pyiwfm.components.lake import (
    AppLake,
    Lake,
    LakeElement,
    LakeOutflow,
    LakeRating,
)
from pyiwfm.core.exceptions import FileFormatError
from pyiwfm.io.iwfm_reader import (
    COMMENT_CHARS,
)
from pyiwfm.io.iwfm_reader import (
    is_comment_line as _is_comment_line,
)
from pyiwfm.io.iwfm_reader import (
    next_data_or_empty as _next_data_or_empty,
)
from pyiwfm.io.iwfm_reader import (
    resolve_path as _resolve_path_f,
)
from pyiwfm.io.iwfm_reader import (
    strip_inline_comment as _strip_comment,
)


@dataclass
class LakeFileConfig:
    """
    Configuration for lake component files.

    Attributes:
        output_dir: Directory for output files
        lakes_file: Lake definitions file name
        lake_elements_file: Lake elements file name
        rating_curves_file: Lake rating curves file name
        outflows_file: Lake outflows file name
    """

    output_dir: Path
    lakes_file: str = "lakes.dat"
    lake_elements_file: str = "lake_elements.dat"
    rating_curves_file: str = "lake_rating_curves.dat"
    outflows_file: str = "lake_outflows.dat"

    def get_lakes_path(self) -> Path:
        return self.output_dir / self.lakes_file

    def get_lake_elements_path(self) -> Path:
        return self.output_dir / self.lake_elements_file

    def get_rating_curves_path(self) -> Path:
        return self.output_dir / self.rating_curves_file

    def get_outflows_path(self) -> Path:
        return self.output_dir / self.outflows_file


class LakeWriter:
    """
    Writer for IWFM lake component files.

    Writes all lake-related input files including definitions, elements,
    rating curves, and outflows.

    Example:
        >>> config = LakeFileConfig(output_dir=Path("./model"))
        >>> writer = LakeWriter(config)
        >>> files = writer.write(lake_component)
    """

    def __init__(self, config: LakeFileConfig) -> None:
        """
        Initialize the lake writer.

        Args:
            config: File configuration
        """
        self.config = config
        config.output_dir.mkdir(parents=True, exist_ok=True)

    def write(self, lakes: AppLake) -> dict[str, Path]:
        """
        Write all lake component files.

        Args:
            lakes: AppLake component to write

        Returns:
            Dictionary mapping file type to output path
        """
        files: dict[str, Path] = {}

        # Write lakes definition
        if lakes.lakes:
            files["lakes"] = self.write_lake_definitions(lakes)

        # Write lake elements
        if lakes.lake_elements:
            files["lake_elements"] = self.write_lake_elements(lakes)

        # Write rating curves for lakes that have them
        lakes_with_ratings = [lk for lk in lakes.lakes.values() if lk.rating is not None]
        if lakes_with_ratings:
            files["rating_curves"] = self.write_rating_curves(lakes)

        # Write outflows for lakes that have them
        lakes_with_outflows = [lk for lk in lakes.lakes.values() if lk.outflow is not None]
        if lakes_with_outflows:
            files["outflows"] = self.write_outflows(lakes)

        return files

    def write_lake_definitions(self, lakes: AppLake, header: str | None = None) -> Path:
        """
        Write lake definitions file.

        Args:
            lakes: AppLake component
            header: Optional header comment

        Returns:
            Path to written file
        """
        filepath = self.config.get_lakes_path()

        with open(filepath, "w") as f:
            # Write header
            if header:
                for line in header.strip().split("\n"):
                    f.write(f"C  {line}\n")
            else:
                f.write("C  Lake definitions file\n")
                f.write("C  Generated by pyiwfm\n")
                f.write("C\n")
                f.write("C  ID   MAX_ELEV     INIT_STORAGE  NAME\n")

            # Write lake count
            f.write(f"{len(lakes.lakes):<10}                              / NLAKES\n")

            # Write lakes in ID order
            for lake_id in sorted(lakes.lakes.keys()):
                lake = lakes.lakes[lake_id]
                max_elev = lake.max_elevation if lake.max_elevation != float("inf") else 9999.0
                f.write(
                    f"{lake.id:<6} {max_elev:>12.4f} {lake.initial_storage:>14.4f}  {lake.name}\n"
                )

        return filepath

    def write_lake_elements(self, lakes: AppLake, header: str | None = None) -> Path:
        """
        Write lake elements file.

        Args:
            lakes: AppLake component
            header: Optional header comment

        Returns:
            Path to written file
        """
        filepath = self.config.get_lake_elements_path()

        with open(filepath, "w") as f:
            # Write header
            if header:
                for line in header.strip().split("\n"):
                    f.write(f"C  {line}\n")
            else:
                f.write("C  Lake elements file\n")
                f.write("C  Generated by pyiwfm\n")
                f.write("C\n")
                f.write("C  ELEM_ID  LAKE_ID  FRACTION\n")

            # Write element count
            f.write(
                f"{len(lakes.lake_elements):<10}                              / NLAKE_ELEMENTS\n"
            )

            # Write lake elements
            for elem in lakes.lake_elements:
                f.write(f"{elem.element_id:>7} {elem.lake_id:>7} {elem.fraction:>10.6f}\n")

        return filepath

    def write_rating_curves(self, lakes: AppLake, header: str | None = None) -> Path:
        """
        Write lake rating curves file.

        Args:
            lakes: AppLake component
            header: Optional header comment

        Returns:
            Path to written file
        """
        filepath = self.config.get_rating_curves_path()

        with open(filepath, "w") as f:
            # Write header
            if header:
                for line in header.strip().split("\n"):
                    f.write(f"C  {line}\n")
            else:
                f.write("C  Lake rating curves file\n")
                f.write("C  Generated by pyiwfm\n")
                f.write("C\n")

            # Count lakes with rating curves
            lakes_with_ratings = [lk for lk in lakes.lakes.values() if lk.rating is not None]
            f.write(
                f"{len(lakes_with_ratings):<10}                              / N_RATING_CURVES\n"
            )

            # Write each rating curve
            for lake in lakes_with_ratings:
                rating = lake.rating
                assert rating is not None
                f.write("C\n")
                f.write(f"C  Rating curve for lake {lake.id}: {lake.name}\n")
                f.write(f"{lake.id:<6} {len(rating.elevations):>5}  / LAKE_ID, N_POINTS\n")
                f.write("C  ELEVATION       AREA          VOLUME\n")

                for i in range(len(rating.elevations)):
                    f.write(
                        f"{rating.elevations[i]:>12.4f} {rating.areas[i]:>14.4f} "
                        f"{rating.volumes[i]:>14.4f}\n"
                    )

        return filepath

    def write_outflows(self, lakes: AppLake, header: str | None = None) -> Path:
        """
        Write lake outflows file.

        Args:
            lakes: AppLake component
            header: Optional header comment

        Returns:
            Path to written file
        """
        filepath = self.config.get_outflows_path()

        with open(filepath, "w") as f:
            # Write header
            if header:
                for line in header.strip().split("\n"):
                    f.write(f"C  {line}\n")
            else:
                f.write("C  Lake outflows file\n")
                f.write("C  Generated by pyiwfm\n")
                f.write("C\n")
                f.write("C  LAKE_ID  DEST_TYPE    DEST_ID    MAX_RATE\n")

            # Count lakes with outflows
            lakes_with_outflows = [lk for lk in lakes.lakes.values() if lk.outflow is not None]
            f.write(f"{len(lakes_with_outflows):<10}                              / N_OUTFLOWS\n")

            # Write outflows
            for lake in lakes_with_outflows:
                outflow = lake.outflow
                assert outflow is not None
                max_rate = outflow.max_rate if outflow.max_rate != float("inf") else 9999999.0
                f.write(
                    f"{lake.id:>7} {outflow.destination_type:<12} {outflow.destination_id:>7} "
                    f"{max_rate:>14.4f}\n"
                )

        return filepath


class LakeReader:
    """
    Reader for IWFM lake component files.
    """

    def read_lake_definitions(self, filepath: Path | str) -> dict[int, Lake]:
        """
        Read lake definitions from file.

        Args:
            filepath: Path to lakes file

        Returns:
            Dictionary mapping lake ID to Lake object
        """
        filepath = Path(filepath)
        lakes: dict[int, Lake] = {}

        with open(filepath) as f:
            line_num = 0
            n_lakes = None

            # Find NLAKES (skip version header lines starting with #)
            for line in f:
                line_num += 1
                if _is_comment_line(line):
                    continue
                stripped = line.strip()
                if stripped.startswith("#"):
                    # Version header (e.g., "#4.0") — skip
                    continue

                value, _ = _strip_comment(line)
                try:
                    n_lakes = int(value)
                except ValueError as e:
                    raise FileFormatError(
                        f"Invalid NLAKES value: '{value}'", line_number=line_num
                    ) from e
                break

            if n_lakes is None:
                raise FileFormatError("Could not find NLAKES in file")

            # Read lake data — detect preprocessor vs simulation format
            lakes_read = 0
            for line in f:
                line_num += 1
                if _is_comment_line(line):
                    continue

                parts = line.split()
                if len(parts) < 3:
                    continue

                try:
                    lake_id = int(parts[0])

                    # Preprocessor format: ID TYPDST DST NELAKE IELAKE(first)
                    # Simulation format:   ID MAX_ELEV INITIAL_STORAGE [NAME]
                    if len(parts) >= 5 and lakes_read == 0:
                        # Check if this is preprocessor format by testing
                        # if columns 1-3 are all integers
                        try:
                            typdst = int(parts[1])
                            dst = int(parts[2])
                            nelake = int(parts[3])
                            is_preproc = True
                        except ValueError:
                            is_preproc = False
                    else:
                        is_preproc = lakes_read == 0 and False

                    if is_preproc:
                        # Preprocessor Lake.dat format
                        first_elem = int(parts[4]) if len(parts) > 4 else 0
                        elem_ids = [first_elem] if first_elem > 0 else []

                        # Read remaining NELAKE-1 continuation element rows
                        for _ce in range(nelake - 1):
                            for cont_line in f:
                                line_num += 1
                                if _is_comment_line(cont_line):
                                    continue
                                cont_parts = cont_line.split()
                                if cont_parts:
                                    elem_ids.append(int(cont_parts[0]))
                                break

                        lake = Lake(
                            id=lake_id,
                            elements=elem_ids,
                        )
                        if typdst == 1:
                            lake.outflow = LakeOutflow(
                                lake_id=lake_id,
                                destination_type="stream",
                                destination_id=dst,
                            )
                        lakes[lake_id] = lake
                        lakes_read += 1

                        # Read remaining lakes in preprocessor format
                        for _lk in range(n_lakes - 1):
                            for lline in f:
                                line_num += 1
                                if _is_comment_line(lline):
                                    continue
                                lp = lline.split()
                                if len(lp) < 5:
                                    break
                                lid = int(lp[0])
                                lt = int(lp[1])
                                ld = int(lp[2])
                                nl = int(lp[3])
                                fe = int(lp[4]) if len(lp) > 4 else 0
                                eids = [fe] if fe > 0 else []
                                for _ce2 in range(nl - 1):
                                    for cl2 in f:
                                        line_num += 1
                                        if _is_comment_line(cl2):
                                            continue
                                        cp2 = cl2.split()
                                        if cp2:
                                            eids.append(int(cp2[0]))
                                        break
                                lk = Lake(id=lid, elements=eids)
                                if lt == 1:
                                    lk.outflow = LakeOutflow(
                                        lake_id=lid,
                                        destination_type="stream",
                                        destination_id=ld,
                                    )
                                lakes[lid] = lk
                                lakes_read += 1
                                break
                        break  # Done reading all lakes in preprocessor format
                    else:
                        # Simulation format: ID MAX_ELEV INITIAL_STORAGE [NAME]
                        max_elev = float(parts[1])
                        initial_storage = float(parts[2])
                        name = " ".join(parts[3:]) if len(parts) > 3 else ""

                        lakes[lake_id] = Lake(
                            id=lake_id,
                            max_elevation=max_elev if max_elev < 9990 else float("inf"),
                            initial_storage=initial_storage,
                            name=name,
                        )
                        lakes_read += 1

                except ValueError as e:
                    raise FileFormatError(
                        f"Invalid lake data: '{line.strip()}'", line_number=line_num
                    ) from e

        return lakes

    def read_lake_elements(self, filepath: Path | str) -> list[LakeElement]:
        """
        Read lake elements from file.

        Args:
            filepath: Path to lake elements file

        Returns:
            List of LakeElement objects
        """
        filepath = Path(filepath)
        elements: list[LakeElement] = []

        with open(filepath) as f:
            line_num = 0
            n_elements = None

            # Find NLAKE_ELEMENTS
            for line in f:
                line_num += 1
                if _is_comment_line(line):
                    continue

                value, _ = _strip_comment(line)
                try:
                    n_elements = int(value)
                except ValueError as e:
                    raise FileFormatError(
                        f"Invalid NLAKE_ELEMENTS value: '{value}'",
                        line_number=line_num,
                    ) from e
                break

            if n_elements is None:
                raise FileFormatError("Could not find NLAKE_ELEMENTS in file")

            # Read element data
            for line in f:
                line_num += 1
                if _is_comment_line(line):
                    continue

                parts = line.split()
                if len(parts) < 2:
                    continue

                try:
                    elem_id = int(parts[0])
                    lake_id = int(parts[1])
                    fraction = float(parts[2]) if len(parts) > 2 else 1.0

                    elements.append(
                        LakeElement(
                            element_id=elem_id,
                            lake_id=lake_id,
                            fraction=fraction,
                        )
                    )

                except ValueError as e:
                    raise FileFormatError(
                        f"Invalid lake element data: '{line.strip()}'",
                        line_number=line_num,
                    ) from e

        return elements

    def read_rating_curves(self, filepath: Path | str) -> dict[int, LakeRating]:
        """
        Read lake rating curves from file.

        Args:
            filepath: Path to rating curves file

        Returns:
            Dictionary mapping lake ID to LakeRating object
        """
        filepath = Path(filepath)
        ratings: dict[int, LakeRating] = {}

        with open(filepath) as f:
            line_num = 0
            n_curves = None

            # Find N_RATING_CURVES
            for line in f:
                line_num += 1
                if _is_comment_line(line):
                    continue

                value, _ = _strip_comment(line)
                try:
                    n_curves = int(value)
                except ValueError as e:
                    raise FileFormatError(
                        f"Invalid N_RATING_CURVES value: '{value}'",
                        line_number=line_num,
                    ) from e
                break

            if n_curves is None:
                raise FileFormatError("Could not find N_RATING_CURVES in file")

            # Read each rating curve
            curves_read = 0
            while curves_read < n_curves:
                # Find lake ID and n_points
                for line in f:
                    line_num += 1
                    if _is_comment_line(line):
                        continue

                    parts = line.split()
                    if len(parts) >= 2:
                        lake_id = int(parts[0])
                        n_points = int(parts[1])
                        break

                # Read rating data
                elevations = []
                areas = []
                volumes = []

                points_read = 0
                for line in f:
                    line_num += 1
                    if _is_comment_line(line):
                        continue

                    parts = line.split()
                    if len(parts) >= 3:
                        elevations.append(float(parts[0]))
                        areas.append(float(parts[1]))
                        volumes.append(float(parts[2]))
                        points_read += 1

                        if points_read >= n_points:
                            break

                ratings[lake_id] = LakeRating(
                    elevations=np.array(elevations),
                    areas=np.array(areas),
                    volumes=np.array(volumes),
                )

                curves_read += 1

        return ratings


# =============================================================================
# Lake Component Main File Reader (hierarchical dispatcher file)
# =============================================================================


@dataclass
class LakeParamSpec:
    """
    Per-lake parameters from the simulation lake main file.

    Attributes:
        lake_id: Lake ID
        conductance_coeff: Dimensionless conductance coefficient (CLAKE)
        depth_denom: Lake depth denominator (DLAKE)
        max_elev_col: Column index in max elevation file
        et_col: Column index in ET time series file
        precip_col: Column index in precipitation file
        name: Lake identification name
    """

    lake_id: int = 0
    conductance_coeff: float = 0.0
    depth_denom: float = 1.0
    max_elev_col: int = 0
    et_col: int = 0
    precip_col: int = 0
    name: str = ""


@dataclass
class OutflowRatingPoint:
    """A single point in a lake outflow rating table (v5.0).

    Attributes:
        elevation: Water surface elevation
        outflow: Outflow rate at this elevation
    """

    elevation: float = 0.0
    outflow: float = 0.0


@dataclass
class LakeOutflowRating:
    """Outflow rating table for a lake (v5.0 only).

    Attributes:
        lake_id: Lake ID
        points: List of (elevation, outflow) rating points
    """

    lake_id: int = 0
    points: list[OutflowRatingPoint] = field(default_factory=list)


@dataclass
class LakeMainFileConfig:
    """
    Configuration parsed from Lake component main simulation file.

    The lake main file configures lake hydrological parameters and
    data source file paths for the simulation.

    Attributes:
        version: File format version (e.g., "4.0" or "5.0")
        max_elev_file: Path to max lake elevation time series file
        budget_output_file: Path to lake budget output file
        final_elev_file: Path to end-of-simulation elevation output file
        conductance_factor: Conductance factor K (FactK)
        conductance_time_unit: Time unit for conductance
        depth_factor: Lake depth parameter L (FactL)
        lake_params: Per-lake parameter specifications
        elev_factor: Elevation conversion factor (v5.0 only)
        outflow_factor: Outflow rate conversion factor (v5.0 only)
        outflow_time_unit: Time unit for outflow rating tables (v5.0 only)
        outflow_ratings: Outflow rating tables per lake (v5.0 only)
    """

    version: str = ""
    max_elev_file: Path | None = None
    budget_output_file: Path | None = None
    final_elev_file: Path | None = None
    conductance_factor: float = 1.0
    conductance_time_unit: str = ""
    depth_factor: float = 1.0
    lake_params: list[LakeParamSpec] = field(default_factory=list)

    # v5.0 outflow rating tables
    elev_factor: float = 1.0
    outflow_factor: float = 1.0
    outflow_time_unit: str = ""
    outflow_ratings: list[LakeOutflowRating] = field(default_factory=list)


class LakeMainFileReader:
    """
    Reader for IWFM lake component main simulation file.

    The Lake main file is a hierarchical dispatcher that contains:
    1. Version header (e.g., #4.0)
    2. Paths to sub-files (max elevation TS, budget output, final elevation)
    3. Conductance and depth parameters
    4. Per-lake parameter lines
    5. (v5.0) Outflow rating tables

    Supports both version 4.0 and 5.0 formats.
    """

    def __init__(self) -> None:
        self._line_num = 0

    def read(self, filepath: Path | str, base_dir: Path | None = None) -> LakeMainFileConfig:
        """
        Parse Lake main file.

        Args:
            filepath: Path to the Lake component main file
            base_dir: Base directory for resolving relative paths.
                     If None, uses the parent directory of filepath.

        Returns:
            LakeMainFileConfig with parsed values
        """
        filepath = Path(filepath)
        if base_dir is None:
            base_dir = filepath.parent

        config = LakeMainFileConfig()
        self._line_num = 0

        with open(filepath) as f:
            # Read version header
            config.version = self._read_version(f)
            is_v50 = config.version.startswith("5")

            # Max lake elevation file path
            max_elev_path = _next_data_or_empty(f)
            if max_elev_path:
                config.max_elev_file = _resolve_path_f(base_dir, max_elev_path)

            # Budget output file path
            budget_path = _next_data_or_empty(f)
            if budget_path:
                config.budget_output_file = _resolve_path_f(base_dir, budget_path)

            # Final elevation output file path (optional)
            final_path = _next_data_or_empty(f)
            if final_path:
                config.final_elev_file = _resolve_path_f(base_dir, final_path)

            # Conductance factor K
            factk_str = _next_data_or_empty(f)
            if factk_str:
                config.conductance_factor = float(factk_str)

            # Conductance time unit
            config.conductance_time_unit = _next_data_or_empty(f)

            # Depth factor L
            factl_str = _next_data_or_empty(f)
            if factl_str:
                config.depth_factor = float(factl_str)

            # Per-lake parameter lines
            # We read lines until we encounter something that doesn't parse
            # as a lake parameter line. Each line has:
            # CLAKE_ID  CLAKE  DLAKE  MaxElev_Col  ET_Col  Precip_Col  [Name]
            while True:
                data = _next_data_or_empty(f)
                if not data:
                    break

                parts = data.split()
                if len(parts) < 6:
                    # Not a lake param line - might be v5.0 elevation factor
                    # or initial conditions section
                    if is_v50 and len(parts) == 1:
                        try:
                            config.elev_factor = float(parts[0])
                            break
                        except ValueError:
                            break
                    break

                try:
                    param = LakeParamSpec(
                        lake_id=int(float(parts[0])),
                        conductance_coeff=float(parts[1]),
                        depth_denom=float(parts[2]),
                        max_elev_col=int(float(parts[3])),
                        et_col=int(float(parts[4])),
                        precip_col=int(float(parts[5])),
                        name=" ".join(parts[6:]) if len(parts) > 6 else "",
                    )
                    config.lake_params.append(param)
                except (ValueError, IndexError):
                    break

            # v5.0: Read outflow rating tables
            if is_v50 and config.lake_params:
                # Outflow rate factor (already read elev_factor above)
                factq_str = _next_data_or_empty(f)
                if factq_str:
                    config.outflow_factor = float(factq_str)

                # Outflow time unit
                config.outflow_time_unit = _next_data_or_empty(f)

                # Read outflow rating table for each lake
                for _ in range(len(config.lake_params)):
                    rating = self._read_outflow_rating(f, config.elev_factor, config.outflow_factor)
                    if rating:
                        config.outflow_ratings.append(rating)

        return config

    def _read_outflow_rating(
        self,
        f: TextIO,
        elev_factor: float,
        outflow_factor: float,
    ) -> LakeOutflowRating | None:
        """Read a single outflow rating table for a lake (v5.0).

        Format:
            Lake_ID  NPoints  Elev_1  Outflow_1
            Elev_2  Outflow_2
            ...
        """
        header = _next_data_or_empty(f)
        if not header:
            return None

        parts = header.split()
        if len(parts) < 4:
            return None

        rating = LakeOutflowRating(lake_id=int(float(parts[0])))
        n_points = int(float(parts[1]))

        # First point on header line
        rating.points.append(
            OutflowRatingPoint(
                elevation=float(parts[2]) * elev_factor,
                outflow=float(parts[3]) * outflow_factor,
            )
        )

        # Remaining points
        for _ in range(n_points - 1):
            line = _next_data_or_empty(f)
            if not line:
                break
            lp = line.split()
            if len(lp) >= 2:
                rating.points.append(
                    OutflowRatingPoint(
                        elevation=float(lp[0]) * elev_factor,
                        outflow=float(lp[1]) * outflow_factor,
                    )
                )

        return rating

    def _read_version(self, f: TextIO) -> str:
        """Read the version header from the file."""
        for line in f:
            self._line_num += 1
            stripped = line.strip()
            if not stripped:
                continue
            if stripped.startswith("#"):
                return stripped[1:].strip()
            if line[0] in COMMENT_CHARS:
                continue
            break
        return ""


# Convenience functions


def read_lake_main_file(filepath: Path | str, base_dir: Path | None = None) -> LakeMainFileConfig:
    """
    Read IWFM lake component main simulation file.

    Args:
        filepath: Path to the Lake component main file
        base_dir: Base directory for resolving relative paths.

    Returns:
        LakeMainFileConfig with parsed values
    """
    reader = LakeMainFileReader()
    return reader.read(filepath, base_dir)


def write_lakes(
    lakes: AppLake,
    output_dir: Path | str,
    config: LakeFileConfig | None = None,
) -> dict[str, Path]:
    """
    Write lake component to files.

    Args:
        lakes: AppLake component to write
        output_dir: Output directory
        config: Optional file configuration

    Returns:
        Dictionary mapping file type to output path
    """
    output_dir = Path(output_dir)

    if config is None:
        config = LakeFileConfig(output_dir=output_dir)
    else:
        config.output_dir = output_dir

    writer = LakeWriter(config)
    return writer.write(lakes)


def read_lake_definitions(filepath: Path | str) -> dict[int, Lake]:
    """
    Read lake definitions from file.

    Args:
        filepath: Path to lakes file

    Returns:
        Dictionary mapping lake ID to Lake object
    """
    reader = LakeReader()
    return reader.read_lake_definitions(filepath)


def read_lake_elements(filepath: Path | str) -> list[LakeElement]:
    """
    Read lake elements from file.

    Args:
        filepath: Path to lake elements file

    Returns:
        List of LakeElement objects
    """
    reader = LakeReader()
    return reader.read_lake_elements(filepath)
