/*
* Copyright (C) 2009.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package uk.me.parabola.imgfmt.app.net;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFile;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.lbl.LBLFileReader;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* Read the NET file.
*/
public class NETFileReader extends ImgFile {
private final NETHeader netHeader = new NETHeader();
// To begin with we only need LBL offsets.
private final Map<Integer, Integer> offsetLabelMap = new HashMap<Integer, Integer>();
private List<Integer> offsets;
private List<City> cities;
private int citySize;
private List<Zip> zips;
private int zipSize;
private LBLFileReader labels;
public NETFileReader(ImgChannel chan) {
setHeader(netHeader);
setReader(new BufferedImgFileReader(chan));
netHeader.readHeader(getReader());
readLabelOffsets();
}
/**
* Get the label offset, given the NET offset.
* @param netOffset An offset into NET 1, as found in the road entries in
* RGN for example.
* @return The offset into LBL as found in NET 1.
*/
public int getLabelOffset(int netOffset) {
Integer off = offsetLabelMap.get(netOffset);
if (off == null)
return 0;
else
return off;
}
/**
* Get the list of roads from the net section.
*
* Saving the bare minimum that is needed, please improve.
* @return A list of RoadDefs. Note that currently not everything is
* populated in the road def so it can't be written out as is.
*/
public List<RoadDef> getRoads() {
ImgFileReader reader = getReader();
int start = netHeader.getRoadDefinitionsStart();
List<RoadDef> roads = new ArrayList<RoadDef>();
int record = 0;
for (int off : offsets) {
reader.position(start + off);
RoadDef road = new RoadDef(++record, off, null);
readLabels(reader, road);
byte netFlags = reader.get();
/*int len =*/ reader.getu3();
int[] counts = new int[24];
int level = 0;
while (level < 24) {
int n = reader.get();
counts[level++] = (n & 0x7f);
if ((n & 0x80) != 0)
break;
}
for (int i = 0; i < level; i++) {
int c = counts[i];
for (int j = 0; j < c; j++) {
/*byte b =*/ reader.get();
/*char sub =*/ reader.getChar();
}
}
if ((netFlags & RoadDef.NET_FLAG_ADDRINFO) != 0) {
char flags2 = reader.getChar();
int zipFlag = (flags2 >> 10) & 0x3;
int cityFlag = (flags2 >> 12) & 0x3;
int numberFlag = (flags2 >> 14) & 0x3;
IntArrayList indexes = new IntArrayList();
fetchZipCityIndexes(reader, zipFlag, zipSize, indexes);
for (int index : indexes){
road.addZipIfNotPresent(zips.get(index));
}
fetchZipCityIndexes(reader, cityFlag, citySize, indexes);
for (int index : indexes){
road.addCityIfNotPresent(cities.get(index));
}
fetchNumber(reader, numberFlag);
}
if ((netFlags & RoadDef.NET_FLAG_NODINFO) != 0) {
int nodFlags = reader.get();
int nbytes = nodFlags & 0x3;
if (nbytes > 0) {
/*int nod = */reader.getUint(nbytes+1);
}
}
roads.add(road);
}
return roads;
}
/**
* Parse a list of zip/city indexes.
* @param reader
* @param flag
* @param size
* @param indexes
*/
private void fetchZipCityIndexes(ImgFileReader reader, int flag, int size, IntArrayList indexes) {
indexes.clear();
if (flag == 2) {
// fetch city/zip index
int ind = (size == 2)? reader.getChar(): (reader.get() & 0xff);
if (ind != 0)
indexes.add(ind-1);
} else if (flag == 3) {
// there is no item
} else if (flag == 0) {
int n = reader.get() & 0xff;
parseList(reader, n, size, indexes);
} else if (flag == 1) {
int n = reader.getChar();
parseList(reader, n, size, indexes);
} else {
assert false : "flag is " + flag;
}
}
private void parseList(ImgFileReader reader, int n, int size,
IntArrayList indexes) {
long endPos = reader.position() + n;
int node = 0; // not yet used
while (reader.position() < endPos) {
int initFlag = reader.get() & 0xff;
int skip = (initFlag & 0x1f);
initFlag >>= 5;
if (initFlag == 7) {
// Need to read another byte
initFlag = reader.get() & 0xff;
skip |= ((initFlag & 0x1f) << 5);
initFlag >>= 5;
}
node += skip + 1;
int right = 0, left = 0;
if (initFlag == 0) {
right = left = getCityOrZip(reader, size, endPos);
} else if ((initFlag & 0x4) != 0) {
if ((initFlag & 1) == 0)
right = 0;
if ((initFlag & 2) == 0)
left = 0;
} else {
if ((initFlag & 1) != 0)
left = getCityOrZip(reader, size, endPos);
if ((initFlag & 2) != 0)
right = getCityOrZip(reader, size, endPos);
}
if (left > 0)
indexes.add(left - 1);
if (right > 0 && left != right)
indexes.add(right - 1);
}
}
private int getCityOrZip(ImgFileReader reader, int size, long endPos) {
if (reader.position() > endPos - size) {
assert false : "ERRROR overflow";
return 0;
}
int cnum;
if (size == 1)
cnum = reader.get() & 0xff;
else if (size == 2)
cnum = reader.getChar();
else {
assert false : "unexpected size value" + size;
return 0;
}
return cnum;
}
/**
* Fetch a block of numbers.
* @param reader The reader.
* @param numberFlag The flag that says how the block is formatted.
*/
private void fetchNumber(ImgFileReader reader, int numberFlag) {
int n = 0;
if (numberFlag == 0) {
n = reader.get();
} else if (numberFlag == 1) {
n = reader.getChar();
} else if (numberFlag == 3) {
// There is no block
return;
} else {
// Possible but don't know what to do in this context
assert false;
}
if (n > 0)
reader.get(n);
}
private void readLabels(ImgFileReader reader, RoadDef road) {
for (int i = 0; i < 4; i++) {
int lab = reader.getu3();
Label label = labels.fetchLabel(lab & 0x7fffff);
road.addLabel(label);
if ((lab & 0x800000) != 0)
break;
}
}
/**
* The first field in NET 1 is a label offset in LBL. Currently we
* are only interested in that to convert between a NET 1 offset and
* a LBL offset.
*/
private void readLabelOffsets() {
ImgFileReader reader = getReader();
offsets = readOffsets();
int start = netHeader.getRoadDefinitionsStart();
for (int off : offsets) {
reader.position(start + off);
int labelOffset = reader.getu3();
// TODO what if top bit is not set?, there can be more than one name and we will miss them
offsetLabelMap.put(off, labelOffset & 0x7fffff);
}
}
/**
* NET 3 contains a list of all the NET 1 record start positions. They
* are in alphabetical order of name. So read them in and sort into
* memory address order.
* @return A list of start offsets in NET 1, sorted by increasing offset.
*/
private List<Integer> readOffsets() {
int start = netHeader.getSortedRoadsStart();
int end = netHeader.getSortedRoadsEnd();
ImgFileReader reader = getReader();
reader.position(start);
List<Integer> offsets = new ArrayList<Integer>();
while (reader.position() < end) {
int net1 = reader.getu3();
// The offset is stored in the bottom 22 bits. The top 2 bits are an index into the list
// of lbl pointers in the net1 entry. Since we pick up all the labels at a particular net1
// entry we only need one of the offsets so pick the first one.
int idx = (net1 >> 22) & 0x3;
if (idx == 0)
offsets.add((net1 & 0x3fffff) << netHeader.getRoadShift());
}
// Sort in address order in the hope of speeding up reading.
Collections.sort(offsets);
return offsets;
}
public void setCities(List<City> cities) {
this.cities = cities;
this.citySize = cities.size() > 255? 2: 1;
}
public void setZips(List<Zip> zips) {
this.zips = zips;
this.zipSize = zips.size() > 255? 2: 1;
}
public void setLabels(LBLFileReader labels) {
this.labels = labels;
}
}