#!/usr/bin/python

import smbus
import time
from mpd import MPDClient
import socket

# Server for MPD
SERVER = "lounge-pi"
client = MPDClient()               # create client object
client.timeout = 10                # network timeout in seconds (floats allowed), default: None
client.idletimeout = None          # timeout for fetching the result of the idle command is handled seperately, default: None

# Host lookup for determining Internet connectivity
INET_SERVER = "www.google.com"

# Define some device parameters
I2C_ADDR  = 0x27 # I2C device address
LCD_WIDTH = 20   # Maximum characters per line

# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off

ENABLE = 0b00000100 # Enable bit

# Timing constants
E_PULSE   = 0.0005
E_DELAY   = 0.0005
ZERO_DISP = 3

#Open I2C interface
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1

def lcd_init():
    # Initialist display
    lcd_byte(0x33,LCD_CMD) # 110011 Initialise
    lcd_byte(0x32,LCD_CMD) # 110010 Initialise
    lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
    lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
    lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
    lcd_byte(0x01,LCD_CMD) # 000001 Clear display
    time.sleep(E_DELAY)

def lcd_byte(bits, mode):
    # Send byte to data pins
    # bits = the data
    # mode = 1 for data
    #        0 for command

    bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
    bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT

    # High bits
    bus.write_byte(I2C_ADDR, bits_high)
    lcd_toggle_enable(bits_high)

    # Low bits
    bus.write_byte(I2C_ADDR, bits_low)
    lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
    # Toggle enable
    time.sleep(E_DELAY)
    bus.write_byte(I2C_ADDR, (bits | ENABLE))
    time.sleep(E_PULSE)
    bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
    time.sleep(E_DELAY)

def lcd_string(message,line):
    # Send string to display
    message = message.ljust(LCD_WIDTH," ")
    lcd_byte(line, LCD_CMD)
    for i in range(LCD_WIDTH):
        lcd_byte(ord(message[i]),LCD_CHR)

def music_details():
    # Set defaults just in case of errors
    artist   = "Cannot connect to"
    album    = SERVER
    song     = ""
    length   = 0
    position = 0
    
    # Connect to MPD on the server
    server_up = 1
    try:
        client.connect(SERVER, 6600)
    # Catch socket errors
    except IOError:
        server_up = 0
        print("Car_Server_LCD.py: Could not connect to "  + SERVER)
    # Catch Other MPD Errors
    except MPDError:
        server_up = 0
        print("Car_Server_LCD.py: Could not connect to "  + SERVER)

    if server_up == 1:    
        status = client.status()
        if status['state'] == "play":
            position = int(float(status['elapsed']))
        track = client.currentsong()
        if len(track) > 0:
            artist = track['artist']
            if len(artist) > LCD_WIDTH:
                artist += "   "
            album  = track['album']
            if len(album) > LCD_WIDTH:
                album += "   "
            song   = track['title']
            if len(song) > LCD_WIDTH:
                song += "   "
            length = int(track['time'])
        else:
            artist = "Unknown"
            album  = "Unknown"
            song   = "Unknown"
            
        # Close and disconnect from the server
        client.close()
        client.disconnect()

    return (artist, album, song, length, position)

def format_string(old_string, cur_string, pos):
    if old_string != cur_string:
        # We need to reset the position on a new string
        pos = 0
    if len(cur_string) > LCD_WIDTH:
        return cur_string[pos:pos + 20]
    else:
        return cur_string

def string_pos(cur_string, pos, zero_count):
    return_pos = 0    
    if (pos + 20) < len(cur_string):
        if (zero_count < ZERO_DISP) and (pos == 0):
            zero_count = zero_count + 1
        else:
            zero_count = 0
            return_pos = pos + 1
    return (return_pos, zero_count)
    
def is_connected(hostname):
    try:
        # see if we can resolve the host name -- tells us if there is
        # a DNS listening
        host = socket.gethostbyname(hostname)
        # connect to the host -- tells us if the host is actually
        # reachable
        s = socket.create_connection((host, 80), 2)
        s.close()
        return True
    except:
        pass
    return False

def main():
    # Main program block

    # Initialise display
    lcd_init()
    
    # Define old variables to speed up display updates
    old_artist = ""
    old_album  = ""
    old_song   = ""
    # Define old displays to speed up display updates
    disp_artist = ""
    disp_album  = ""
    disp_song   = ""
    disp_timer  = ""
    # Define the start positions of text strings
    start_artist = 0
    start_album  = 0
    start_song   = 0
    # Define the number of times a scrolling message has been at pos=0
    zero_artist = 0
    zero_album  = 0
    zero_song   = 0
    
    # Define an internet check timer
    inet_timer = 0
    
    while True:

        # Query the MPD server
        the_music = music_details()
        artist    = the_music[0]
        album     = the_music[1]
        song      = the_music[2]
        length    = the_music[3]
        position  = the_music[4]
        
        # Convert the times into HH:SS
        song_len = time.strftime('%M:%S', time.gmtime(length))
        song_pos = time.strftime('%M:%S', time.gmtime(position))

        # Work out if we need to display part of a string
        artist_string = format_string(old_artist, artist, start_artist)
        old_artist    = artist
        pos_artist    = string_pos(artist, start_artist, zero_artist)
        start_artist  = pos_artist[0]
        zero_artist   = pos_artist[1]
        
        album_string  = format_string(old_album, album, start_album)
        old_album     = album
        pos_album     = string_pos(album, start_album, zero_album)
        start_album   = pos_album[0]
        zero_album    = pos_album[1]
        
        song_string   = format_string(old_song, song, start_song)
        old_song      = song
        pos_song      = string_pos(song, start_song, zero_song)
        start_song    = pos_song[0]
        zero_song     = pos_song[1]

        # Now work out if we have Internet connection, but only
        # do it every 10 seconds to save bandwidth
        if time.time() - inet_timer >= 10:
            if is_connected(INET_SERVER):
                inet = "*"
            else:
                inet = "-"
            inet_timer = time.time()
        
        timer = song_pos + " - " + song_len + "      " + inet
        
        # Send some test
        if artist_string != disp_artist:
            disp_artist = artist_string
            lcd_string(artist_string, LCD_LINE_1)
        if album_string != disp_album:
            disp_album = album_string
            lcd_string(album_string,  LCD_LINE_2)
        if song_string != disp_song:
            disp_song = song_string
            lcd_string(song_string,   LCD_LINE_3)
        if timer != disp_timer:
            disp_timer = timer
            lcd_string(timer,  LCD_LINE_4)

        time.sleep(0.2)

if __name__ == '__main__':

    try:
        main()
    except KeyboardInterrupt:
        pass
    finally:
        lcd_byte(0x01, LCD_CMD)

