require "singleton"
require "json"

module SpTrans

class ApiFacade 
  include Singleton

  def initialize
    @api_url = "api.olhovivo.sptrans.com.br/v0/"
  end

  def seed_routes 
    requests = []
    ("A".."Z").each do |letter|
      requests << "Linha/Buscar?termosBusca=#{letter}"
    end
    run(requests, method(:save_routes))
  end

  def seed_bus_stops 
    requests = []
    Route.all.each do |route| 
      rt_id = route.provider_route_id
      requests << "Parada/BuscarParadasPorLinha?codigoLinha=#{rt_id}"
    end
    run(requests, method(:save_bus_stops))
  end

  def locate_buses
    requests = []
    # Routes that serve stop id:1811 name:"INSS B/C"
    routes = BusStop.find_by(name: "INSS B/C").routes.map { |r| r.id }

    Route.where(id: routes).each do |route|
      requests << "Posicao?codigoLinha=#{route.provider_route_id}"
    end
    
    run(requests, method(:save_buses_locations))
  end

  private
    def authenticate(try=1)
      easy = Ethon::Easy.new
      easy.http_request(
        "#{@api_url}Login/Autenticar?token=#{ENV["SP_TRANS_API_TOKEN"]}",
        :post, { cookiefile: ".cookiejar", cookiejar: ".cookiejar" }
      )
      easy.perform

      if easy.response_body != "true"
        easy.handle.free
        if try > 5
          raise "Error while authenticating to Sp Trans Api."
        else
          puts "Coulnd't authenticate. Retrying in #{try*5} seconds..."
          sleep try*5
          authenticate(try+1)
        end
      else
        easy.handle.free
      end
    end

    def run(requests, save_callback)
      authenticate

      loop do
        responses = {}
        hydra = Typhoeus::Hydra.new(max_concurrency: 1)

        requests.pop(10).each do |args|
          request = Typhoeus::Request.new(
            "#{@api_url}#{args}", 
            method: :get,
            cookiefile: ".cookiejar", 
            cookiejar: ".cookiejar"
          )
          
          request.on_complete do |response|
            #puts "#{response.effective_url}"
            api_resp = JSON.parse(response.body)
            if api_resp.is_a?(Hash) and api_resp.has_key? "Message"
              puts api_resp["Message"]

              # Volta requisição que falhou à lista de requisições
              # e realiza autenticação novamente
              requests << args
              authenticate
              next
            end
            responses[response.effective_url] = api_resp
          end
          hydra.queue(request)
        end
        hydra.run

        ActiveRecord::Base.transaction do
          save_callback.call(responses)
        end

        break if requests.empty?
      end
    end

    def save_routes(responses)
      ids = []
      routes = []
      responses.values.each do |resp|
        resp.each do |rt|
          unless ids.include? rt["CodigoLinha"]
            unless Route.exists?(provider_route_id: rt["CodigoLinha"])
              ids << rt["CodigoLinha"]
              routes << Route.new(
                provider_route_id: rt["CodigoLinha"],
                loop_route: rt["Circular"],
                signboard: rt["Letreiro"],
                destination: rt["Sentido"],
                mode: rt["Tipo"],
                sign_main_to_sec: rt["DenominacaoTPTS"],
                sign_sec_to_main: rt["DenominacaoTSTP"],
                details: rt["Informacoes"]
              )
            end
          end
        end
      end
      
      Route.import routes, validate: false
    end

    def save_bus_stops(responses) 
      ids = []
      stops = []
      responses.each do |url, resp|
        route_id = String.new url
        route_id.slice! "HTTP://#{@api_url}Parada/BuscarParadasPorLinha?codigoLinha="
        resp.each do |bs|
          unless ids.include? bs["CodigoParada"]
            unless BusStop.exists?(provider_bus_stop_id: bs["CodigoParada"])
              ids << bs["CodigoParada"]
              stops << BusStop.new(
                provider_bus_stop_id: bs["CodigoParada"],
                name: bs["Nome"],
                address: bs["Endereco"],
                latitude: bs["Latitude"],
                longitude: bs["Longitude"]
              )
            end
          end
        end
      end
      
      BusStop.import stops, validate: false

      route_stops = []
      responses.each do |url, resp|
        route_id = String.new url
        route_id.slice! "HTTP://#{@api_url}Parada/BuscarParadasPorLinha?codigoLinha="
        route = Route.find_by(provider_route_id: route_id)
        resp.each do |bs|
          stop = BusStop.find_by(provider_bus_stop_id: bs["CodigoParada"])
           # Ordem provisória, pois o JSON é FUCKING DESORDENADO!
          route_stops << RouteStop.new(
            route: route,
            bus_stop: stop,
            index: nil
          )
        end
      end
      RouteStop.import route_stops, validate: false
    end

    def save_buses_locations(responses)
      #puts "Saving...."
      locations = []
      responses.each do |url, resp|
        #puts url
        route_id = String.new url
        route_id.slice! "HTTP://#{@api_url}Posicao?codigoLinha="
        route = Route.find_by(provider_route_id: route_id)

        if route.nil?
          raise "Invalid route exception!"
          next
        end

        resp["vs"].each do |vehicles|

          time = resp['hr']
          timezone = Time.zone.formatted_offset
          registration_time = DateTime.strptime(
            DateTime.now.strftime("%d/%m/%Y #{time} #{timezone}"), 
            "%d/%m/%Y %H:%M %z")

          # 
          unless BusLocation.exists?(
            bus_id: vehicles["p"],  
            latitude: vehicles["py"],
            longitude: vehicles["px"],
            registration_time: registration_time)
          
            locations << BusLocation.new(
              bus_id: vehicles["p"],
              a: vehicles["a"],
              registration_time: registration_time,
              latitude: vehicles["py"],
              longitude: vehicles["px"],
              route_id: route.id,
            )  
          end
        end
      end
      BusLocation.import locations, validate: false
    end
end

end
