package localsearch;

import java.io.InputStream;
import java.lang.Exception;
import javafx.io.http.HttpRequest;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.animation.Interpolator;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextBox;
import javafx.scene.paint.Color;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import localsearch.model.Category;
import localsearch.model.Rating;
import localsearch.model.Restaurant;
import localsearch.parser.JSONPullParser;
import localsearch.parser.XMLPullParser;
import localsearch.view.ImageButton;

// Data type to be used in trasaction
def dataType = "xml"; // set to xml or json

// TODO: get an appid from http://developer.yahoo.com/search/
def appid = FX.getArgument("yahoo_appid");

// Application Bounds
var stageX = 280.0;
var stageY = 140.0;
def stageWidth = 240;
def stageHeight = 320;

// Is running as Applet?
var inBrowser = "true".equals(FX.getArgument("isApplet") as String);

// Restaurant Details Index
var index:Integer = 0;

// Information about all relevant restaurants
public var restaurants: Restaurant[];

// Search and load restaurant details
public function searchCoffeeShops(zipcode:String) {
    
    delete restaurants;
    resetRestaurantDetails();
    
    // Perform some basic validation on zipcode
    if(not validateZipCode(zipcode)) { 
        alert("Error", "Zip Code {zipcode} is not valid. Enter valid zip code.");
        return; 
    }
    
    def query = "coffee";
    def results = 5;
    def location = "http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid={appid}&query={query}&zip={zipcode}&results={results}&output={dataType}";
            
    var resultsProcessor: function(is: InputStream);
    if("xml".equalsIgnoreCase(dataType)) {
        resultsProcessor = XMLPullParser.processResults;
    } else {
        resultsProcessor = JSONPullParser.processResults;
    }
        
    println("Loading {dataType} data from {location}...\"");
    alert("Please wait...", "Searching for Coffee Shops near zip code: {zipcode}...");

    try {
        var request = RequestHandler {
            location: location
            method: HttpRequest.GET
            processResults: resultsProcessor
        }
        request.enqueue();
    } catch (e:Exception) {
        println("WARNING: {e}");
        alert("Error", "Could not search... Please try again later.");
    }
}

// Basic validation for Zip Code
function validateZipCode(zipcode:String): Boolean {
    
    //
    // Zip Code Format -> 12345 or 12345-1234
    //
    
    try {
        
        if(zipcode.length() == 5) {
            var zipCodeInt = java.lang.Integer.valueOf(zipcode).intValue();
            return (zipCodeInt > 0);
        } else if (zipcode.length() == 10) {
            var dashIndex = zipcode.indexOf("-");
            if(dashIndex != 5) return false;
            var firstPart = zipcode.substring(0, dashIndex);
            var zipCodeInt = java.lang.Integer.valueOf(firstPart).intValue();
            if(zipCodeInt <= 0) { return false; }
            var secondPart = zipcode.substring(0, dashIndex);
            zipCodeInt = java.lang.Integer.valueOf(secondPart).intValue();
            return (zipCodeInt > 0);
        }
        
    } catch (e:Exception) { }
    
    return false;
}

// Background Image
var bgImage:ImageView = ImageView { 
    focusable: true
    image: Image {
        url: "{__DIR__}images/background.png"
    }
    onKeyPressed:function(e:KeyEvent) {
        if(e.code == KeyCode.VK_LEFT) {
            onBack();
        } else if(e.code == KeyCode.VK_RIGHT) {
            onNext();
        } else if(e.code == KeyCode.VK_DOWN) {
            zipCodeText.requestFocus();
        }
    }
    onMouseClicked:function(e:MouseEvent) {
        bgImage.requestFocus();
    }
}

// Display details of previous restaurant in list
var backButton = ImageButton { 
    
    x: 3
    y: 150
    normalImage: Image { url: "{__DIR__}images/arrow_left_normal.png" }
    overImage: Image { url: "{__DIR__}images/arrow_left_over.png" }
    
    onMouseClicked: function(e) {
        onBack();
    }
}
function onBack() {
    if((sizeof restaurants) == 0) { return; }
    index--; if(index < 0) { index = ((sizeof restaurants) - 1); }
    showRestaurantDetails(index, false);
}

// Display details of next restaurant in list
var nextButton = ImageButton { 
    
    x: stageWidth - 19
    y: 150
    normalImage: Image { url: "{__DIR__}images/arrow_right_normal.png" }
    overImage: Image { url: "{__DIR__}images/arrow_right_over.png" }
    
    onMouseClicked: function(e) {
        onNext();
    }
}
function onNext() {
    if((sizeof restaurants) == 0) { return; }
    index++; if(index >= (sizeof restaurants)) { index = 0; }
    showRestaurantDetails(index, true);
}

// Dispose Application
var closeButton = ImageButton { 
    
    x: stageWidth - 20
    y: 8
    normalImage: Image { url: "{__DIR__}images/x_normal.png" }
    overImage: Image { url: "{__DIR__}images/x_over.png" }
    visible: bind (not inBrowser)
    
    onMouseClicked: function(e) {
        javafx.lang.FX.exit();
    }
}

// Reset Restaurant Details
public function resetRestaurantDetails() {
    shopName = "";
    address = "";
    city = "";
    phone = "";
    star.visible = false;
    comments = "";
    title = "Nearest Coffee Shops";
}

// Display details of restaurant at specified index in list
public function showRestaurantDetails(index:Integer, scrollLeft:Boolean) {
    
    if(index >= (sizeof restaurants)) { return ; }
    
    var scrollXVal = 1; // Scroll Right
    if(scrollLeft) { scrollXVal = -1; }
    
    shopDetailsX = 0;
    
    // Slide restaurant details animation
    var timeline:Timeline = Timeline {
       repeatCount:1
       autoReverse: false
       rate: 1.0
       keyFrames: [ 
            KeyFrame {
                time: 250ms
                values: [ shopDetailsX => scrollXVal * stageWidth tween Interpolator.LINEAR ] 
                action: function() {
                    shopDetailsX = scrollXVal * -stageWidth;
                    var result = restaurants[index];
                    shopName = trimString(result.title, 25);
                    address = trimString(result.address, 30);
                    city = trimString("{result.city} {result.state}", 30);
                    phone = "{result.phone}";
                    setStars(result.rating.averageRating);
                    var lastReview = "{result.rating.lastReviewIntro}";
                    comments = trimString("{lastReview}", 240);
                    title = "Coffee Shops ({index + 1} of {sizeof restaurants})";
                }
            },
            KeyFrame {
                time: 250ms
                values: [ shopDetailsX => scrollXVal * -stageWidth tween Interpolator.DISCRETE ] 
            },
            KeyFrame {
                time: 500ms
                values: [ shopDetailsX => 0 tween Interpolator.LINEAR ] 
            }
       ]
    };
    timeline.playFromStart();    
}

// Trim the string if length is greater than specified length
function trimString(string:String, length:Integer) : String {
        
    if(string == null) return "";
    if(string.length() > length) { 
        return "{string.substring(0, length).trim()}..."; 
    } else {
        return string;
    }
}

// Star Rating Images
var defaultStarImage = Image {
    url: "{__DIR__}images/star0.png"
}

var star = ImageView { 
    x: 25
    y: 111
    image: defaultStarImage
    visible: false
}

// Convert specified star rating in string to
// Integer and display as many number of star images
function setStars(starCount:String) {
    
    var imageSuffix = "0";
    
    try {
        var starIntCount = java.lang.Float.valueOf(starCount).intValue();
        imageSuffix = "{starIntCount}";
        if(starIntCount > 5) {
            imageSuffix = "5"; 
        } else if(starCount.indexOf(".") > 0) {
            imageSuffix = "{imageSuffix}.5"; 
        }
    } catch (e:java.lang.Exception) {
        e.printStackTrace();
    }
    
    star.image = Image {
        url: "{__DIR__}images/star{imageSuffix}.png"
        placeholder: defaultStarImage
    }
    star.visible = true;
}

// Application Title
var titleBar = Rectangle {
    width: stageWidth
    height: 25
    fill: Color.TRANSPARENT
    visible: bind (not inBrowser)
    onMouseDragged: function(e) {
        stageX += e.dragX;
        stageY += e.dragY;
    }
}

var title = "Nearest Coffee Shops";
var titleText = Text {
    x: 45
    y: 18
    font: Font { name:"sansserif", size: 14 }
    fill: Color.BLACK
    content: bind title
}

// Divider
var divider = Line {
    startX: 0  startY:   25
    endX: stageWidth  endY: 25
    stroke: Color.rgb(138, 110, 72)
}

// Restaurant Name
var shopName = "";
var shopNameText = Text {
    x: 25
    y: 50
    font: Font { name:"sansserif", size: 13 }
    fill: Color.BLACK
    content: bind shopName
}

// Street Address
var address = "";
var addressText = Text {
    x: 25
    y: 68
    font: Font { name:"sansserif", size: 12 }
    fill: Color.BLACK
    content: bind address
}

// City and State
var city = "";
var cityText = Text {
    x: 25
    y: 86
    font: Font { name:"sansserif", size: 12 }
    fill: Color.BLACK
    content: bind city
}

// Phone Number
var phone = "";
var phoneText = Text {
    x: 25
    y: 104
    font: Font { name:"sansserif", size: 12 }
    fill: Color.BLACK
    content: bind phone
}

// Latest review comments
var comments = "";
var commentsText = Text {
    x: 25
    y: 147
    font: Font { name:"sansserif", size: 11 }
    fill: Color.BLACK
    content: bind comments
    wrappingWidth: stageWidth - 60
};

// Shop Details Group
var shopDetailsX: Number = 0;
var shopDetailsDisplay = Group {
    content: bind [ 
        shopNameText, addressText, cityText, phoneText, commentsText, star
    ]
    translateX: bind shopDetailsX
}
var shopDetailsGroup = Group {
    content: [ shopDetailsDisplay ]
    clip: Rectangle {
        x: 15
        y: 32
        width: stageWidth - 30
        height: stageHeight - 64
    }
}

// ZipCode
var zipCodeLabel = Text {
    x: 15
    y: stageHeight - 40
    font: Font { name:"sansserif", size: 12 }
    fill: Color.BLACK
    content: "zip code:"
}
var textDeltaBounds: Rectangle = Rectangle {
    x: 2 y: 2 width: 14 height: 5
};
var zipCodeText: TextBox = TextBox {
    blocksMouse: true
    translateX: 75
    translateY: stageHeight - 58
    columns: 7
    selectOnFocus: false
    text: "95054"
    clip: Rectangle { 
        x: bind textDeltaBounds.x 
        y: bind textDeltaBounds.y 
        width: bind (zipCodeText.width - textDeltaBounds.width)
        height: bind (zipCodeText.height - textDeltaBounds.height)
    }
    action: function() {
        searchCoffeeShops(zipCodeText.text.trim());
    }
    onKeyPressed:function(e:KeyEvent) {
        if(e.code == KeyCode.VK_UP) {
            bgImage.requestFocus();
        }
    }
}
var zipCodeTextBorder = Rectangle {
    x: bind zipCodeText.translateX + textDeltaBounds.x
    y: bind zipCodeText.translateY + textDeltaBounds.y
    width: bind (zipCodeText.width - textDeltaBounds.width)
    height: bind (zipCodeText.height - textDeltaBounds.height)
    fill: Color.TRANSPARENT
    stroke: Color.rgb(138, 110, 72)
    strokeWidth: 2.0
}

// Search for restaurants with in range of specified ZipCode
var searchButton = ImageButton { 
    
    x: stageWidth - 79
    y: stageHeight - 57
    normalImage: Image { url: "{__DIR__}images/search_normal.png" };
    overImage: Image { url: "{__DIR__}images/search_over.png" };
    
    onMouseClicked: function(e) {
        searchCoffeeShops(zipCodeText.text.trim());
    }
}

// Service Provider Information
var serviceProviderText = Text {
    x: 50
    y: stageHeight - 12
    font: Font { name:"sansserif", size: 11 }
    fill: Color.rgb(96, 78, 51)
    content: "Web Services by Yahoo!"
}

// Application User Interface
def stage = Stage {
    title: "Coffee Shop Search"
    resizable: false
    x: bind stageX with inverse
    y: bind stageY with inverse
    width: stageWidth
    height: stageHeight
    visible: true
    style: StageStyle.TRANSPARENT
    scene: Scene {
        content: Group {
            content: bind [
                bgImage, titleBar, titleText, divider, shopDetailsGroup, backButton, nextButton, closeButton,
                zipCodeLabel, zipCodeText, zipCodeTextBorder, searchButton, serviceProviderText
            ]
            clip: Rectangle {
                width: stageWidth
                height: stageHeight
                arcWidth: 20
                arcHeight: 20
            }
        }
        fill: Color.TRANSPARENT
    }
}

public function alert(alertTitle:String, msg:String): Void {
    println(msg);
    phone = alertTitle;
    comments = trimString(msg, 240);
}

function run() {
    // Reset TextBox bounds for Mobile
    if(FX.getProperty("javafx.me.profiles") != null) {
        textDeltaBounds = Rectangle { x: 0 y: 0 width: 0 height: 0 }
    }
    searchCoffeeShops("95054");
}