<?php

class RISSucheKrits
{
    /** @var array */
    public $krits = [];

    /**
     * @param string|array $krits
     */
    public function __construct($krits = "")
    {
        if ($krits !== "") {
            if (is_array($krits)) $this->krits = $krits;
            else $this->krits = json_decode($krits);
        }
    }

    /**
     * @return string
     */
    public function getJson()
    {
        return json_encode($this->krits);
    }

    /**
     * @return int
     */
    public function getKritsCount()
    {
        return count($this->krits);
    }

    /**
     * @return bool
     */
    public function isGeoKrit()
    {
        foreach ($this->krits as $krit) if ($krit["typ"] == "geo") return true;
        return false;
    }

    /**
     * Distanz zwischen zwei Koordinaten in Metern
     *
     * @param float $lng1
     * @param float $lat1
     * @param float $lng2
     * @param float $lat2
     * @return float
     */
    private function calcDistance($lng1, $lat1, $lng2, $lat2)
    {
        $theta = $lng1 - $lng2;
        $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
        $dist = acos($dist);
        $dist = rad2deg($dist);
        $meters = $dist * 60 * 1.1515 * 1.609344 * 1000;
        return $meters;
    }

    /**
     * Bestimmt, ob der angegebene Ort den angegebenen geo-Krits entspricht. true, wenn keine geo-Krits vorhanden sind
     *
     * @param OrtGeo $ort
     * @return bool
     */
    public function filterGeo($ort)
    {
        if ($ort === null) return false;

        if (!$this->isGeoKrit()) return true;

        foreach ($this->krits as $krit) {
            if ($krit["typ"] == "geo" && $this->calcDistance($krit["lng"], $krit["lat"], $ort->lon, $ort->lat) <= $krit["radius"])
                return true;
        }
        return false;
    }

    /**
     * @return null|array
     */
    public function getGeoKrit()
    {
        foreach ($this->krits as $krit) if ($krit["typ"] == "geo") return $krit;
        return null;
    }

    /**
     * Baut eine absolute URL, die alle Krits als Parameter enhält
     *
     * @param string $path
     * @return string
     */
    public function getUrl($path = "index/suche")
    {
        $str = "";
        foreach ($this->krits as $krit) {
            if ($str != "") $str .= "&";
            $str .= "krit_typ[]=" . rawurlencode($krit["typ"]) . "&krit_val[]=";
            switch ($krit["typ"]) {
                case "betreff":
                    $str .= rawurlencode($krit["suchbegriff"]);
                    break;
                case "volltext":
                    $str .= rawurlencode($krit["suchbegriff"]);
                    break;
                case "antrag_typ":
                    $str .= rawurlencode($krit["suchbegriff"]);
                    break;
                case "antrag_wahlperiode":
                    $str .= rawurlencode($krit["suchbegriff"]);
                    break;
                case "ba":
                    $str .= IntVal($krit["ba_nr"]);
                    break;
                case "geo":
                    $str .= rawurlencode($krit["lng"] . "-" . $krit["lat"] . "-" . $krit["radius"]);
                    break;
                case "referat":
                    $str .= $krit["referat_id"];
                    break;
                case "antrag_nr":
                    $str .= rawurlencode($krit["suchbegriff"]);
                    break;
            }
        }
        return Yii::app()->createUrl($path) . "/?" . $str;
    }

    /**
     * @return array
     */
    public function getUrlArray()
    {
        $krits = $vals = [];
        foreach ($this->krits as $krit) {
            $krits[] = $krit["typ"];
            if ($krit["typ"] == "geo") $vals[] = $krit["lng"] . "-" . $krit["lat"] . "-" . $krit["radius"];
            elseif ($krit["typ"] == "ba") $vals[] = $krit["ba_nr"];
            elseif ($krit["typ"] == "referat") $vals[] = $krit["referat_id"];
            else $vals[] = $krit["suchbegriff"];
        }
        return ["krit_typ" => $krits, "krit_val" => $vals];
    }

    /**
     * Baut die URL zu einem RSS-Feed.
     *
     * @return string
     */
    public function getFeedUrl()
    {
        return $this->getBenachrichtigungKrits()->getUrl("index/feed");
    }

    /**
     * FIXME: Schließen sich volltext und antrag_nr gegenseitig aus?
     *
     * @param \Solarium\QueryType\Select\Query\Query $select
     */
    public function addKritsToSolr(&$select)
    {
        foreach ($this->krits as $krit) switch ($krit["typ"]) {
            case "betreff":
                $helper = $select->getHelper();
                $select->createFilterQuery("betreff")->setQuery("antrag_betreff:" . $helper->escapeTerm($krit["suchbegriff"]));
                break;
            case "antrag_typ":
                $select->createFilterQuery("antrag_typ")->setQuery("antrag_typ:" . $krit["suchbegriff"]);
                break;
            case "antrag_wahlperiode":
                $select->createFilterQuery("antrag_wahlperiode")->setQuery("antrag_wahlperiode:" . $krit["suchbegriff"]);
                break;
            case "volltext":
                /** @var Solarium\QueryType\Select\Query\Component\DisMax $dismax */
                $dismax = $select->getDisMax();
                $dismax->setQueryParser('edismax');
                $dismax->setQueryFields("text text_ocr");
                $select->setQuery($krit["suchbegriff"]);
                break;
            case "ba":
                $select->createFilterQuery("dokument_bas")->setQuery("dokument_bas:" . $krit["ba_nr"]);
                break;
            case "geo":
                $helper = $select->getHelper();
                $select->createFilterQuery("geo")->setQuery($helper->geofilt("geo", $krit["lat"], $krit["lng"], ($krit["radius"] / 1000)));
                break;
            case "referat":
                $helper = $select->getHelper();
                $select->createFilterQuery("referat")->setQuery("referat_id:" . $helper->escapeTerm($krit["referat_id"]));
                break;
            case "antrag_nr":
                /** @var Solarium\QueryType\Select\Query\Component\DisMax $dismax */
                $dismax = $select->getDisMax();
                $dismax->setQueryParser('edismax');
                $dismax->setQueryFields("antrag_nr");
                $select->setQuery("*" . $krit["suchbegriff"] . "*");
                break;
        }
    }

    /**
     * Gibt das erste Krit als Solr-Query-String zurück
     *
     * @param \Solarium\QueryType\Select\Query\Query $select
     * @return string
     */
    public function getSolrQueryStr($select)
    {
        foreach ($this->krits as $krit) switch ($krit["typ"]) {
            case "betreff":
                $helper = $select->getHelper();
                return "antrag_betreff:" . $helper->escapeTerm($krit["suchbegriff"]);
                break;
            case "antrag_typ":
                return "antrag_typ:" . $krit["suchbegriff"];
                break;
            case "antrag_wahlperiode":
                return "antrag_wahlperiode:" . $krit["suchbegriff"];
                break;
            case "ba":
                return "dokument_bas:" . $krit["ba_nr"];
                break;
            case "volltext":
                return $krit["suchbegriff"];
                break;
            case "geo":
                $helper = $select->getHelper();
                return $helper->geofilt("geo", $krit["lat"], $krit["lng"], ($krit["radius"] / 1000));
                break;
            case "referat":
                return "referat_id:" . $krit["referat_id"];
                break;
            case "antrag_nr":
                if (isset($krit["antrag_nr"])) {
                    return "*" . $krit["antrag_nr"] . "*";
                }
                if (isset($krit["suchbegriff"])) {
                    return "*" . $krit["suchbegriff"] . "*";
                }
                return "";
                break;
        }
        return "";
    }

    /**
     * Erstellt ein Kopie dieses Objekt ohne antrag_wahlperiode
     *
     * @return RISSucheKrits
     */
    public function getBenachrichtigungKrits()
    {
        $krits = [];
        foreach ($this->krits as $krit) if (!in_array($krit["typ"], ["antrag_wahlperiode"])) $krits[] = $krit;
        return new RISSucheKrits($krits);
    }


    /**
     * Textuelle Beschreibung der Suche im Nominalstil
     *
     * @param Dokument|null $dokument
     * @return string
     */
    public function getBeschreibungDerSuche($dokument = null)
    {
        if (count($this->krits) == 1) switch ($this->krits[0]["typ"]) {
            case "betreff":
                $such = $this->krits[0]["suchbegriff"];
                if ($such[0] == "\"" && $such[strlen($such) - 1] == "\"") return "Dokumente mit " . $this->krits[0]["suchbegriff"] . " im Betreff";
                return "Dokumente mit \"" . $such . "\" im Betreff";
            case "antrag_typ":
                return "Dokumente des Typs \"" . Dokument::TYPEN_ALLE[$this->krits[0]["suchbegriff"]] . "\"";
            case "volltext":
                $such = $this->krits[0]["suchbegriff"];
                if ($such[0] == "\"" && $such[strlen($such) - 1] == "\"") return "Volltextsuche nach " . $this->krits[0]["suchbegriff"];
                return "Volltextsuche nach \"" . $such . "\"";
            case "ba":
                /** @var Bezirksausschuss $ba */
                $ba = Bezirksausschuss::model()->findByAttributes(["ba_nr" => $this->krits[0]["ba_nr"]]);
                if ($ba->ba_nr == 0) {
                    $title = "Stadtrat";
                } else {
                    $title = "Bezirksausschuss " . $ba->ba_nr . " (" . $ba->name . ")";
                }

                if ($dokument) {
                    $gefundene_orte = array_filter($dokument->orte, function (AntragOrt $ort) use ($ba) {
                        return $ort->ort->ba_nr == $ba->ba_nr;
                    });
                    if (count($gefundene_orte) > 0) {
                        $namen = [];
                        foreach ($gefundene_orte as $gef_ort) $namen[] = $gef_ort->ort->ort;
                        $title .= ": " . implode(", ", $namen);
                    }
                }

                return $title;
            case "geo":
                $ort = OrtGeo::findClosest($this->krits[0]["lng"], $this->krits[0]["lat"]);
                $title = "Dokumente mit Ortsbezug (ungefähr: " . IntVal($this->krits[0]["radius"]) . "m um \"" . $ort->ort . "\")";

                // Diejenigen zum Dokument gehörenden Orte angeben, die sich im Suchradius befinden
                if ($dokument) {
                    /** @var OrtGeo[] $gefundene_orte */
                    $gefundene_orte = [];
                    foreach ($dokument->orte as $dok_ort_ort) {
                        $dok_ort = $dok_ort_ort->ort;
                        $distance = RISGeo::getDistance($dok_ort->lat, $dok_ort->lon, $this->krits[0]["lat"], $this->krits[0]["lng"]);
                        if (($distance * 1000) <= $this->krits[0]["radius"]) $gefundene_orte[] = $dok_ort;
                    }
                    if (count($gefundene_orte) > 0) {
                        $namen = [];
                        foreach ($gefundene_orte as $gef_ort) $namen[] = $gef_ort->ort;
                        $title .= ": " . implode(", ", $namen);
                    }
                }
                return $title;
            case "referat":
                /** @var Referat $ref */
                $ref = Referat::model()->findByPk($this->krits[0]["referat_id"]);
                return $ref->name;
                break;
            case "antrag_wahlperiode":
                return "Dokumente der Wahlperiode " . $this->krits[0]["suchbegriff"];
            case "antrag_nr":
                return "Antrag Nr. " . str_replace("*", " ", $this->krits[0]["suchbegriff"]);
        }
        if (count($this->krits) > 1) {
            $krits = [];
            foreach ($this->krits as $cr) switch ($cr["typ"]) {
                case "betreff":
                    $krits[] = "mit \"" . $cr["suchbegriff"] . "\" im Betreff";
                    break;
                case "antrag_typ":
                    $krits[] = "vom Typ \"" . Dokument::TYPEN_ALLE[$cr["suchbegriff"]] . "\"";
                    break;
                case "volltext":
                    $krits[] = "mit dem Suchausdruck \"" . $cr["suchbegriff"] . "\"";
                    break;
                case "ba":
                    /** @var Bezirksausschuss $ba */
                    $ba = Bezirksausschuss::model()->findByAttributes(["ba_nr" => $cr["ba_nr"]]);
                    $krits[] = "aus dem Bezirksausschuss " . $ba->ba_nr . ": " . $ba->name;
                    break;
                case "geo":
                    $ort = OrtGeo::findClosest($cr["lng"], $cr["lat"]);
                    $krits[] = "mit einem Ortsbezug (ungefähr: " . IntVal($cr["radius"]) . "m um \"" . $ort->ort . "\")";
                    break;
                case "antrag_nr":
                    $krits[] = "zum Antrag Nr. " . $cr["suchbegriff"];
                    break;
                case "referat":
                    /** @var Referat $ref */
                    $ref = Referat::model()->findByPk($cr["referat_id"]);
                    $krits[] = "im Zuständigkeitsbereich des " . $ref->name;
                    break;
                case "antrag_wahlperiode":
                    $krits[] = "aus der Wahlperiode " . CHtml::encode($cr["suchbegriff"]);
                    break;
                default:
                    $krits[] = json_encode($cr);
            }

            // Eine deutsche Reihung mit passenden Trennern bilden
            $text = "Dokumente ";
            for ($i = 0; $i < (count($krits) - 1); $i++) {
                $text .= $krits[$i];
                if ($i < (count($krits) - 2)) $text .= ", ";
            }
            $text .= " und " . $krits[count($krits) - 1];
            return $text;
        }
        return json_encode($this->krits);
    }

    /**
     * Fügt ein einzelnes Kriterium mit dem $name und dem Wert $value hinzu
     *
     * @param string $name
     * @param string $value
     * @return $this
     */
    public function addKrit($name, $value)
    {
        switch ($name) {
            case "betreff":
                $this->krits[] = [
                    "typ" => "betreff",
                    "suchbegriff" => $value
                ];
                break;
            case "volltext":
                $this->krits[] = [
                    "typ" => "volltext",
                    "suchbegriff" => $value
                ];
                break;
            case "antrag_typ":
                $this->krits[] = [
                    "typ" => "antrag_typ",
                    "suchbegriff" => $value
                ];
                break;
            case "antrag_wahlperiode":
                $this->krits[] = [
                    "typ" => "antrag_wahlperiode",
                    "suchbegriff" => $value
                ];
                break;
            case "ba":
                $this->krits[] = [
                    "typ" => "ba",
                    "ba_nr" => IntVal($value)
                ];
                break;
            case "geo":
                $options = explode("-", $value);
                $this->krits[] = [
                    "typ" => "geo",
                    "lng" => FloatVal($options[0]),
                    "lat" => FloatVal($options[1]),
                    "radius" => FloatVal($options[2])
                ];
                break;
            case "referat":
                $this->krits[] = [
                    "typ" => "referat",
                    "referat_id" => IntVal($value)
                ];
                break;
            case "antrag_nr":
                $value = preg_replace("/[^a-zA-Z0-9 \/-]/siu", "", $value);
                $value = preg_replace("/ +/siu", "*", $value);
                $this->krits[] = [
                    "typ" => "antrag_nr",
                    "suchbegriff" => $value,
                ];
                break;
            default:
                throw new Exception("Invalid Krit");
        }

        return $this;
    }

    /**
     * Überprüft, ob ein Kriterium mit dem entsprechenden Namen existiert
     *
     * @param $name
     * @return bool
     */
    public function hasKrit($name) {
        foreach ($this->krits as $krit) if ($krit["typ"] == $name) return true;
        return false;
    }

    /**
     * Erstellt ein Objekt auf Basis der URL-Parameter
     *
     * @param array $request
     * @return RISSucheKrits
     */
    public static function createFromUrl($request)
    {
        $x = new RISSucheKrits();
        if (!isset($request["krit_typ"]))
            return $x;

        for ($i = 0; $i < count($request["krit_typ"]); $i++)
            $x->addKrit($request["krit_typ"][$i], $request["krit_val"][$i]);
        return $x;
    }

    /**
     * Erstellt eine Kopie
     *
     * @return RISSucheKrits
     */
    public function cloneKrits()
    {
        return new RISSucheKrits($this->krits);
    }
}
