#!/usr/bin/python
# -*- coding: koi8-r -*-
# This file is part of the metromap
#
# Copyright(c) 2010, Sergey Pinaev
# http://metromap.antex.ru/
#
# This file may be licensed under the terms of of the
# GNU General Public License Version 2 (the ``GPL'').
#
# Software distributed under the License is distributed
# on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
# express or implied. See the GPL for the specific language
# governing rights and limitations.
#
# You should have received a copy of the GPL along with this
# program. If not, go to http://www.gnu.org/licenses/gpl.html
# or write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import locale
import math
import ConfigParser
import StringIO

import os, stat, zipfile, time

class MyConfigParser:
        def __init__(self):
                self.data = {}
                self.nocase_data_index = {}

        def readfp(self, fp):
                self.data = {}
                self.appendfp(fp)

        def appendfp(self, fp):
                nowsec = None
                for l in fp.readlines():
                        l = l.rstrip().lstrip()
                        if l.startswith('[') and l.endswith(']'):
                                nowsec = l[1:-1]
                        elif nowsec and l and not l.lstrip().startswith(';'):
                                try:
                                        option, value = map(lambda a: a.lstrip().rstrip(), l.split('=', 1))
                                        if option and value:
                                                if not self.data.has_key(nowsec):
                                                        self.data[nowsec] = {}
                                                        sec_lower = nowsec.lower()
                                                        if not self.nocase_data_index.has_key(sec_lower):
                                                                self.nocase_data_index[sec_lower] = {}
                                                if not self.data[nowsec].has_key(option):
                                                        self.data[nowsec][option] = []
                                                        sec_lower = nowsec.lower()
                                                        opt_lower = option.lower()
                                                        if not self.nocase_data_index[sec_lower].has_key(opt_lower):
                                                                self.nocase_data_index[sec_lower][opt_lower] = [nowsec, option]
                                                self.data[nowsec][option].append(value)
                                except:
                                        pass

        def sections(self):
                return self.data.keys()

        def options(self, section):
                return self.data[section].keys()

        def has_option(self, section, option):
                return self.data.has_key(section) and self.data[section].has_key(option)

        def has_option_nocase(self, section, option):
                section = section.lower()
                option = option.lower()
                return self.nocase_data_index.has_key(section) and self.nocase_data_index[section].has_key(option)

        def get(self, section, option):
                return self.data[section][option]

        def get_nocase(self, section, option):
                section = section.lower()
                option = option.lower()
                if self.nocase_data_index.has_key(section) and self.nocase_data_index[section].has_key(option):
                        return self.data[self.nocase_data_index[section][option][0]][self.nocase_data_index[section][option][1]]
                return None

        def get_nocase_first(self, section, option):
                val = self.get_nocase(section, option)
                if val and len(val):
                        return val[0]
                return None

def GetMapName(ini):
        cp = ConfigParser.RawConfigParser()
        try:
                cp.readfp(ini)
        except:
                pass
        try:
		ini.seek(0)
        except:
                pass
        sec = cp.sections()
        lsec = map(lambda a: a.lower(), sec)
        if 'options' in lsec:
                s = sec[lsec.index('options')]
                if cp.has_option(s, 'name'):
                        name = cp.get(s, 'name')
                        if type(name) != str:
                                name = name[0]
                        return unicode(name, detect_encoding(name))

        return None

def GetMapVec(ini):
        cp = ConfigParser.RawConfigParser()
        try:
                cp.readfp(ini)
        except:
                pass
        try:
		ini.seek(0)
        except:
                pass
        sec = cp.sections()
        lsec = map(lambda a: a.lower(), sec)
        if 'options' in lsec:
                s = sec[lsec.index('options')]
                if cp.has_option(s, 'imagefilename'):
                        name = cp.get(s, 'imagefilename')
                        if type(name) != str:
                                name = name[0]
                        return unicode(name, detect_encoding(name))

        return None

def detect_encoding(s):
        if HILDON:
                return 'utf-8'
        for enc in ('cp1251', 'koi8-r', 'utf-8', 'iso8859-5'):
                try:
                        l, u = count_lu(unicode(s, enc))
                        if u * 2 < l:
                                return enc
                except:
                        pass
        #fallback to default locale encoding
        (nlcode, nlenc) = locale.getdefaultlocale()
        if not nlenc:
                nlenc = 'iso8859-1'
        if nlenc.lower() == 'utf':
                nlenc = 'utf-8'

        return nlenc

def count_lu(s):
        u = len(filter(lambda a: a.isupper(), s))
        l = len(filter(lambda a: a.islower(), s))
        return l, u

def cp_get(cp, section, option, fallback):
        if not cp.has_option(section, option):
                return fallback
        v = cp.get(section, option)
        if type(v) != str:
                v = v[0]
        return v

def cp_get_int(cp, section, option, fallback):
        if not fallback:
                fallback = 0
        if not cp.has_option(section, option):
                return fallback
        v = cp.get(section, option)
        if type(v) != str:
                v = v[0]
        if not v.isdigit():
                return fallback
        return int(v)

def cp_get_color(cp, section, option, fallback):
        if not fallback:
                fallback = "000000"
        if not cp.has_option(section, option):
                return fallback
        v = cp.get(section, option)[0]
        if type(v) != str:
                v = v[0]
        if not len(v) == 6:
                return fallback
        try:
                val = int(v, 16)
        except:
                return fallback
        return v

def parse_with_quotes(s):
        res = list()
        ns = ''
        waitclose = None
        for c in s.lstrip().rstrip():
                if waitclose:
                        if c == waitclose:
                                if waitclose == ')':
                                        ns += c
                                waitclose = None
                        else:
                                ns += c
                else:
                        if c == '"':
                                waitclose = '"'
                        elif c == '(':
                                ns += c
                                waitclose = ')'
                        elif c == ',': #delimiter
                                res.append(ns.lstrip().rstrip())
                                ns = ''
                        else:
                                ns += c
        ns = ns.lstrip().rstrip()
        res.append(ns)

        return res

def split_by_comma(s, num):
        #FIXME: int() can raise expception
	#if s.rstrip() == "":
	#	return list()
        if s.rstrip()[-1] == ',':
                s = s.rstrip()[:-1]
        l = map(lambda a: int(a.lstrip().rstrip()), s.split(','))
        res = list()
        for i in xrange(len(l) / num):
                res.append(l[i * num : i * num + num])

        return res

def split_by_comma2(s, num):
        #FIXME: int() can raise expception
        if s.rstrip()[-1] == ',':
                s = s.rstrip()[:-1]
        l = map(lambda a: int(a.lstrip().rstrip()), s.split(','))
        res = list()
        for i in xrange(len(l) / num):
                res.append(tuple(l[i * num : i * num + num]))

        e = 0
        if len(l) % num:
                e = l[-1]

        return [e, res]

def strict_split(value, separator):
        return filter(lambda a: a, map(lambda a: a.lstrip().rstrip(), value.split(separator)))

def read_full_section(cp, section):
        res = list()
        try:
                for i in cp.options(section):
                        #res.append(map(lambda a: a.rstrip().lstrip(), parse_with_quotes(cp.get(section, i))))
                        v = cp.get(section, i)
                        if type(v) != str:
                                v = v[0]
                        res.append(parse_with_quotes(v))
        except Exception, e:
                print e
                pass
        return res

class Rotate:
        def __init__(self, angle, xc, yc):
                self.angle = None
                self.angle_deg = 0
                self.cos = None
                self.sin = None
                self.xc = None
                self.yc = None
                self.set_angle(angle)
                self.set_center(xc, yc)

        def set_angle(self, angle):
                self.angle_deg = angle
                self.angle = math.radians(angle)
                self.cos = math.cos(self.angle)
                self.sin = math.sin(self.angle)

        def set_center(self, x, y):
                self.xc = x
                self.yc = y

        def rotate(self, x, y):
                if self.angle == 0:
                        return x, y

                xr = int(round(self.xc + (x - self.xc) * self.cos - (y - self.yc) * self.sin))
                yr = int(round(self.yc + (x - self.xc) * self.sin + (y - self.yc) * self.cos))

                return xr, yr

        def rotate_list(self, lst):
                return map(lambda a: self.rotate(a[0], a[1]), lst)

class ReadMap:
        def __init__(self, datafile, vectorsfile = None, grdfile = None, ui_update_func = None):
                self.ui_update_func = ui_update_func
                if type(datafile) == tuple:
                        #"new" map format. convert it to old one
                        ddic = {}

                        line_name_dict = dict()
                        trp = datafile[2]
                        line_no = ''
                        line_name = ''
                        for line in trp.readlines():
				line = line.strip()
				if line.lower().startswith('[line') and line.endswith(']'):
					line_no = line[1:-1]
				if line.lower().startswith('name=') and line_no != '':
					line_name = line[5:]
					if not line_name_dict.has_key(line_name):
						line_name_dict[line_name] = line_no
						line_no = ''
						line_name = ''
                        datafile[2].seek(0)
                        trp.seek(0)

                        for fd in datafile[:-1]:
                                state = None
                                for line in fd.readlines():
                                        if self.ui_update_func:
                                                self.ui_update_func()
                                        line = line.strip()
                                        if line.startswith('[') and line.endswith(']'):
                                                state = line[1:-1]
                                                if line_name_dict.has_key(state):
							state = line_name_dict[state]
                                        elif state and line:
                                                if not ddic.has_key(state):
                                                        ddic[state] = []
                                                ddic[state].append(line)
                        self.ini = StringIO.StringIO()
                        for k in ddic.iterkeys():
                                if self.ui_update_func:
                                        self.ui_update_func()
                                if k:
                                        self.ini.write('[')
                                        self.ini.write(k)
                                        self.ini.write(']\n')
                                        for line in ddic[k]:
                                                self.ini.write(line)
                                                self.ini.write("\n")
                        self.ini.write('[Vectors]\n')
                        self.ini.write(datafile[-1].read())
                        self.ini.seek(0)
                else:
                        self.ini = datafile
                self.vec = vectorsfile
                self.grdfile = grdfile
                self.Map = dict()
                self.Lines = dict()
                self.Stations = dict()
                self.Graph = list()
                self.delaytime = 'day'
                self.line_key_cache = dict()
                self.station_key_cache = dict()
                self._load()
                self._makeGraph()

                if 0: #change to 1 to produce datafile parsable by doc/findpath.c
                        print len(self.Graph)
                        for k in self.Graph:
                                print len(k)
                                for ge in k:
                                        for e in ge:
                                                print e
                        print len(self.Lines)
                        for line in self.Lines:
                                print line
                                print self.Lines[line]['delays']['day']
                                print self.Lines[line]['delays']['night']

                if self.vec:
                        self._load_vec()

        def SetDelayTime(self, dtime):
                if dtime == 1 or dtime == '1' or \
                   (type(dtime) == str and dtime.lower() == 'night'):
                        self.delaytime = 'night'
                else:
                        self.delaytime = 'day'

        def WaitLen(self, station):
                return self.Lines[self.Stations[station]['line']]['delays'][self.delaytime]

        def set_encoding(self, encoding):
                self.MapEncoding = encoding
                for l in self.Lines.keys():
                        self.Lines[l]['name'] = unicode(self.Lines[l]['name_orig'], encoding)
                for s in self.Stations.keys():
                        self.Stations[s]['name'] = unicode(self.Stations[s]['name_orig'], encoding)
                self.Map['city'] = unicode(self.Map['city_orig'], encoding)
                self.Map['name'] = unicode(self.Map['name_orig'], encoding)

                for i in xrange(len(self.Map['vectors']['instructions'])):
                        if self.Map['vectors']['instructions'][i][0] == 'text':
                                self.Map['vectors']['instructions'][i][1]['text'] = unicode(self.Map['vectors']['instructions'][i][1]['text_orig'], encoding)

        def _get_line_key(self, name):
                n = name.lower()
                if self.line_key_cache.has_key(n):
                        return self.line_key_cache[n]
                for k in self.Lines.keys():
                        sdn = self.Lines[k]['name'].lower()
                        sdno = self.Lines[k]['name_orig'].lower()
                        if (type(sdn) == type(n) and sdn == n) or \
                           (type(sdno) == type(n) and sdno == n):
                                self.line_key_cache[n] = k
                                return k
                self.line_key_cache[n] = None
                return None

        def _get_st_key(self, name, linenum):
                n = name.lower()
                if self.station_key_cache.has_key((n, linenum)):
                        return self.station_key_cache[(n, linenum)]
                for k in self.Stations.keys():
                        if self.Stations[k]['line'] == linenum:
                                sdn = self.Stations[k]['name'].lower()
                                sdno = self.Stations[k]['name_orig'].lower()
                                if (type(sdn) == type(n) and sdn == n) or \
                                   (type(sdno) == type(n) and sdno == n):
                                        self.station_key_cache[(n, linenum)] = k
                                        return k
                self.station_key_cache[(n, linenum)] = None
                return None

        def _makeGraph(self):
                lowers = 0
                uppers = 0

                station_num = 0
                for line in self.Map['lines'].keys():
                        if self.ui_update_func:
                                self.ui_update_func()
                        self.Lines[line] = dict()
                        self.Lines[line]['name_orig'] = self.Map['lines'][line]['name']
                        self.Lines[line]['color'] = self.Map['lines'][line]['color']
                        self.Lines[line]['labelscolor'] = self.Map['lines'][line]['labelscolor']
                        self.Lines[line]['diameter'] = self.Map['diameter']
                        self.Lines[line]['stations'] = list()
                        self.Lines[line]['delays'] = dict()
                        self.Lines[line]['delays']['day'] = self.Map['lines'][line]['delayday']
                        self.Lines[line]['delays']['night'] = self.Map['lines'][line]['delaynight']
                        for sn in xrange(len(self.Map['lines'][line]['names'])):
                                if self.ui_update_func:
                                        self.ui_update_func()
                                if sn < len(self.Map['lines'][line]['coordinates']):
					if self.Map['lines'][line]['coordinates'][sn][0] != 0 and \
					       self.Map['lines'][line]['coordinates'][sn][1] != 0:
						self.Lines[line]['stations'].append(station_num)
						self.Stations[station_num] = dict()
						self.Stations[station_num]['line'] = line
						self.Stations[station_num]['number'] = station_num
						self.Stations[station_num]['name_orig'] = self.Map['lines'][line]['names'][sn]
						self.Stations[station_num]['x'] = self.Map['lines'][line]['coordinates'][sn][0]
						self.Stations[station_num]['y'] = self.Map['lines'][line]['coordinates'][sn][1]
						self.Stations[station_num]['diameter'] = self.Map['diameter']
                                                #HACK: workaround [0,0,0,0] namerect for some stations on Moscow map
                                                #if (self.Map['lines'][line]['rects'][sn][0] == 0 and
                                                #    self.Map['lines'][line]['rects'][sn][1] == 0):
                                                #        self.Map['lines'][line]['rects'][sn] = [5,
                                                #                                                self.Map['lines'][line]['coordinates'][sn][1] - 12,
                                                #                                                self.Stations[station_num - 1]['namerect'][2] + self.Stations[station_num - 1]['namerect'][0] - 5,
                                                #                                                self.Stations[station_num - 1]['namerect'][3]]
						if len(self.Map['lines'][line]['rects']) <= sn:
							self.Stations[station_num]['namerect'] = [0, 0, 0, 0]
						else:
							self.Stations[station_num]['namerect'] = self.Map['lines'][line]['rects'][sn]
						self.Stations[station_num]['add'] = dict()
						self.Stations[station_num]['neighbours'] = list()
						self.Stations[station_num]['transfers'] = list()
						self.Graph.append(list())
						station_num += 1

                self.set_encoding(detect_encoding(self.Map['city_orig'] + self.Map['name_orig']))

                for node in self.Map['addnodes']:
                        if self.ui_update_func:
                                self.ui_update_func()
                        #node - 'line name', 'from station name', 'to station name'
                        ln = self._get_line_key(node[0])
                        if ln != None:
                                fs = self._get_st_key(node[1], ln)
                                if fs != None:
                                        ts = self._get_st_key(node[2], ln)
                        if ln != None and fs != None and ts != None:
                                self.Stations[fs]['add'][ts] = dict()
                                self.Stations[ts]['add'][fs] = dict()
                                atype = 'line'
                                if not node[-1].isdigit():
                                        atype = node[-1].lower()
                                self.Stations[fs]['add'][ts]['type'] = atype
                                self.Stations[ts]['add'][fs]['type'] = atype
                                self.Stations[fs]['add'][ts]['coords'] = list()
                                self.Stations[ts]['add'][fs]['coords'] = list()
                                for i in xrange((len(node) - 3) / 2):
                                        if node[3 + i].isdigit() and node[4 + i].isdigit():
                                                self.Stations[fs]['add'][ts]['coords'].append(tuple(map(int, node[3 + i * 2: 5 + i * 2])))
                                                self.Stations[ts]['add'][fs]['coords'].append(tuple(map(int, node[3 + i * 2 : 5 + i * 2])))

                #add all neighbours to graph
                for line in self.Map['lines']:
                        for i in xrange(len(self.Map['lines'][line]['neighbours'])):
                                if self.ui_update_func:
                                        self.ui_update_func()
                                sf = self._get_st_key(self.Map['lines'][line]['names'][i], line)
                                if sf == None:
                                        continue
                                for j in self.Map['lines'][line]['neighbours'][i]:
                                        st = self._get_st_key(self.Map['lines'][line]['names'][j[0]], line)
                                        if st == None:
                                                continue
                                        if not [st, j[1], line, line] in self.Graph[sf]:
                                                self.Graph[sf].append([st, j[1], line, line])
                                                self.Stations[sf]['neighbours'].append(st)
                                        if not [sf, j[1], line, line] in self.Graph[st]:
                                                self.Graph[st].append([sf, j[1], line, line])
                                                self.Stations[st]['neighbours'].append(sf)

                #add all transfers to graph
                for trans in self.Map['transfers']:
                        if len(trans) > 3:
                                sf = None
                                st = None
                                if self.ui_update_func:
                                        self.ui_update_func()
                                lf = self._get_line_key(trans[0])
                                if lf != None:
                                        lt = self._get_line_key(trans[2])
                                        if lt != None:
                                                sf = self._get_st_key(trans[1], lf)
                                                if sf != None:
                                                        st = self._get_st_key(trans[3], lt)
                                if sf != None and st != None:
                                        if len(trans) < 5:
                                                d = 0
                                        else:
                                                d = float(trans[4])
                                        if d == 0: #hack =/
                                                d = 0.01
                                        self.Graph[sf].append([st, d, lf, lt])
                                        self.Graph[st].append([sf, d, lt, lf])
                                        self.Stations[sf]['transfers'].append(st)
                                        self.Stations[st]['transfers'].append(sf)

                #workaround broken data files
                #(stations, that have jumps to herself?)
                for ki in xrange(len(self.Graph)):
                        if self.ui_update_func:
                                self.ui_update_func()
                        tor = list()
                        for i in xrange(len(self.Graph[ki]) - 1):
                                for j in xrange(i + 1, len(self.Graph[ki])):
                                        if self.Graph[ki][i][0] == self.Graph[ki][j][0] and not self.Graph[ki][j] in tor:
                                                tor.append(self.Graph[ki][j])
                        if tor:
                                #print 'Bad data:', tor
                                for r in tor:
                                        self.Graph[ki].remove(r)

                #mark all "under construction" stations
                for s in self.Stations.values():
                        if self.ui_update_func:
                                self.ui_update_func()
                        uc = True
                        for n in self.Graph[s['number']]:
                                if n[1] != 0 and n[2] == n[3]:
                                        uc = False
                                        break
                        self.Stations[s['number']]['uc'] = uc

                #...and remove them from graph
                uclist = map(lambda a: a['number'], filter(lambda a: a['uc'], self.Stations.values()))
                for i in xrange(len(self.Graph)):
                        if i in uclist:
                                self.Graph[i] = ()
                        else:
                                self.Graph[i] = filter(lambda a: a[0] not in uclist, self.Graph[i])

        def _load_vec(self):
                cp = ConfigParser.RawConfigParser()
                try:
                        cp.readfp(self.vec)
                except: #ConfigParser.readfp raises exception, when he thinks,
                        #that file is not "absolutely good" ini
                        #so fuck it
                        pass
                if self.ui_update_func:
                        self.ui_update_func()

                sec = cp.sections()
                lsec = map(lambda a: a.lower(), sec)
                for l in self.Lines.keys():
                        if self.Lines[l]['name_orig'].lower() in lsec:
                                if self.ui_update_func:
                                        self.ui_update_func()
                                section = sec[lsec.index(self.Lines[l]['name_orig'].lower())]
                                op = cp.options(section)
                                lop = map(lambda a: a.lower(), op)
                                for st in self.Lines[l]['stations']:
                                        if self.Stations[st]['name_orig'].lower() in lop:
                                                if self.ui_update_func:
                                                        self.ui_update_func()
                                                self.Stations[st]['vfile'] = cp_get(cp, section, self.Stations[st]['name_orig'], None)

        def _load(self):
                cp = ConfigParser.RawConfigParser()
                try:
                        cp.readfp(self.ini)
                except: #ConfigParser.readfp raises exception, when he thinks,
                        #that file is not "absolutely good" ini
                        #so fuck it
                        pass
                if self.ui_update_func:
                        self.ui_update_func()

                sec = cp.sections()
                lsec = map(lambda a: a.lower(), sec)
                s = sec[lsec.index('options')]
                self.Map['city_orig']     = cp_get(cp, s, 'cityname', '')
                self.Map['name_orig']     = cp_get(cp, s, 'name', '')
                self.Map['image']    = cp_get(cp, s, 'imagefilename', None)
                self.Map['diameter'] = cp_get_int(cp, s, 'stationdiameter', 16)

                self.Map['lines'] = dict()

                for nows in lsec:
                        if nows.startswith('line') and len(nows[4:]):
                                if self.ui_update_func:
                                        self.ui_update_func()
                                s = sec[lsec.index(nows)]
                                if len(cp_get(cp, s, 'coordinates', '')) == 0:
                                        continue

                                linenum = nows[4:]

                                self.Map['lines'][linenum] = dict()

                                self.Map['lines'][linenum]['name'] = cp_get(cp, s, 'name', '')
                                self.Map['lines'][linenum]['color'] = cp_get_color(cp, s, 'color', None)

                                if cp.has_option(s, 'delayday'):
                                        self.Map['lines'][linenum]['delayday'] = cp_get_int(cp, s, 'delayday', 0)
                                        self.Map['lines'][linenum]['delaynight'] = cp_get_int(cp, s, 'delaynight', 0)
                                else: #new version of Metro.ini
                                        delays = cp_get(cp, s, 'delays', '0,0').split(',')
                                        try:
                                                if len(delays) == 3: #even more new version
                                                        #FIXME: get names from "DelayNames"
                                                        self.Map['lines'][linenum]['delayday'] = float(delays[0])
                                                        self.Map['lines'][linenum]['delaynight'] = float(delays[2])
                                                else:
                                                        self.Map['lines'][linenum]['delayday'] = float(delays[0])
                                                        self.Map['lines'][linenum]['delaynight'] = float(delays[1])
                                        except:
                                                self.Map['lines'][linenum]['delayday'] = 3.0
                                                self.Map['lines'][linenum]['delaynight'] = 3.0

                                self.Map['lines'][linenum]['labelscolor'] = cp_get_color(cp, s, 'labelscolor', self.Map['lines'][linenum]['color'])

                                self.Map['lines'][linenum]['coordinates'] = split_by_comma(cp_get(cp, s, 'coordinates', ''), 2)
                                if cp_get(cp, s, 'rects', '') != '':
					self.Map['lines'][linenum]['rects'] = split_by_comma(cp_get(cp, s, 'rects', ''), 4)
                                else:
					self.Map['lines'][linenum]['rects'] = []

                                names = parse_with_quotes(cp_get(cp, s, 'stations', ''))
                                drive = parse_with_quotes(cp_get(cp, s, 'driving', ''))

                                self.Map['lines'][linenum]['names'] = list()
                                for i in xrange(len(names)):
                                        if self.ui_update_func:
                                                self.ui_update_func()
                                        if not len(names[i]):
                                                continue
                                        if names[i].find('(') != -1:
                                                nowst = names[i][:names[i].index('(')]
                                        else:
                                                nowst = names[i]
                                        #workaround duplicate station names
                                        if nowst in self.Map['lines'][linenum]['names']:
                                                nowst += ' [' + str(i) + ']'
                                        self.Map['lines'][linenum]['names'].append(nowst)

                                self.Map['lines'][linenum]['neighbours'] = list()
                                for i in xrange(len(names)):
                                        if self.ui_update_func:
                                                self.ui_update_func()
                                        if not len(names[i]):
                                                continue
                                        self.Map['lines'][linenum]['neighbours'].append([])
                                        if names[i].find('(') != -1:
                                                jnames = map(lambda a: a.lstrip('-'), parse_with_quotes(names[i][names[i].index('(') + 1:-1]))
                                                if drive[i][0] == '(':
                                                        jdrives = drive[i][1:-1].split(',')
                                                else:
                                                        if drive[i].lstrip().rstrip() == "":
                                                                drive[i] = '0'
                                                        jdrives = [drive[i]] * len(jnames)
                                                for j in xrange(len(jnames)):
                                                        if not len(jnames[j]):
                                                                continue
                                                        if j >= len(jdrives) or jdrives[j] == "":
                                                                jd = "0"
                                                        else:
                                                                jd = jdrives[j]
                                                        self.Map['lines'][linenum]['neighbours'][-1].append((self.Map['lines'][linenum]['names'].index(jnames[j]), float(jd)))
                                        else:
                                                if i < len(names) - 1:
                                                        if len(drive) < i + 1:
                                                                drive.append("1")
                                                        t = drive[i]
                                                        if drive[i][0] == '(':
                                                                arr = drive[i][1:-1].split(',')
                                                                if not arr[0]:
                                                                        t = 1
                                                                elif len(arr) == 1:
                                                                        t = arr[0]
                                                                else:
                                                                        t = arr[1]
                                                        self.Map['lines'][linenum]['neighbours'][-1].append((i + 1, float(t)))

                if 'additionalnodes' in lsec:
                        self.Map['addnodes'] = read_full_section(cp, sec[lsec.index('additionalnodes')])
                else:
                        self.Map['addnodes'] = list()
                if 'transfers' in lsec:
                        self.Map['transfers'] = read_full_section(cp, sec[lsec.index('transfers')])
                else:
                        self.Map['transfers'] = list()
                if self.ui_update_func:
                        self.ui_update_func()

                self.Map['vectors'] = dict()
                for vp in ['vectormapparameters', 'options']:
                        if vp in lsec:
                                self.Map['vectors']['lineswidth'] = cp_get_int(cp, sec[lsec.index(vp)], 'lineswidth', 0)
                                if self.Map['vectors']['lineswidth'] == 0:
                                        del self.Map['vectors']['lineswidth']
                if self.ui_update_func:
                        self.ui_update_func()

                self.Map['vectors']['instructions'] = list()
                del cp
                self.ini.seek(0)
                vecdata = list()
                insec = False
                for l in self.ini.readlines():
                        if self.ui_update_func:
                                self.ui_update_func()
                        l = l.rstrip()
                        if l.lower() == '[vectors]':
                                insec = True
                                continue
                        if not l or not insec:
                                continue
                        if l[0] == '[':
                                insec = False
                                continue
                        vecdata.append(l)

                nu = NullUpdater()
                self.Map['vectors']['instructions'] = parse_vectors(vecdata, nu)['instructions']
                #rv = ReadVec(vecdata, self.ui_update_func)
                #self.Map['vectors']['instructions'] = rv.data['instructions']

                if self.ui_update_func:
                        self.ui_update_func()

                self.grd = None
                if self.grdfile:
                        self.grd = ReadGrd(self.grdfile, self.ui_update_func)

        def get_transport_names(self, line = None, station = None):
                if self.grd:
                        return self.grd.get_transport_names(line, station)
                return None

        def get_transport_number_len(self, transport, line = None, station = None):
                if self.grd:
                        return self.grd.get_transport_number_len(transport, line, station)
                return None

        def get_transport_numbers(self, transport, line = None, station = None):
                if self.grd:
                        return self.grd.get_transport_numbers(transport, line, station)
                return None

        def get_transport_stations_list(self, transport, number, idx):
                if self.grd:
                        return self.grd.get_transport_stations_list(transport, number, idx)
                return None

        def get_transport_stations(self, transport, number, idx):
                if self.grd:
                        return self.grd.get_transport_stations(transport, number, idx)
                return None

        def get_transport_streets(self, transport, number, idx):
                if self.grd:
                        return self.grd.get_transport_streets(transport, number, idx)
                return None

        def get_transport_info(self, transport, number, idx):
                if self.grd:
                        return self.grd.get_transport_info(transport, number, idx)
                return None

        def get_transport_start_end(self, transport, number, idx):
                if self.grd:
                        return self.grd.get_transport_start_end(transport, number, idx)
                return None

class ReadGrd:
        def __init__(self, grddata, ui_update_func = None):
                self.ui_update_func = ui_update_func
                self.names = {'taxi' : u'íÁÒÛÒÕÔÎÙÅ ÔÁËÓÉ', 'tram' : u'ôÒÁÍ×ÁÉ', 'bus' : u'á×ÔÏÂÕÓÙ',
                              'train' : u'üÌÅËÔÒÉÞËÉ', 'trolleybus' : u'ôÒÏÌÌÅÊÂÕÓÙ'}
                self.data = {}
                self.statindex = {}
                self.transports = []
                cp = MyConfigParser()
                cp.readfp(grddata)

                sec = cp.sections()
                lsec = map(lambda a: a.lower(), sec)
                s = ""
                if 'options' in lsec:
                        options_name = sec[lsec.index('options')]
                        for opt in cp.options(options_name):
                                if opt.lower() == 'caption' or opt.lower() == 'stringtoadd':
                                        s += "\n".join(cp.get(options_name, opt))
                self.enc = detect_encoding(s)

                for ls in lsec:
                        if ls != 'options':
                                if self.names.has_key(ls):
                                        transport = self.names[ls]
                                else:
                                        transport = unicode(ls, self.enc)
                                self.transports.append(transport)
                                if not self.data.has_key(transport):
                                        self.data[transport] = {}
                                s = sec[lsec.index(ls)]
                                for num in cp.options(s):
                                        name = unicode(num, self.enc)
                                        self.data[transport][name] = []
                                        data_idx = 0
                                        for data in cp.get(s, num):
                                                s_data = {}
						foo = data.split('^')
						if len(foo) == 4:
							foo.append("")
						if len(foo) != 5:
							#print len(foo)
							#for fooel in foo:
							#	print "[" + unicode(fooel, self.enc) + "]",
							#print
							continue
                                                start_end, streets, stops, info, stations = foo
                                                stations = parse_with_quotes(stations)
                                                s_data['start_end'] = unicode(start_end, self.enc)
                                                s_data['streets'] = unicode(streets, self.enc)
                                                s_data['stops'] = unicode(stops, self.enc)

                                                if s_data['stops'].find('\\n') != -1: #trains
                                                        stlist = map(lambda a: a.lstrip().rstrip().replace("\\t", "\t"), s_data['stops'].split('\\n'))
                                                else:
                                                        stspl = map(lambda a: a.lstrip().rstrip(), s_data['stops'].split(','))
                                                        stlist = []
                                                        prev = ""
                                                        for st in stspl:
                                                                if st:
                                                                        st = st.lstrip().rstrip()
                                                                        if self.possible_home_number(st) or \
                                                                           (st.endswith(')') and self.possible_home_number(st[:-1].rstrip())) or \
                                                                           (st.endswith(')') and st.find('(') != -1 and self.possible_home_number(st[:st.find('(')].rstrip())):
                                                                                prev += ', ' + st
                                                                        else:
                                                                                if prev:
                                                                                        stlist.append(prev)
                                                                                prev = st
                                                        if prev:
                                                                stlist.append(prev)
                                                s_data['stops_list'] = stlist

                                                s_data['info'] = unicode(info.replace("\\n", "\n").replace("\n\n", "\n"), self.enc)
                                                for i in xrange(len(stations) / 2):
                                                        lname = unicode(stations[i * 2], self.enc).lower()
                                                        sname = unicode(stations[i * 2 + 1], self.enc).lower()
                                                        if not self.statindex.has_key((lname, sname)):
                                                                self.statindex[(lname, sname)] = {}
                                                        if not self.statindex[(lname, sname)].has_key(transport):
                                                                self.statindex[(lname, sname)][transport] = []
                                                        self.statindex[(lname, sname)][transport].append((name, data_idx))
                                                data_idx += 1
                                                self.data[transport][name].append(s_data)

        def possible_home_number(self, name):
                for n in map(lambda a: a.lstrip().rstrip(), name.split('/')):
                        if not len(n) or not n[0].isdigit() or (not n.isalnum() and not n.lower().endswith(u'É Ô.Ä.')):
                                return False
                return True

        def get_transport_names(self, line = None, station = None):
                if line == None and station == None:
                        return self.transports
                if self.statindex.has_key((line, station)):
                        tr = self.statindex[(line, station)].keys()
                        tr.sort()
                        return tr
                return None

        def get_transport_numbers(self, transport, line = None, station = None):
                if line == None and station == None and self.data.has_key(transport):
                        numbers = self.data[transport].keys()
                        numbers.sort()
                        return numbers
                elif self.statindex.has_key((line, station)) and self.statindex[(line, station)].has_key(transport):
                        numbers = self.statindex[(line, station)][transport]
                        numbers.sort()
                        return numbers
                return None

        def get_transport_number_len(self, transport, number):
                number = unicode(number)
                if self.data.has_key(transport) and self.data[transport].has_key(number):
                        return len(self.data[transport][number])
                return 0

        def _get_transport_data(self, transport, number, idx, key):
                number = unicode(number)
                if self.data.has_key(transport) and self.data[transport].has_key(number) \
                   and idx < len(self.data[transport][number]) and self.data[transport][number][idx].has_key(key):
                        return self.data[transport][number][idx][key]
                return None

        def get_transport_stations_list(self, transport, number, idx = 0):
                return self._get_transport_data(transport, number, idx, 'stops_list')

        def get_transport_stations(self, transport, number, idx = 0):
                return self._get_transport_data(transport, number, idx, 'stops')

        def get_transport_streets(self, transport, number, idx = 0):
                return self._get_transport_data(transport, number, idx, 'streets')

        def get_transport_info(self, transport, number, idx = 0):
                return self._get_transport_data(transport, number, idx, 'info')

        def get_transport_start_end(self, transport, number, idx = 0):
                return self._get_transport_data(transport, number, idx, 'start_end')

class ReadAddInfo:
        def __init__(self, infodata, encoding):
                self.name = None
                self.data = dict()
                self.enc = encoding
                cp = ConfigParser.RawConfigParser()
                try:
                        cp.readfp(infodata)
                except:
                        pass

                sec = cp.sections()
                lsec = map(lambda a: a.lower(), sec)
                if 'options' in lsec:
                        s = sec[lsec.index('options')]
                        if cp.has_option(s, 'type'):
                                v = cp.get(s, 'type')
                                if type(v) != str:
                                        v = v[0]
                                if v.lower() == '3dimage':
                                        #do not load 3d data
                                        return
                        if cp.has_option(s, 'caption'):
                                cap_name = cp.get(s, 'caption')
                                if type(cap_name) != str:
                                        cap_name = cap_name[0]
                                        cap_name = cap_name.lstrip().rstrip()
                                if (cap_name.startswith("'") and cap_name.endswith("'")) or \
                                   (cap_name.startswith('"') and cap_name.endswith('"')):
                                        cap_name = cap_name[1:-1].lstrip().rstrip()
                                self.name = unicode(cap_name, self.enc)
                                hack = False
                                if self.name.lower() == unicode("ÔÒÁÎÓÐÏÒÔ", "koi8-r"):
                                        #hack to display transport correctly
                                        hack = True
				sadd = ""
				if cp.has_option(s, 'stringtoadd'):
                                        v = cp.get(s, 'stringtoadd')
                                        if type(v) != str:
                                                v = v[0]
					sadd = v.lstrip().rstrip().lstrip("'").rstrip("'").lstrip().rstrip()
					if sadd != "":
						sadd = unicode(sadd, self.enc) + " "
                                for ls in lsec:
                                        if ls != 'options':
                                                s = sec[lsec.index(ls)]
                                                lname = unicode(s, self.enc).lower()
                                                for o in cp.options(s):
                                                        sname = unicode(o, self.enc).lower()
                                                        if not self.data.has_key((lname, sname)):
                                                                self.data[(lname, sname)] = sadd
                                                        if hack:
                                                                v = cp.get(s, o)
                                                                if type(v) != str:
                                                                        v = v[0]
                                                                self.data[(lname, sname)] += "\n".join(unicode(v, self.enc).split('.'))
                                                                #self.data[(lname, sname)] = "\n".join(unicode(cp.get(s, o), self.enc).decode("string_escape").split('.'))
                                                        else:
                                                                v = cp.get(s, o)
                                                                if type(v) != str:
                                                                        v = v[0]
                                                                self.data[(lname, sname)] += unicode(v, self.enc) #.decode("string_escape")

class DataReader:
        def __init__(self, datapath, ui_updater):
                """ WARNING! File names are case insensitive! """
                self.ui_updater = ui_updater
                self.datapath = datapath
                self.memory_cache = {}
                self.block_size = 1024
                self.ui_updater.update(["start", "init datareader " + datapath, self])
                if os.path.isdir(datapath):
                        self.zipfile = None
                elif (datapath.lower().endswith('.zip') or datapath.lower().endswith('.pmz')):
                        self.ui_updater.update(["start", "read zip " + datapath, self])
                        self.zipfile = zipfile.ZipFile(datapath, 'r')
                        for f in self.zipfile.namelist():
                                if f.lower().endswith('.pmz'):
                                        self.ui_updater.update(["start", "read nested zip " + datapath + "/" + f, self])
                                        pmzf = StringIO.StringIO(self.zipfile.read(f))
                                        self.zipfile = zipfile.ZipFile(pmzf, 'r')
                                        self.ui_updater.update(["end", "read nested zip " + datapath + "/" + f, self])
                                        break
                        self.ui_updater.update(["end", "read zip " + datapath, self])
                else:
                        raise _("Unsupported data: %s") % (datapath)
                self.ui_updater.update(["end", "init datareader " + datapath, self])

        def filelist(self):
                """ return file list """
                if self.zipfile:
                        return self.zipfile.namelist()
                return os.listdir(self.datapath)

        def have_file(self, name):
                """ check if we have a file with "name" """
                n_lower = name.lower()
                for f in self.filelist():
                        if f.lower() == n_lower:
                                return True
                return False

        def read(self, name):
                """ read contents of a file. raise exception when file not found. """
                self.ui_updater.update(["start", "read file " + name, self])
                n_lower = name.lower()
                if not self.memory_cache.has_key(n_lower):
                        for f in self.filelist():
                                if f.lower() == n_lower:
                                        if self.zipfile:
                                                self.ui_updater.update(["start", "read file from zipfile " + f, self])
                                                self.memory_cache[n_lower] = self.zipfile.read(f)
                                                self.ui_updater.update(["end", "read file from zipfile " + f, self])
                                        else:
                                                self.ui_updater.update(["start", "read from file " + f, self])
                                                fname = os.path.join(self.datapath, f)
                                                fsize = os.stat(fname)[stat.ST_SIZE]
                                                ffd = open(fname)
                                                self.memory_cache[n_lower] = ""
                                                for n in xrange(fsize / self.block_size):
                                                        self.memory_cache[n_lower] += ffd.read(self.block_size)
                                                        self.ui_updater.update()
                                                self.memory_cache[n_lower] += ffd.read(fsize % self.block_size)
                self.ui_updater.update(["end", "read file " + name, self])
                return self.memory_cache[n_lower]

def parse_vectors(vecdata, ui_updater, encoding = None):
        rt = Rotate(0, 0, 0)
        data = {}
        data['instructions'] = list()
        data['instructions'].append(['set_rgb_fg_color', (0, 0, 0)])
        ru = "ÀàÂâÑñÅåáÁ÷×óÓåÅ"
        en = "aabbcceeaabbccee"

        ui_updater.update(["start", "parse vectors", None])
        for l in vecdata:
                res = l.rstrip().lstrip().split(" ", 1)
                if len(res) == 2 and res[0][0] != ';':
                        res[0] = res[0].lower()
                        if res[0] == 'size':
                                sizes = res[1].split("x", 1)
                                if len(sizes) == 2 and sizes[0].isdigit() and sizes[1].isdigit():
                                        data['width'] = int(sizes[0])
                                        data['height'] = int(sizes[1])
                                        rt.set_center(data['width'] / 2, data['height'] / 2)
                        elif res[0] == 'brushcolor'or res[0] == 'pencolor':
                                if len(res[1]) == 6:
                                        for i in xrange(len(ru)):
                                                res[1] = res[1].replace(ru[i], en[i])
                                        try:
                                                color = map(lambda a: int(a, 16) / 256.0, (res[1][0:2], res[1][2:4], res[1][4:6]))
                                                if res[0].lower() == 'pencolor':
                                                        data['instructions'].append(['set_rgb_fg_color', color])
                                                else:
                                                        data['instructions'].append(['set_rgb_bg_color', color])
                                        except ValueError:
                                                pass
                        elif res[0] == 'angletextout' or res[0] == 'textout':
                                try:
                                        lst = parse_with_quotes(res[1])
                                        angle = 0
                                        if res[0] == 'angletextout' and len(lst) > 5:
                                                angle = int(lst[0])
                                                lst = lst[1:]
                                        if len(lst) > 4:
                                                foo = dict()
                                                foo['font'] = lst[0]
                                                foo['size'] = int(lst[1])
                                                xy = rt.rotate(int(lst[2]), int(lst[3]))
                                                foo['x'] = xy[0]
                                                foo['y'] = xy[1]
                                                if foo['y'] < 0:
                                                        foo['y'] = 0
                                                foo['text_orig'] = lst[4]
                                                if encoding:
                                                        try:
                                                                foo['text'] = unicode(lst[4], encoding)
                                                        except UnicodeDecodeError:
                                                                foo['text'] = lst[4]
                                                else:
                                                        foo['text'] = lst[4]
                                                foo['angle'] = angle - int(rt.angle_deg)
                                                data['instructions'].append(['text', foo])
                                except ValueError:
                                        pass
                        elif res[0] == 'polygon':
                                try:
                                        data['instructions'].append(['poly', split_by_comma2(res[1], 2)])
                                        data['instructions'][-1][1][1] = rt.rotate_list(data['instructions'][-1][1][1])
                                except ValueError:
                                        pass
                        elif res[0] == 'line' or res[0] == 'dashed' or res[0] == 'dased':
                                dashed = 1
                                if res[0] == 'line':
                                        dashed = 0
                                try:
                                        data['instructions'].append(['lines', dashed, split_by_comma2(res[1], 2)])
                                        data['instructions'][-1][2][1] = rt.rotate_list(data['instructions'][-1][2][1])
                                except ValueError:
                                        pass
                        elif res[0] == 'arrow':
                                try:
                                        width, points = split_by_comma2(res[1], 2)
                                        if width == 0:
                                                width = 1
                                        points = rt.rotate_list(points)
                                        data['instructions'].append(['lines', 0, [width, points]])
                                        nrt = Rotate(135, points[0][0], points[0][1])
                                        lxy = nrt.rotate(points[1][0], points[1][1])
                                        lxy = (points[1][0] - (points[1][0] - lxy[0]) / 6, points[1][1] - (points[1][1] - lxy[1]) / 6)
                                        nrt.set_angle(-135)
                                        rxy = nrt.rotate(points[1][0], points[1][1])
                                        rxy = (points[1][0] - (points[1][0] - rxy[0]) / 6, points[1][1] - (points[1][1] - rxy[1]) / 6)
                                        data['instructions'].append(['lines', 0, [width, [lxy, points[1], rxy]]])
                                except ValueError:
                                        pass
                        elif res[0] == 'stairs':
                                try:
                                        width, points = split_by_comma2(res[1], 2)
                                        if width == 0:
                                                width = 1
                                        points = rt.rotate_list(points)

                                        if abs(points[0][0] - points[2][0]) > abs(points[0][1] - points[2][1]):
                                                steps = int(round(abs(points[0][0] - points[2][0]) / 3))
                                        else:
                                                steps = int(round(abs(points[0][1] - points[2][1]) / 3))
                                        d = float(steps)
                                        dx = float((points[0][0] - points[2][0]) / d)
                                        dy = float((points[0][1] - points[2][1]) / d)

                                        data['instructions'].append(['set_rgb_bg_color', (0.8125, 0.8125, 0.8125)])
                                        points.insert(2, (points[2][0] + points[1][0] - points[0][0], points[2][1] + points[1][1] - points[0][1]))
                                        data['instructions'].append(['poly', [width, points]])
                                        l = list()

                                        for i in xrange(steps):
                                                x1 = int(round(points[0][0] - dx * i))
                                                y1 = int(round(points[0][1] - dy * i))
                                                x2 = int(round(points[1][0] - dx * i))
                                                y2 = int(round(points[1][1] - dy * i))
                                                data['instructions'].append(['lines', 0, [width, ((x1, y1), (x2, y2))]])
                                except ValueError:
                                        pass
                        elif res[0] == 'angle':
                                try:
                                        rt.set_angle(-int(res[1]))
                                except ValueError:
                                        pass
                        elif res[0].lower() == 'spotcircle':
                                #print 'sc', res[1]
                                pass
                        elif res[0].lower() == 'spotrect':
                                #print 'sr', res[1]
                                pass
                        elif res[0].lower() == 'spline':
                                try:
                                        width, points = split_by_comma2(res[1], 2)
                                        points = rt.rotate_list(points)
                                        data['instructions'].append(['spline', width, points])
                                except ValueError:
                                        pass
                        elif res[0].lower() == 'ellipse':
                                try:
					width, points = split_by_comma2(res[1], 2)
					points = rt.rotate_list(points)
					angle = round(-float(rt.angle_deg))
					data['instructions'].append(['ellipse', width, points, angle])
                                except ValueError:
                                        pass
                        elif res[0].lower() == 'image':
                                #not supported
                                pass
                        elif res[0].lower() == 'opaque':
                                #not supported
                                pass
                        elif res[0].lower() == 'railway':
                                #not supported
                                pass
                        else:
                                print _('unknown instruction:'), res[0]
                ui_updater.update()
        ui_updater.update(["end", "parse vectors", None])
        return data

class pMetroParser:
        def __init__(self, basename, reader, ui_updater, encoding = None):
                self.DEFAULT_COST = 3.0
                self.basename = basename
                self.reader = reader
                self.ui_updater = ui_updater
                self.encoding = encoding or "cp1251"
                self.num_delays = 2

        def get_map_info(self):
                MapInfo = {}
                try:
                        self.ui_updater.update(["start", "parse cty " + self.basename + '.cty', self])
                        cp = MyConfigParser()
                        cp.readfp(StringIO.StringIO(self.reader.read(self.basename + '.cty')))
                        MapInfo['name'] = self.encode(cp.get_nocase_first('options', 'name') or self.basename)
                        MapInfo['city_name'] = self.encode(cp.get_nocase_first('options', 'cityname') or MapInfo['name'])
                        MapInfo['country'] = self.encode(cp.get_nocase_first('options', 'country') or "")
                        MapInfo['rus_name'] = self.encode(cp.get_nocase_first('options', 'rusname') or MapInfo['name'])
                        MapInfo['delay_names'] = strict_split(self.encode(cp.get_nocase_first('options', 'DelayNames') or _("Day, Night")), ',')
                        self.num_delays = len(MapInfo['delay_names'])
                        MapInfo['info'] = self.encode("\n".join(cp.get_nocase('options', 'mapauthors') or []).replace("\\n", "\n").replace("\\t", "\t"))
                        self.ui_updater.update(["end", "parse cty " + self.basename + '.cty', self])
                except:
                        pass
                return MapInfo

        def get_vectors(self):
                return parse_vectors(self.reader.read(self.basename + '.vec'), ui_updater, self.encoding)

        def encode(self, string):
                return unicode(string, self.encoding or "utf8")

        def __get_st_name__(self, name):
                par_pos = name.find("(")
                if par_pos != -1:
                        name = name[:par_pos].lstrip().rstrip()
                return self.encode(name), name, par_pos

        def __set_st_name__(self, name, new_name):
                par_pos = name.find("(")
                if par_pos != -1:
                        return new_name + " " + name[par_pos:]
                return new_name

        def get_data(self):
                MapData = {}
                MapData["lines"] = {}
                MapData["stations"] = {}
                MapData["line_types"] = {}
                self.ui_updater.update(["start", "parse Metro.trp", self])
                self.st_dict = {}
                self.metro_stations = []
                cp = MyConfigParser()
                cp.readfp(StringIO.StringIO(self.reader.read('Metro.trp')))
                station_num = 0
                for section in cp.sections():
                        if section.lower().startswith("line"):
                                ln = cp.get_nocase(section, 'name')
                                if ln:
                                        line_num = ln[0]
                                else:
                                        line_num = section
                                self.ui_updater.update(["start", "parse line " + line_num, self])
                                MapData["lines"][line_num] = {}
                                names = cp.get_nocase(section, 'names')
                                driving = cp.get_nocase(section, 'driving')
                                if names and driving:
                                        MapData["lines"][line_num]['name'] = self.encode((cp.get_nocase(section, 'name') or [line_num])[0])
                                        MapData["lines"][line_num]['type'] = self.encode((cp.get_nocase(section, 'type') or [""])[0]) or u"íÅÔÒÏ"
                                        if not MapData["lines"][line_num]['type'].lower() in MapData["line_types"].keys():
                                                MapData["line_types"][MapData["lines"][line_num]['type'].lower()] = [line_num,]
                                        else:
                                                MapData["line_types"][MapData["lines"][line_num]['type'].lower()].append(line_num)

                                        MapData["lines"][line_num]['linemap'] = (cp.get_nocase(section, 'linemap') or [""])[0]
                                        MapData["lines"][line_num]['aliases'] = (cp.get_nocase(section, 'aliases') or [""])[0]
                                        is_metro = False
                                        if MapData["lines"][line_num]['type'] == u"íÅÔÒÏ":
                                                is_metro = True
                                        try:
                                                if cp.get_nocase(section, 'delayday'):
                                                        #old format
                                                        delays = [float(cp.get_nocase(section, delay_name)[0]) for delay_name  in ['delayday', 'delaynight']]
                                                else:
                                                        delays = [float(delay) for delay in cp.get_nocase(section, 'delays')[0].split(',')]
                                                if len(delays) != self.num_delays:
                                                        raise Exception("invalid delays")
                                        except Exception, e:
                                                #use "default" delays
                                                delays = [self.DEFAULT_COST] * self.num_delays
                                        MapData["lines"][line_num]['delays'] = delays

                                        MapData["lines"][line_num]['stations'] = []

                                        names = parse_with_quotes(names[0])
                                        driving = parse_with_quotes(driving[0])

                                        line_stations = {}
                                        for i in xrange(len(names)):
                                                #note, that st_dict have ORIGINAL name, not encoded to utf8
                                                name, orig_name, par_pos = self.__get_st_name__(names[i])
                                                if line_stations.has_key(orig_name):
                                                        line_stations[orig_name] += 1
                                                        orig_name = orig_name + " [%d]" % (line_stations[orig_name])
                                                        names[i] = self.__set_st_name__(names[i], orig_name)
                                                else:
                                                        line_stations[orig_name] = 1
                                                self.st_dict[(line_num, orig_name)] = station_num
                                                MapData["stations"][station_num] = {}
                                                MapData["stations"][station_num]["name"] = name
                                                MapData["stations"][station_num]["line"] = line_num
                                                MapData["lines"][line_num]['stations'].append(station_num)
                                                if is_metro:
                                                        self.metro_stations.append(station_num)
                                                station_num += 1

                                        for i in xrange(len(names)):
                                                name, orig_name, par_pos = self.__get_st_name__(names[i])
                                                st_number = self.st_dict[(line_num, orig_name)]
                                                neighbours = {}
                                                neighbours[0] = {}
                                                neighbours[1] = {}
                                                cost = -1
                                                costs = None
                                                if i < len(driving) and driving[i].startswith("("):
                                                        costs = parse_with_quotes(driving[i][1:-1])
                                                if par_pos == -1:
                                                        #we can move from that station only to previous/next stations
                                                        if i > 0:
                                                                target_st_num = self.st_dict[(line_num, self.__get_st_name__(names[i - 1])[1])]
                                                                neighbours[1][target_st_num] = {}
                                                                try:
                                                                        if costs:
                                                                                cost = float(costs[1])
                                                                        elif i <= len(driving):
                                                                                cost = MapData["stations"][target_st_num]["neighbours"][0][st_number]['cost']
                                                                except:
                                                                        pass
                                                                neighbours[1][target_st_num]['cost'] = cost
                                                                neighbours[1][target_st_num]['chdir'] = False
                                                        if i < len(names) - 1:
                                                                target_st_num = self.st_dict[(line_num, self.__get_st_name__(names[i + 1])[1])]
                                                                neighbours[0][target_st_num] = {}
                                                                try:
                                                                        if costs:
                                                                                cost = float(costs[0])
                                                                        else:
                                                                                cost = float(driving[i])
                                                                except:
                                                                        pass
                                                                neighbours[0][target_st_num]['cost'] = cost
                                                                neighbours[0][target_st_num]['chdir'] = False
                                                else:
                                                        #we can move from that station only to stations enumerated
                                                        #in ()
                                                        current_neighbours = parse_with_quotes(names[i][par_pos + 1:-1])
                                                        if not costs:
                                                                costs = [self.DEFAULT_COST] * len(current_neighbours)
                                                        else:
                                                                costs.reverse()
                                                        step = 1
                                                        for neighbour in current_neighbours:
                                                                try:
                                                                        cost = float(costs.pop())
                                                                except:
                                                                        cost = -1
                                                                if neighbour:
                                                                        change_direction = False
                                                                        if neighbour.startswith("-"):
                                                                                neighbour = neighbour[1:]
                                                                                change_direction = True
                                                                        try:
                                                                                target_st_num = self.st_dict[(line_num, neighbour)]
                                                                                neighbours[step][target_st_num] = {}
                                                                                neighbours[step][target_st_num]['cost'] = cost
                                                                                neighbours[step][target_st_num]['chdir'] = change_direction
                                                                        except Exception, e:
                                                                                print 'Bad datafile. Unknown neighbour for station "%s" on line "%s": %s' % (name, MapData["lines"][line_num]['name'], self.encode(neighbour))
                                                                step = 1 - step
                                                MapData["stations"][st_number]["neighbours"] = neighbours

                                self.ui_updater.update(["end", "parse line " + line_num, self])


                for section in cp.sections():
                        if section.lower() == "transfers":
                                self.ui_updater.update(["start", "parse transfers ", self])
                                for o in cp.options(section):
                                        #print "[%s]" % (self.encode(o))
                                        for tr in cp.get(section, o):
                                                if type(tr) != str:
                                                        tr = tr[0]
                                                trs = parse_with_quotes(tr)
                                                if len(trs) > 3:
                                                        try:
                                                                st_fr = self.st_dict[(trs[0], trs[1])]
                                                                st_to = self.st_dict[(trs[2], trs[3])]
                                                                cost = self.DEFAULT_COST
                                                                if len(trs) > 4:
                                                                        cost = float(trs[4])
                                                                #len(trs) == 6 and trs[5] == 'invisible' - ?
                                                                MapData["stations"][st_fr]["neighbours"][0][st_to] = {'cost': cost, 'chdir' : False}
                                                                MapData["stations"][st_fr]["neighbours"][1][st_to] = {'cost': cost, 'chdir' : False}
                                                                MapData["stations"][st_to]["neighbours"][0][st_fr] = {'cost': cost, 'chdir' : False}
                                                                MapData["stations"][st_to]["neighbours"][1][st_fr] = {'cost': cost, 'chdir' : False}
                                                        except:
                                                                print 'Bad datafile. Transfer on unknown stations:', self.encode(tr)
                                self.ui_updater.update(["end", "parse transfers ", self])

                self.ui_updater.update(["end", "parse Metro.trp", self])
                return MapData

class NullUpdater:
        def update(self, info = None):
                pass

class MetroMap:
        def __init__(self, datapath, ui_updater = None, encoding = None):
                self.parser = None
                self.encoding = encoding
                self.ui_updater = ui_updater or NullUpdater()
                self.reader = DataReader(datapath, self.ui_updater)

                self.MapInfo = None
                self.Vectors = None
                self.MapData = None
                self.current_delay = 0

                for f in self.reader.filelist():
                        f_lower = f.lower()
                        if f_lower.endswith('.cty'):
                                self.parser = pMetroParser(f_lower[:f_lower.find('.cty')], self.reader, self.ui_updater, self.encoding)
                                break
                        elif f_lower == 'metro.ini':
                                break
                        elif f_lower == 'metromap.data':
                                break

        def find_stations(self, station_name, line = None, type = None):
                if self.parser:
                        self._load_map_data()
                        station_name = station_name.lower()
                        slist = []
                        if station_name.isdigit() and self.MapData["stations"].has_key(int(station_name)):
                                return [int(station_name)]
                        if line != None:
                                if self.MapData["lines"].has_key(line):
                                        slist = self.MapData["lines"][line]['stations']
                        elif type != None:
                                if self.MapData["line_types"].has_key(type.lower()):
                                        for line in self.MapData["line_types"][type.lower()]:
                                                slist += self.MapData["lines"][line]['stations']
                        else:
                                slist = self.MapData["stations"].keys()
                        return [st for st in slist if self.MapData["stations"][st]['name'].lower().find(station_name) != -1]
                return []

        def get_station_line(self, station):
                return self.MapData['stations'][station]['line']

        def get_line_name(self, line):
                return self.MapData['lines'][line]['name']

        def get_line_type(self, line):
                return self.MapData['lines'][line]['type']

        def get_station_name(self, station):
                return self.MapData['stations'][station]['name']

        def get_wait(self, station):
                return self.MapData['lines'][self.MapData['stations'][station]['line']]['delays'][self.current_delay]

        def get_neighbours(self, station):
                return self.MapData['stations'][station]['neighbours']

        def get_vectors(self):
                if self.parser:
                        self._load_map_info()
                        if self.Vectors == None:
                                self.Vectors = self.parser.get_vectors()
                        return self.Vectors
                return {}

        def get_stations(self):
                if self.parser:
                        self._load_map_data()
                        return self.MapData["stations"].keys()
                return []

        def get_lines(self, type = None):
                if self.parser:
                        self._load_map_data()
                        if type == None:
                                return self.MapData["lines"].keys()
                        elif self.MapData["line_types"].has_key(type.lower()):
                                return self.MapData["line_types"][type.lower()]
                return []

        def get_line_types(self):
                if self.parser:
                        self._load_map_data()
                        return self.MapData["line_types"].keys()
                return []

        def _load_map_info(self):
                if self.parser and self.MapInfo == None:
                        self.MapInfo = self.parser.get_map_info()

        def _load_map_data(self):
                self._load_map_info()
                if self.parser and self.MapData == None:
                        self.MapData = self.parser.get_data()

        def __getattr__(self, name):
                """ some sort of hack to implement get_map_ methods using parser's MapInfo dictionary """
                if name.startswith("get_map_"):
                        if self.parser:
                                self._load_map_info()
                                if self.MapInfo.has_key(name[8:]):
                                        return lambda: self.MapInfo[name[8:]]
                        return lambda: None
                raise AttributeError, "attribute %s not exist " % (name)

if __name__ == '__main__':
        import sys, codecs
        _ = str

        class ui_updater:
                def __init__(self):
                        self.alist = []

                def update(self, info = None):
                        if info:
                                self.alist.append(info + [time.time()])

                def find_start(self, pos):
                        for i in xrange(pos - 1, -1, -1):
                                if self.alist[i][1] == self.alist[pos][1] and self.alist[i][2] == self.alist[pos][2]:
                                        return i
                        return None

                def summary(self):
                        spaces = 0
                        for n in xrange(len(self.alist)):
                                if self.alist[n][0] == "start":
                                        print "%s->%s" % (" " * spaces, self.alist[n][1])
                                        spaces += 2
                                elif self.alist[n][0] == "end":
                                        i = self.find_start(n)
                                        if i == None:
                                                print "alien end:", self.alist[n][1]
                                        else:
                                                for c in xrange(i, n):
                                                        if self.alist[c][0] == 'start':
                                                                spaces -= 2
                                                        elif self.alist[c][0] == 'end':
                                                                spaces += 2
                                                print "%s<-%s (%f)" % (" " * spaces, self.alist[n][1], self.alist[n][3] - self.alist[i][3])

        def unicodize(what):
                if what:
                        return unicode(what, locale.getpreferredencoding())
                return what

        def print_path(p, metro):
                for st in p:
                        print "%s (%s) [%s]" % (metro.get_station_name(st), metro.get_line_name(metro.get_station_line(st)), st)
                print
                return
                if not p or not p[0] or not len(p[0]) == len(p[1]):
                        print "Bad path:", p
                else:
                        if p[1][0]:
                                print 'Waiting %s min' % (str(p[1][0]).rstrip('.0'))
                        line = metro.get_station_line(p[0][0])
                        print 'Starting on line %s' % (metro.get_line_name(line))
                        for i in xrange(len(p[0]) - 1):
                                n_line = metro.get_station_line(p[0][i + 1])
                                if n_line != line:
                                        line = n_line
                                        print 'Transfering to line %s [%s min]' % (metro.get_line_name(line), str(p[1][i + 1][0]).rstrip('.0'))
                                        print 'Waiting %s min' % (str(p[1][i + 1][1]).rstrip('.0'))
                                        print '%s' % (metro.get_station_name(p[0][i + 1]))
                                else:
                                        print '%s [%s min]' % (metro.get_station_name(p[0][i + 1]), str(p[1][i + 1]).rstrip('.0'))

        #make stdout redirect work in same encoding as terminal =/
        sys.stdout = codecs.lookup(locale.getpreferredencoding()).streamwriter(sys.stdout)

        dlist = []
        uu = ui_updater()

        from optparse import OptionParser
        oparser = OptionParser()
        oparser.add_option("-m", "--map", dest = "map",  action="store", default = "../data/Moscow", help = "load map from MAP")
        oparser.add_option("-f", "--from", dest = "fr",  action="store", default = None, help = "drive from FR")
        oparser.add_option("-t", "--to", dest = "to",  action="store", default = None, help = "drive to TO")
        oparser.add_option("-l", "--list-types", dest = "list_type",  action="store_true", help = "list line types")
        oparser.add_option("-s", "--list-stations", dest = "list_stations",  action="store_true", help = "list all stations")
        oparser.add_option("-T", "--type", dest = "type",  action="store", default = None, help = "drive on line type TYPE")

        if len(sys.argv) == 1:
                oparser.print_help()
                sys.exit(0)

        (options, args) = oparser.parse_args()
        #convert all strings to unicode
        options.type = unicodize(options.type)
        options.fr = unicodize(options.fr)
        options.to = unicodize(options.to)

        metro = MetroMap(options.map, uu)
        if options.list_type:
                for t in metro.get_line_types():
                        print t
                sys.exit(0)

        if options.list_stations:
                for st in metro.find_stations("", type = options.type):
                        print "%d %s (%s)" % (st, metro.get_station_name(st), metro.get_line_name(metro.get_station_line(st)))
                sys.exit(0)

        if not options.fr or not options.to:
                print 'please specify "from" and "to" stations'
                sys.exit(1)

        import FindPath
        finder = FindPath.PathFinder(metro, 10)

        for st_f in metro.find_stations(options.fr, type = options.type):
                for st_t in metro.find_stations(options.to, type = options.type):
                        line_f = metro.get_station_line(st_f)
                        line_t = metro.get_station_line(st_t)
                        print 'Driving from %s (%s) to %s (%s) [%s]:' % (
                              metro.get_station_name(st_f),
                              metro.get_line_name(line_f),
                              metro.get_station_name(st_t),
                              metro.get_line_name(line_t),
                              options.type)
                        p = finder.find(st_f, st_t)
                        for path in p:
                                print_path(path, metro)
        #finder.find(0, 1)
        #print metro.get_wait(1234)
        #for k in metro.MapData['stations'].keys():
        #        try:
        #                for n in metro.MapData['stations'][k]['neighbours'][0].keys():
        #                        if metro.MapData['stations'][k]['neighbours'][0][n]['chdir']:
        #                                print n
        #        except:
        #                print k, metro.MapData['stations'][k]
        #                break
        #for d in dlist:
        #        print "[%s] [%s]: %s (%s/%s)" % (d[0], d[1], d[2].get_map_name(), d[2].get_map_country(), d[2].get_map_rus_name())
        #        if d[2].get_map_name() == "Moscow":
        #                for k in d[2].parser.MapData["lines"]["1"].keys():
        #                        print "%s: %s" % (k, d[2].parser.MapData["lines"]["1"][k])

        #uu.summary()
