
// WeatherApp.java
// Andrew Davison, ad@coe.psu.ac.th, Jan 2023

/* libGDX applet demonstrating the use of HTTP to get
   weather data from https://www.weatherapi.com/ in
   JSON format, and the use of JsonBeans 
   (https://github.com/EsotericSoftware/jsonbeans/) to
   use it as Java objects.

   Usage
   -----
   Test as a desktop app by calling:
    > gradlew desktop:run

   Can be compiled into an GDX 'applet' using:
    > gradlew html:superdev
      -- takes around 27 secs
      -- access in a browser at http://localhost:8080
    and
    > gradlew html:dist
      -- takes around 40 secs
      -- results are stored in test\html\build\dist
      -- then copied to a server, with index.html renamed to app.html

      -- my new index.html uses an iframe of size (800,600)
         when calling app.html, which creates a window of
         size (300, 200). The window size is specified in the
         GDX launcher code (in DesktopLauncher.java and 
         HtmlLauncher.java).
*/


package com.mygdx.game;

import java.io.*;
import java.util.*;

import com.badlogic.gdx.*;
import com.badlogic.gdx.utils.*;
import com.badlogic.gdx.graphics.*;

import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.ui.TextField.*;
import com.badlogic.gdx.scenes.scene2d.utils.*;

import com.badlogic.gdx.net.*;
import com.badlogic.gdx.Net.*;



public class WeatherApp extends ApplicationAdapter 
                        implements InputProcessor
{
  private static final float MIN_DELAY = 1/30f;

  private static final String API_KEY = "xxxx";
    /* This is NOT a real API key. You will need to register 
       at https://www.weatherapi.com/ to get a key.
    */

  private boolean isDebugging = true;


  private Skin skin;
  private Stage stage;

  // widgets
  private TextField cityTF;
  private TextArea weatherTA;

  // globals used by the input textfield
  private String tfInput;
  private boolean tfHasData = false;
  private boolean tfHasFocus = false;
  private Weather weather = null;
  private String errMsg = null;



  public void create() 
  {
    Gdx.app.setLogLevel(Application.LOG_INFO);   // for browser debugging 

    Gdx.input.setCatchKey(Input.Keys.SPACE, true);
              // to stop scrolling on a webpage, but need to
              // catch spaces in code for the textfield

    // skin = new Skin(Gdx.files.internal("data/uiskin.json"));
    skin = new Skin(Gdx.files.internal("glassy/glassy-ui.json"));
         // https://github.com/czyzby/gdx-skins/


    stage = new Stage();
	stage.addActor(buildGUI());

    InputMultiplexer imp = new InputMultiplexer(this, stage);
    Gdx.input.setInputProcessor(imp);
    // Gdx.input.setInputProcessor(stage);

    // stage.setDebugAll(true);
  }  // end of create()


  private Table buildGUI()
  // add widgets to a table
  {
    Table t = new Table(skin);
	t.setFillParent(true);

    // first row
    Label lbl = new Label(" Enter city: ", skin);
    lbl.setAlignment(Align.center);
    lbl.getStyle().background = 
                  colorBackground(0.30f, 0.65f, 1.00f);  // light blue
    t.add(lbl).pad(10);

    cityTF = new TextField("", skin);

    cityTF.setTextFieldListener(new TextFieldListener() {
       public void keyTyped(TextField textField, char key) 
       // keep updating tfInput until ENTER is typed
       {
          if (!tfHasData) {
            if ((key == '\r') || (key == '\n') ||
                (key == Input.Keys.ENTER)) {
              lookupWeather(tfInput.trim());
            }
            else
              tfInput = textField.getText();
          }
       }
    });

    cityTF.addListener(new FocusListener() {
        public void keyboardFocusChanged(FocusListener.FocusEvent event, 
                                         Actor actor, boolean isFocused) 
        {  tfHasFocus = isFocused; }
    });

    t.add(cityTF).fillX().align(Align.left).padBottom(5);
    t.row();

    // second row
    weatherTA = new TextArea("", skin);
    weatherTA.setPrefRows(6);
    weatherTA.setDisabled(true);  // not editable

    t.add(weatherTA).colspan(2).fillX();
    t.row();

    return t;
  }  // end of buildGUI()



  private void lookupWeather(String cityStr)
  /* send a GET request to https://www.weatherapi.com/, and 
     wait for the response that may be JSON data
  */
  {
    dprint("Looking up: \"" + cityStr + "\"");

    // build GET parameters
    HashMap<String, String> paramsMap = new HashMap<String, String>();
    paramsMap.put("key", API_KEY);
    paramsMap.put("q", cityStr);
    paramsMap.put("aqi", "no");

    HttpRequestBuilder requestBuilder = new HttpRequestBuilder();
    HttpRequest req = requestBuilder.newRequest().
        method(HttpMethods.GET).
        url("http://api.weatherapi.com/v1/current.json").  // current weather
        content( HttpParametersUtils.convertHttpParameters(paramsMap)).
        timeout(5000).  // ms
        build();

    Gdx.net.sendHttpRequest(req, new Net.HttpResponseListener() 
    {
       public void handleHttpResponse(Net.HttpResponse resp) 
       {
         int status = resp.getStatus().getStatusCode();
         if (status == 200)   // data was retrieved
           buildWeather(resp);
         else
           buildError(status, resp);
       }

       public void failed(Throwable t) 
       {  errMsg = "Website access failed";  }

       public void cancelled() 
       {  dprint("Cancelled");  }
    });  // end of HttpResponseListener

  }  // end of lookupWeather()



  private void buildWeather(Net.HttpResponse resp)
  // extract a Weather object from the JSON response
  {
    String jsonStr = resp.getResultAsString();
    if (jsonStr == null)
      errMsg = "Website data is empty";
    else {
      Json json = new Json();
      dprint("\nResult: \n" +  json.prettyPrint(jsonStr) + "\n");
      Weather w = json.fromJson(Weather.class, jsonStr);
        // GWT requires class config for serialization
        // in \weather\core\src\WeatherApp.gwt.xml
        // https://libgdx.com/wiki/utils/reflection

      if (w != null) {
        weather = w;
        tfHasData = true;
      }
      else
        errMsg = "Could not parse JSON data";
    }
  }  // end of buildWeather()
  

  private void buildError(int status, Net.HttpResponse resp)
  // extract a WeatherError object from the JSON response
  {
    errMsg = "Website error: " + status;

    String jsonStr = resp.getResultAsString();
    if (jsonStr != null) {
      Json json = new Json();
      dprint("Error: \n" +  json.prettyPrint(jsonStr) + "\n");
      WeatherError wErr = json.fromJson(WeatherError.class, jsonStr);
        // GWT requires class config for serialization
        // in \weather\core\src\WeatherApp.gwt.xml
        // https://libgdx.com/wiki/utils/reflection

      if (wErr != null) {
        Error err = wErr.getError();
        errMsg += "; " + err.getMessage();
      }
    }
  }  // end of buildError()



  public void render() 
  {
    ScreenUtils.clear(Color.WHITE);
    showWeatherInfo();
	stage.act( Math.min(Gdx.graphics.getDeltaTime(), MIN_DELAY));
    stage.draw(); 
  }  // end of render()



  private void showWeatherInfo()
  // update text area, either with weather info or an error message
  {
    if (tfHasData) {  // there is weather data
      dprint("Building report");
      Location loc = weather.getLocation();
      Current current = weather.getCurrent();
      Condition cond = current.getCondition();
      String report = loc.getName() + ", " +
               loc.getRegion() + ", " +
               loc.getCountry() + "\n" + 
               current.getTemp_c() +  "c, " + 
               cond.getText() + "\n" +
               loc.getLocaltime();
      dprint(report);
      weatherTA.setText(report);
      tfHasData = false;  // data has been reported
    }
    
    // instead of weather data there might 
    // be an error message to report
    if (errMsg != null) {
      weatherTA.setText(errMsg);
      errMsg = null;   // error has been reported
    }
  }  // end of showWeatherInfo()



  private TextureRegionDrawable colorBackground(float r, float g, float b)
  // a pixel used for coloring backgrounds
  {
    Pixmap pixmap = new Pixmap(1,1, Pixmap.Format.RGBA8888);
    pixmap.setColor(r, g, b, 1f);  // (red,green,blue)
    pixmap.fill();
    TextureRegionDrawable bg = 
              new TextureRegionDrawable( new Texture(pixmap));
    pixmap.dispose();
    return bg;
  }  // end of colorBackground()



  public void resize(int width, int height) 
  // no resizing is possible, so this method isn't needed
  {  stage.getViewport().update(width, height, true);  }



  public void dispose() 
  {
    stage.dispose();
    skin.dispose();
  }


  private void dprint(String s)
  // print debugging info to the desktop or to the browser console
  {
    if (isDebugging) {
      if (isWebGL())
        Gdx.app.log("WeatherApp", s);   
           // appears in browser web console
           // which is displayed in Firefox with ctrl-shift-i
      else
        System.out.println(s);
    }
  }  // end of dprint()


  private boolean isWebGL()
  {  return (Gdx.app.getType() == Application.ApplicationType.WebGL); }



  // InputProcessor methods ------------------------

  public boolean touchUp(int x, int y, int pointer, int button)
  {   return false;  }
  
  public boolean touchDragged(int x, int y, int pointer)
  {   return false;  }
  
  public boolean touchDown(int x, int y, int pointer, int button)
  {   return false;  }
  
  public boolean scrolled(float amountX, float amountY)
  {   return false;  }
  
  public boolean mouseMoved(int x, int y)
  {   return false;  }
  
  public boolean keyUp(int code) 
  {   return false;  }
  
  public boolean keyTyped(char ch) 
  {   return false;  }
  

  public boolean keyDown(int code) 
  {
    if ((code == Input.Keys.SPACE) && isWebGL() && tfHasFocus) {
      dprint("space pressed in textfield");
      int pos = cityTF.getCursorPosition();
      // String lets = Input.Keys.toString(code);  // "SPACE"
      cityTF.setText( insertStr(cityTF.getText(), " ", pos));
      cityTF.setCursorPosition(pos+1); 
      tfInput = cityTF.getText();
    }
    return false;
  }


  private String insertStr(String s, String lets, int pos) 
  {  return (s.substring(0, pos) + lets + s.substring(pos));  }


}  // end of WeatherApp class
