#!/usr/bin/python
# -*- coding: koi8-r -*-
import pygtk
pygtk.require('2.0')
import gtk
import gobject

import os
import sys
import time
import zipfile
import StringIO

PROGRAM = 'metromap'
MAPINI = 'Metro.ini'

MD = None
MetroMap = None
Finder = None

DTlist = dict()
DeletedTransfers = dict()

scalelist = list()
for sc in xrange(5, 21):
        scalelist.append([str(sc * 10) + '%', sc / 10.0])
scale = 1.0

XSIZE, YSIZE = None, None
compact = False
dw, dh = gtk.gdk.screen_width(), gtk.gdk.screen_height()
if dw < 800:
        compact = True
delta = 5

dtime = 0
h = time.localtime()[3]
if h >= 21 or h <= 7:
        dtime = 1

CITY = None

import gettext

binpath = os.path.realpath(os.path.dirname(sys.argv[0]))
if binpath.endswith('/bin'): #FHS
        PREFIX = binpath[:binpath.rfind('/bin')]
        DATAPATH = PREFIX + '/share/' + PROGRAM + '/data/'
        MODULEPATH = PREFIX + '/share/' + PROGRAM + '/modules/'
        gettext.bindtextdomain(PROGRAM, PREFIX + '/share/locale/')
else:
        PREFIX = binpath
        DATAPATH = PREFIX + '/data/'
        MODULEPATH = PREFIX + '/modules/'
        gettext.bindtextdomain(PROGRAM, PREFIX + '/locale/')

sys.path.insert(1, MODULEPATH)
gettext.textdomain(PROGRAM)
_ = gettext.gettext

from ReadMap import ReadMap, GetMapName
from FindPath import FindPath

import MapDisplay
MapDisplay._ = _

import Interface
Interface._ = _

RCDIR = os.environ.get('HOME') + "/." + PROGRAM
if not os.path.isdir(RCDIR):
        try:
                os.mkdir(RCDIR)
        except:
                pass
#load config
if os.path.isfile(RCDIR + "/rc"):
        f = file(RCDIR + "/rc", 'r')
        done = False
        while not done:
                s = f.readline()
                if not s:
                        break
                p = s.split('=')
                if len(p) == 2:
                        if p[0] == 'city':
                                CITY = p[1].rstrip()
                        elif p[0] == 'zoom':
                                scale = float(p[1].rstrip())
                                if not scale in map(lambda a: a[1], scalelist):
                                        scale = 1
                        elif p[0] == 'xsize':
                                XSIZE = int(p[1].rstrip())
                        elif p[0] == 'ysize':
                                YSIZE = int(p[1].rstrip())
                        elif p[0] == 'delta':
                                delta = int(p[1].rstrip())
                                if delta < 0:
                                        delta = 0
                                elif delta > 99:
                                        delta = 99
                        elif p[0] == 'compact':
                                if p[1].rstrip().lower() == "true":
                                        compact = True
                        elif p[0].startswith('dt[') and p[0][-1] == ']':
                                DTlist[p[0][3:-1]] = list()
                                sp = p[1].rstrip().split(',')
                                for pair in sp:
                                        l = pair.rstrip().lstrip().split('-', 2)
                                        if len(l) == 2 and l[0].isdigit() and l[1].isdigit():
                                                DTlist[p[0][3:-1]].append((int(l[0]), int(l[1])))
        f.close()

#save config
def save_config():
        try:
                f = file(RCDIR + "/rc", 'w')
                f.write('city=' + CITY + '\n')
                f.write('xsize=' + str(Iface.xsize) + '\n')
                f.write('ysize=' + str(Iface.ysize) + '\n')
                f.write('delta=' + str(delta) + '\n')
                if Iface.compact_mode:
                        f.write('compact=true\n')
                else:
                        f.write('compact=false\n')
                f.write('zoom=' + str(scale) + '\n')
                for c in DTlist.keys():
                        if len(DTlist[c]):
                                st = 'dt[' + c + ']='
                                for p in DTlist[c]:
                                        st += str(p[0]) + '-' + str(p[1]) + ','
                                st = st[:-1] + '\n'
                                f.write(st)
                f.close()
        except:
                pass

def sync_to_dt():
        DTlist[CITY] = list()
        for dt in DeletedTransfers.keys():
                if not (dt[0], dt[1]) in DTlist[CITY] and not (dt[1], dt[0]) in DTlist[CITY]:
                        DTlist[CITY].append((dt[0], dt[1]))

def sync_from_dt():
        global DeletedTransfers

        DeletedTransfers = dict()
        if DTlist.has_key(CITY):
                Finder.set_graph(dt_disable(Finder.graph, DTlist[CITY]))

def atexit(foo):
        save_config()
        gtk.main_quit()

st_start = None
st_end = None
path_list = list()

def ciext_file_find(path, name, ext):
        for f in os.listdir(path):
                if f.startswith(name) and len(f) == len(name) + len(ext) + 1 and \
                   f[-len(ext):].lower() == ext.lower():
                        return f
        #not found, return some shit
        return name + '.' + ext

def get_data_full(datapath, city, filename):
        f = None
        n = None
        try:
                n = datapath + city + '/' + filename
                f = open(n)
        except:
                try:
                        n = datapath + ciext_file_find(datapath, city, 'pmz')
                        z = zipfile.ZipFile(n, 'r')
                        f = StringIO.StringIO(z.read(filename))
                        z.close()
                except:
                        try:
                                n = datapath + ciext_file_find(datapath, city, 'zip')
                                z = zipfile.ZipFile(n, 'r')
                                intn = city + '.pmz'
                                for zn in z.namelist():
                                        if zn.lower() == city.lower() + '.pmz':
                                                intn = zn
                                                break
                                pmzf = StringIO.StringIO(z.read(intn))
                                z.close()
                                z = zipfile.ZipFile(pmzf, 'r')
                                f = StringIO.StringIO(z.read(filename))
                                z.close()
                        except:
                                n = _("unknown")
                                f = None
        return n, f

def get_data(city, filename):
        for dp in DATAPATH, RCDIR + '/':
                n, f = get_data_full(dp, city, filename)
                if f:
                        break
        return n, f

citylist = list()
for dp in DATAPATH, RCDIR + '/':
        if not os.path.isdir(dp):
                continue
        for c in os.listdir(dp):
                cn = c
                if c.lower().endswith('.zip') or c.lower().endswith('.pmz'):
                        cn = c[:-4]
                n, f = get_data_full(dp, cn, 'Metro.ini')
                if f:
                        name = GetMapName(f)
                        f.close()
                        if name:
                                citylist.append((name, cn))

if len(citylist) == 0:
        print unicode(_('No data files found, please install some.'))
        sys.exit(0)

if not CITY or not len(filter(lambda a: a[1] == CITY, citylist)):
        if len(filter(lambda a: a[1] == "Moscow", citylist)):
                CITY = "Moscow"
        else:
                CITY = citylist[0][1]

def station_selected(station, isstart):
        global st_start, st_end, path_list

        if st_start != station and st_end != station:
                if isstart:
                        st_start = station
                        Iface.set_from(station)
                        MD.set_start_station(station)
                else:
                        st_end = station
                        Iface.set_to(station)
                        MD.set_stop_station(station)

                if st_start != None and st_end != None:
                        path_list = Finder.waves_accurate(st_start, st_end, MetroMap.WaitLen)
                        path_list.sort()

                if len(path_list):
                        MD.set_path_list(path_list[0][2:])
                else:
                        MD.set_path_list(None)

                Iface.set_path_list(map(lambda a: _("Minutes: %d Changes: %d") % (a[0], a[1]), path_list))

def list_selected(num):
        global path_list

        if path_list and num < len(path_list):
                MD.set_path_list(path_list[num][2:])

def zoom_changed(zoom):
        global scale

        if scale != zoom:
                scale = zoom
                city_changed(None)

def city_changed(name):
        global st_start, st_end, path_list, MetroMap, MD, Finder, prefs, CITY, dtime, delta, scale

        if name != None:
                CITY = name
                st_start = st_end = None
                path_list = list()
        n, f = get_data(CITY, MAPINI)
        if not f:
                print unicode(_('Cannot find Metro.ini for city "') + CITY + '"')
                sys.exit(0)
        MetroMap = ReadMap(f)
        f.close()
        MetroMap.SetDelayTime(dtime)
        snames = map(lambda a: ["%s (%s)" % (a['name'], MetroMap.Lines[a['line']]['name']), a['number']], MetroMap.Stations.values())
        snames.sort()

        sh = False #hack for StPetersburg map
        if CITY.lower() == 'peterburg':
                sh = True

        mdlines = MetroMap.Lines
        mdstations = MetroMap.Stations
        mdvectors = MetroMap.Map['vectors']

        if scale != 1:
                for k in mdlines.keys():
                        mdlines[k]['diameter'] = int(round(mdlines[k]['diameter'] * scale))
                for k in mdstations.keys():
                        mdstations[k]['diameter'] = int(round(mdstations[k]['diameter'] * scale))
                        for ak in mdstations[k]['add'].keys():
                                for i in xrange(len(mdstations[k]['add'][ak]['coords'])):
                                        foo = mdstations[k]['add'][ak]['coords'][i]
                                        mdstations[k]['add'][ak]['coords'][i] = (int(round(foo[0] * scale)), int(round(foo[1] * scale)))
                        mdstations[k]['x'] = int(round(mdstations[k]['x'] * scale))
                        mdstations[k]['y'] = int(round(mdstations[k]['y'] * scale))
                        mdstations[k]['namerect'][0] = int(round(mdstations[k]['namerect'][0] * scale))
                        mdstations[k]['namerect'][1] = int(round(mdstations[k]['namerect'][1] * scale))
                        mdstations[k]['namerect'][2] = int(round(mdstations[k]['namerect'][2] * scale))
                        mdstations[k]['namerect'][3] = int(round(mdstations[k]['namerect'][3] * scale))
                if mdvectors.has_key('lineswidth'):
                        mdvectors['lineswidth'] = int(round(mdvectors['lineswidth'] * scale))
                for i in xrange(len(mdvectors['instructions'])):
                        if mdvectors['instructions'][i][0] == 'spline':
                                mdvectors['instructions'][i][1] = int(round(mdvectors['instructions'][i][1] * scale))
                                for j in xrange(len(mdvectors['instructions'][i][2])):
                                        foo = mdvectors['instructions'][i][2][j]
                                        mdvectors['instructions'][i][2][j] = (int(round(foo[0] * scale)), int(round(foo[1] * scale)))
                        elif mdvectors['instructions'][i][0] == 'text':
                                mdvectors['instructions'][i][1]['size'] = int(round(mdvectors['instructions'][i][1]['size'] * scale))
                                mdvectors['instructions'][i][1]['x'] = int(round(mdvectors['instructions'][i][1]['x'] * scale))
                                mdvectors['instructions'][i][1]['y'] = int(round(mdvectors['instructions'][i][1]['y'] * scale))

        MD = MapDisplay.MapDisplay(mdlines, mdstations, mdvectors, station_selected, sh)
        #MD = MapDisplay.MapDisplay(MetroMap.Lines, MetroMap.Stations, MetroMap.Map['vectors'], station_selected, sh)
        MD.set_menu(Iface.menu_callback, Iface.menu_items)
        MD.set_dtcb(transfers_cb)

        if name != None:
                Iface.set_path_list([])
        Iface.set_da(MD.da, MD.pw, MD.ph)
        Iface.set_st_list(snames)
        Iface.set_dtime(dtime)
        Iface.set_win_title(PROGRAM + ' - ' + MetroMap.Map['city'])
        if name == None:
                MD.set_start_station(st_start)
                MD.set_stop_station(st_end)
                Iface.set_from(st_start)
                Iface.set_to(st_end)
                Iface.path_selected(None)
        if name != None:
                if not Finder:
                        Finder = FindPath(MetroMap.Graph, delta)
                else:
                        Finder.set_graph(MetroMap.Graph)
        sync_from_dt()

def dt_enable(g, pairs):
        for p in pairs:
                for tr in DeletedTransfers[(p[0], p[1])]:
                        g[p[0]].append(tr)
                        g[p[1]].append(tr)
                del DeletedTransfers[(p[0], p[1])]
                del DeletedTransfers[(p[1], p[0])]
        return g

def dt_disable(g, pairs):
        for p in pairs:
                DeletedTransfers[(p[0], p[1])] = list()
                DeletedTransfers[(p[1], p[0])] = list()
                for tr in g[p[0]]:
                        if tr[0] == p[1]:
                                DeletedTransfers[(p[0], p[1])].append(tr)
                                DeletedTransfers[(p[1], p[0])].append(tr)
                                g[p[0]].remove(tr)
                                break
                for tr in g[p[1]]:
                        if tr[0] == p[0]:
                                DeletedTransfers[(p[0], p[1])].append(tr)
                                DeletedTransfers[(p[1], p[0])].append(tr)
                                g[p[1]].remove(tr)
                                break
        return g

def transfers_cb(act, sfrom = None, sto = None):
        if act == 'get':
                return DeletedTransfers
        elif act == 'set':
                if sfrom == None or sto == None: #enable all disabled transfers
                        Finder.set_graph(dt_enable(Finder.graph, DTlist[CITY]))
                else:
                        if DeletedTransfers.has_key((sfrom, sto)):
                                Finder.set_graph(dt_enable(Finder.graph, [[sfrom, sto]]))
                        else:
                                Finder.set_graph(dt_disable(Finder.graph, [[sfrom, sto]]))
                recalc_paths()
                sync_to_dt()

def recalc_paths():
        global st_start

        if st_start != None:
                station = st_start
                st_start = None
                station_selected(station, True)

def dtime_changed(num):
        global MetroMap, dtime

        if num != dtime:
                dtime = num
                MetroMap.SetDelayTime(dtime)
                recalc_paths()

def delta_changed(num):
        global delta

        if num != delta:
                delta = num
                Finder.maxerror = delta
                recalc_paths()

Iface = Interface.Interface(PROGRAM, None, citylist = citylist, compact = compact, delta = delta,
                            citynow = CITY, xsize = XSIZE, ysize = YSIZE,
                            zoomlist = scalelist, zoomnow = scale)
Iface.win.connect('destroy', atexit)
Iface.set_path_cb(list_selected)
Iface.set_station_cb(station_selected)
Iface.set_city_cb(city_changed)
Iface.set_dtime_cb(dtime_changed)
Iface.set_delta_cb(delta_changed)
Iface.set_zoom_cb(zoom_changed)
Iface.quit_cb = atexit

city_changed(CITY)

gtk.main()

