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 ) }