penguin/golifehk

@golifehk bot in Telegram

datasources/mtr/bus/query.go

raw ยท 5089 bytes

package bus

import (
    "errors"
    "fmt"
    "strings"
    "github.com/tgckpg/golifehk/utils"
)

type qTerm struct {
    Org string
    Value string
    ProblyRoute bool
}

type QueryObject struct {
    Route string
    BusStops *[]BusStop
    RouteStarts *map[string] *BusStop
    SearchTerms *[] *qTerm
}

func NotFound ( terms *[] *qTerm ) string {
    sb := strings.Builder{}
    sb.WriteString( "Not Found" )
    if 0 < len( *terms ) {
        sb.WriteString( ":" )
        for _, term := range *terms {
            sb.WriteString( " \"" )
            sb.WriteString( term.Org )
            sb.WriteString( "\"" )
        }
    }

    return sb.String()
}

func Query( lang string, message string ) *QueryResult {

    qr := QueryResult{ Lang: lang }

    qo, err := parse( message )
    if err != nil {
        qr.Error = err
        return &qr
    }

    qr.Query = qo

    if qo.Route != "" {

        if qo.BusStops == nil {
            return &qr
        }

        if len( *qo.BusStops ) == 0 {
            qr.Error = errors.New( NotFound( qo.SearchTerms ) )
            return &qr
        }

        schedules, err := getSchedule( lang, qo.Route )
        if err != nil {
            qr.Error = err
            return &qr
        }

        if len( schedules.BusStops ) == 0 {
            qr.Error = errors.New( "Schedules are empty...perhaps Out of Service Time?" )
            return &qr
        }

        matches := map[BusStop] *BusStopBuses{}
        for _, busStop := range *qo.BusStops {

            for _, busStopSch := range schedules.BusStops {
                if busStopSch.BusStopId == busStop.StationId {
                    matches[busStop] = &busStopSch
                    break
                }
            }

        }

        qr.Schedules = &matches
        return &qr
    } else {
        if len( *qo.BusStops ) == 0 {
            return &QueryResult{ Error: errors.New( NotFound( qo.SearchTerms ) ) }
        }

        return &qr
    }

}

func test( entry BusStop, val string ) bool {
    switch true {
    case strings.Contains( strings.ToUpper( (*entry.Name)["zh"] ), val ):
        fallthrough
    case strings.Contains( strings.ToUpper( (*entry.Name)["en"] ), val ):
        return true
    }
    return false
}

func parse( line string ) ( *QueryObject, error ) {
    busStops, err := getBusStops()

    if err != nil {
        return nil, err
    }

    var route string = ""
    matches := []BusStop{}

    // Sanitize and assume properties for each of the keywords
    terms := [] *qTerm{}
    for _, val := range strings.Split( line, " " ) {

        if val == "" {
            continue
        }

        term := qTerm{
            Org: val,
            Value: strings.ToUpper( strings.Trim( val, " " ) ),
            ProblyRoute: strings.ContainsAny( val, utils.ROUTE_CHARS ),
        }
        terms = append( terms, &term )
    }

    // Search for route name first, otherwise search in other props
    for _, entry := range *busStops {

        // Search for RouteId
        for _, term := range terms {

            if term.ProblyRoute && term.Value == entry.RouteId {
                if route != "" && route != term.Value {
                    return nil, fmt.Errorf( "Cannot %s & %s", route, term.Value )
                }
                matches = append( matches, entry )
                route = entry.RouteId
                break 
            }

            if test( entry, term.Value ) {
                matches = append( matches, entry )
                break
            }
        }
    }

    searchTerms := [] *qTerm{}
    matches_in := []BusStop{}

    for _, term := range terms {
        if term.ProblyRoute {
            continue
        }
        searchTerms = append( searchTerms, term )
    }

    if 0 < len( searchTerms ) {
        // If route found, filter out all other route
        // then search the terms within that route
        for _, entry := range matches {
            if route != "" && entry.RouteId != route {
                continue
            }

            for _, term := range searchTerms {
                if test( entry, term.Value ) {
                    matches_in = append( matches_in, entry )
                    break
                }
            }
        }

        matches = matches_in

        return &QueryObject{
            Route: route,
            BusStops: &matches,
            SearchTerms: &searchTerms,
        }, err

    } else if route != "" {
        // Route listing
        st := map[string] *BusStop{}

        for _, entry := range *busStops {
            if entry.RouteId != route {
                continue
            }

            if _, ok := st[ entry.Direction ]; ok {
                continue
            }

            st[ entry.Direction ] = &entry

            for st[ entry.Direction ].PrevStop() != nil {
                st[ entry.Direction ] = st[ entry.Direction ].PrevStop()
            }
        }

        return &QueryObject{ Route: route, RouteStarts: &st, BusStops: nil }, nil
    }

    return nil, fmt.Errorf( "Cannot parse: %s", line )
}