//# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
//# License for more details.
//#
//# You should have received a copy of the GNU Library General Public License
//# along with this library; if not, write to the Free Software Foundation,
//# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#

#include <imageanalysis/IO/RegionTextParser.h>

#include <casacore/casa/IO/RegularFileIO.h>
#include <casacore/coordinates/Coordinates/DirectionCoordinate.h>
#include <casacore/coordinates/Coordinates/SpectralCoordinate.h>
#include <imageanalysis/Annotations/AnnAnnulus.h>
#include <imageanalysis/Annotations/AnnCenterBox.h>
#include <imageanalysis/Annotations/AnnCircle.h>
#include <imageanalysis/Annotations/AnnEllipse.h>
#include <imageanalysis/Annotations/AnnLine.h>
#include <imageanalysis/Annotations/AnnPolygon.h>
#include <imageanalysis/Annotations/AnnRectBox.h>
#include <imageanalysis/Annotations/AnnRotBox.h>
#include <imageanalysis/Annotations/AnnSymbol.h>
#include <imageanalysis/Annotations/AnnText.h>
#include <imageanalysis/Annotations/AnnVector.h>
#include <imageanalysis/IO/ParameterParser.h>

#include <casacore/measures/Measures/MCDirection.h>
#include <casacore/measures/Measures/MDirection.h>
#include <casacore/measures/Measures/VelocityMachine.h>

#include <iomanip>
#include <casacore/casa/BasicSL/STLIO.h>

#define _ORIGIN "RegionTextParser::" + String(__FUNCTION__) + ": "

using namespace casacore;
namespace casa {

const Int RegionTextParser::CURRENT_VERSION = 0;
const Regex RegionTextParser::MAGIC("^#CRTF");

const String RegionTextParser::sOnePair = "[[:space:]]*\\[[^\\[,]+,[^\\[,]+\\][[:space:]]*";
const String RegionTextParser::bTwoPair = "\\[" + sOnePair
        + "," + sOnePair;
// explicit onePair at the end because that one should not be followed by a comma
const String RegionTextParser::sNPair = "\\[(" + sOnePair
        + ",)+" + sOnePair + "\\]";
const Regex RegionTextParser::startOnePair("^" + sOnePair);
const Regex RegionTextParser::startNPair("^" + sNPair);

RegionTextParser::RegionTextParser(
    const String& filename, const CoordinateSystem& csys,
    const IPosition& imShape,
    const Int requireAtLeastThisVersion,
    const String& prependRegion,
    const String& globalOverrideChans, const String& globalOverrrideStokes,
    Bool verbose, bool requireImageRegion
) : _csys(csys), _log(new LogIO()), _currentGlobals(),
    _lines(), _globalKeysToApply(), _fileVersion(-1), _imShape(imShape),
    _regions(0), _verbose(verbose) {
    RegularFile file(filename);
    if (! file.exists()) {
        throw AipsError(
            _ORIGIN + "File " + filename + " does not exist."
        );
    }
    if (! file.isReadable()) {
        throw AipsError(
            _ORIGIN + "File " + filename + " is not readable."
        );
    }
    if (! _csys.hasDirectionCoordinate()) {
        throw AipsError(
            _ORIGIN
            + "Coordinate system does not have a direction coordinate"
        );
    }
    _setInitialGlobals();
    _setOverridingChannelRange(globalOverrideChans);
    _setOverridingCorrelations(globalOverrrideStokes);
    RegularFileIO fileIO(file);
    Int bufSize = 4096;
    std::unique_ptr<char> buffer(new char[bufSize]);
    int nRead;
    String contents;
    if (! prependRegion.empty()) {
        contents = prependRegion + "\n";
    }
    while ((nRead = fileIO.read(bufSize, buffer.get( ), false)) == bufSize) {
        String chunk(buffer.get( ), bufSize);
        if (_fileVersion < 0) {
            _determineVersion(chunk, filename, requireAtLeastThisVersion);
        }
        contents += chunk;
    }
    // get the last chunk
    String chunk(buffer.get( ), nRead);
    if (_fileVersion < 0) {
        _determineVersion(chunk, filename, requireAtLeastThisVersion);
    }
    contents += chunk;
    _parse(contents, filename, requireImageRegion);
}

RegionTextParser::RegionTextParser(
    const CoordinateSystem& csys, const IPosition& imShape,
    const String& text, const String& prependRegion,
    const String& globalOverrideChans, const String& globalOverrrideStokes,
    Bool verbose, Bool requireImageRegion
) : _csys(csys), _log(new LogIO()), _currentGlobals(), _lines(),
    _globalKeysToApply(), _fileVersion(-1), _imShape(imShape), _regions(0),
    _verbose(verbose) {
    if (! _csys.hasDirectionCoordinate()) {
        throw AipsError(
            _ORIGIN + "Coordinate system has no direction coordinate"
        );
    }
    _setInitialGlobals();
    _setOverridingChannelRange(globalOverrideChans);
    _setOverridingCorrelations(globalOverrrideStokes);
    _parse(prependRegion.empty() ? text : prependRegion + "\n" + text, "", requireImageRegion);
}

RegionTextParser::~RegionTextParser() {}

Int RegionTextParser::getFileVersion() const {
    ThrowIf(
        _fileVersion < 0,
        "File version not associated with simple text strings"
    );
    return _fileVersion;
}

vector<AsciiAnnotationFileLine> RegionTextParser::getLines() const {
    return _lines;
}

void RegionTextParser::_determineVersion(
    const String& chunk, const String& filename,
    const Int requireAtLeastThisVersion
) {
    *_log << LogOrigin("RegionTextParser", __FUNCTION__);
    if (_fileVersion >= 0) {
        // already determined
        return;
    }
    ThrowIf(
        ! chunk.contains(MAGIC),
        _ORIGIN + "File " + filename
        + " does not contain CASA region text file magic value"
    );
    Regex version(MAGIC.regexp() + "v[0-9]+");
    if (chunk.contains(version)) {
        const auto vString = chunk.substr(6);
        auto done = false;
        auto count = 1;
        auto oldVersion = -2000;
        while (! done) {
            try {
                _fileVersion = String::toInt(vString.substr(0, count));
                ++count;
                if (_fileVersion == oldVersion) {
                    done = true;
                }
                else {
                    oldVersion = _fileVersion;
                }
            }
            catch (const AipsError&) {
                done = true;
            }
        }
        if (_fileVersion < requireAtLeastThisVersion) {
            *_log << _ORIGIN << "File version " << _fileVersion
                << " is less than required version "
                << requireAtLeastThisVersion << LogIO::EXCEPTION;
        }
        if (_fileVersion > CURRENT_VERSION) {
            *_log << _ORIGIN << "File version " << _fileVersion
                << " is greater than the most recent version of the spec ("
                << CURRENT_VERSION
                << "). Did you bring this file with you when you traveled "
                << "here from the future perhaps? Unfortunately we don't "
                << "support such possibilities yet." << LogIO::EXCEPTION;
        }
        *_log << LogIO::NORMAL << _ORIGIN << "Found spec version "
            << _fileVersion << LogIO::POST;
    }
    else {
        *_log << LogIO::WARN << _ORIGIN << "File " << filename
            << " does not contain a CASA Region Text File spec version. "
            << "The current spec version, " << CURRENT_VERSION << " will be assumed. "
            << "WE STRONGLY SUGGEST YOU INCLUDE THE SPEC VERSION IN THIS FILE TO AVOID "
            << "POSSIBLE FUTURE BACKWARD INCOMPATIBILTY ISSUES. Simply ensure the first line "
            << "of the file is '#CRTFv" << CURRENT_VERSION << "'" << LogIO::POST;
        _fileVersion = CURRENT_VERSION;
    }
}

void RegionTextParser::_parse(const String& contents, const String& fileDesc, bool requireImageRegion) {
    _log->origin(LogOrigin("AsciiRegionFileParser", __func__));
    static const Regex startAnn("^ann[[:space:]]+");
    static const Regex startDiff("^-[[:space:]]*");
    static const Regex startGlobal("^global[[:space:]]+");
    AnnotationBase::unitInit();
    auto lines = stringToVector(contents, '\n');
    uInt lineCount = 0;
    auto qFreqs = _overridingFreqRange
        ? std::pair<Quantity, Quantity>(
            Quantity(_overridingFreqRange->first.getValue().getValue(), "Hz"),
            Quantity(_overridingFreqRange->second.getValue().getValue(), "Hz")
        )
        : std::pair<Quantity, Quantity>(Quantity(0), Quantity(0));
    for(auto iter=lines.cbegin(); iter!=lines.cend(); ++iter) {
        ++lineCount;
        Bool annOnly = false;
        ostringstream preambleoss;
        preambleoss << fileDesc + " line# " << lineCount << ": ";
        const auto preamble = preambleoss.str();
        Bool difference = false;
        iter->trim();
        if (
            iter->empty() || iter->startsWith("#")
        ) {
            // ignore comments and blank lines
            _addLine(AsciiAnnotationFileLine(*iter));
            continue;
        }
        auto consumeMe = *iter;
        // consumeMe.downcase();
        Bool spectralParmsUpdated;
        ParamSet newParams;
        if (consumeMe.contains(startDiff)) {
            difference = true;
            // consume the difference character to allow further processing of string
            consumeMe.del(0, 1);
            consumeMe.trim();
            *_log << LogIO::NORMAL << preamble << "difference found" << LogIO::POST;
        }
        else if(consumeMe.contains(startAnn)) {
            annOnly = true;
            // consume the annotation chars
            consumeMe.del(0, 3);
            consumeMe.trim();
            *_log << LogIO::NORMAL << preamble << "annotation only found" << LogIO::POST;
        }
        else if(consumeMe.contains(startGlobal)) {
            consumeMe.del(0, 6);
            _currentGlobals = _getCurrentParamSet(
                spectralParmsUpdated, newParams,
                consumeMe, preamble
            );
            std::map<AnnotationBase::Keyword, String> gParms;
            for (const auto& p: newParams) {
                gParms[p.first] = p.second.stringVal;
            }
            _addLine(AsciiAnnotationFileLine(gParms));
            if (
                _csys.hasSpectralAxis() && spectralParmsUpdated
                && newParams.find(AnnotationBase::RANGE) != newParams.end()
            ) {
                qFreqs = _quantitiesFromFrequencyString(
                    newParams[AnnotationBase::RANGE].stringVal, preamble
                );
            }
            *_log << LogIO::NORMAL << preamble << "global found" << LogIO::POST;
            continue;
        }
        // now look for per-line shapes and annotations
        Vector<Quantity> qDirs;
        vector<Quantity> quantities;
        String textString;
        const auto annType = _getAnnotationType(
            qDirs, quantities, textString, consumeMe, preamble
        );
        ParamSet currentParamSet = _getCurrentParamSet(
            spectralParmsUpdated, newParams, consumeMe, preamble
        );
        if (
            newParams.find(AnnotationBase::LABEL) == newParams.end()
            || newParams[AnnotationBase::LABEL].stringVal.empty()
        ) {
            if (newParams.find(AnnotationBase::LABELCOLOR) != newParams.end()) {
                *_log << LogIO::WARN << preamble
                    << "Ignoring labelcolor because there is no associated label specified"
                    << LogIO::POST;
            }
            if (newParams.find(AnnotationBase::LABELPOS) != newParams.end()) {
                *_log << LogIO::WARN << preamble
                    << "Ignoring labelpos because there is no associated label specified"
                    << LogIO::POST;
            }
            if (newParams.find(AnnotationBase::LABELOFF) != newParams.end()) {
                *_log << LogIO::WARN << preamble
                    << "Ignoring labeloff because there is no associated label specified"
                    << LogIO::POST;
            }
        }
        else if (newParams.find(AnnotationBase::LABELCOLOR) == newParams.end()) {
            // if a label is specified but no label color is specified, the labelcolor
            // is to be the same as the annotation color
            newParams[AnnotationBase::LABELCOLOR] = newParams[AnnotationBase::COLOR];
        }
        if (_csys.hasSpectralAxis()) {
            if(spectralParmsUpdated) {
                qFreqs = _quantitiesFromFrequencyString(
                    currentParamSet[AnnotationBase::RANGE].stringVal, preamble
                );
            }
            else if(
                _currentGlobals.find(AnnotationBase::RANGE)
                == _currentGlobals.end()
                || ! _currentGlobals.at(AnnotationBase::RANGE).freqRange
            ) {
                // no global frequency range, so use entire freq span
                qFreqs = std::pair<Quantity, Quantity>(Quantity(0), Quantity(0));
            }
        }
        auto globalsLessLocal = _currentGlobals;
        for (
            auto iter=newParams.cbegin();
            iter != newParams.cend(); ++iter
        ) {
            AnnotationBase::Keyword key = iter->first;
            if (globalsLessLocal.find(key) != globalsLessLocal.end()) {
                globalsLessLocal.erase(key);
            }
        }
        _globalKeysToApply.resize(globalsLessLocal.size(), false);
        uInt i = 0;
        for (
            ParamSet::const_iterator iter=globalsLessLocal.begin();
            iter != globalsLessLocal.end(); ++iter
        ) {
            _globalKeysToApply[i] = iter->first;
            ++i;
        }
        _createAnnotation(
            annType, qDirs, qFreqs, quantities, textString,
            currentParamSet, annOnly, difference, preamble,
            requireImageRegion
        );
    }
    if (_verbose) {
        *_log << LogIO::NORMAL << "Combined " << _regions
            << " image regions (which excludes any annotation regions)" << LogIO::POST;
        if (_regions > 0) {
            *_log << LogIO::NORMAL << "The specified region will select all pixels that are "
                << "included in the region. Full pixels will be included even when they are "
                << "only partially covered by the region(s)." << LogIO::POST;
        }
    }
}

void RegionTextParser::_addLine(const AsciiAnnotationFileLine& line) {
    _lines.push_back(line);
}

AnnotationBase::Type RegionTextParser::_getAnnotationType(
    Vector<Quantity>& qDirs,
    vector<Quantity>& quantities,
    String& textString,
    String& consumeMe, const String& preamble
) const {
    const static String sOnePairOneSingle =
        "\\[" + sOnePair + ",[^\\[,]+\\]";
    const static String sOnePairAndText =
        "\\[" + sOnePair + ",[[:space:]]*[\"\'].*[\"\'][[:space:]]*\\]";
    const static String sTwoPair = bTwoPair + "\\]";
    const static Regex startTwoPair("^" + sTwoPair);
    const static Regex startOnePairAndText("^" + sOnePairAndText);
    const static String sTwoPairOneSingle = bTwoPair
        + ",[[:space:]]*[^\\[,]+[[:space:]]*\\]";
    const static Regex startTwoPairOneSingle("^" + sTwoPairOneSingle);
    const static Regex startOnePairOneSingle("^" + sOnePairOneSingle);
    consumeMe.trim();
    String tmp = consumeMe.through(Regex("[[:alpha:]]+"));
    consumeMe.del(0, (Int)tmp.length());
    consumeMe.trim();
    auto annotationType = AnnotationBase::typeFromString(tmp);
    std::pair<Quantity, Quantity> myPair;
    switch(annotationType) {
    case AnnotationBase::RECT_BOX:
        ThrowIf(
            ! consumeMe.contains(startTwoPair),
            preamble + "Illegal box specification "
        );
        qDirs = _extractNQuantityPairs(consumeMe, preamble);

        if (qDirs.size() != 4) {
            throw AipsError(preamble
                + "rectangle box spec must contain exactly 2 direction pairs but it has "
                + String::toString(qDirs.size())
            );
        }
        break;
    case AnnotationBase::CENTER_BOX:
        ThrowIf(
            ! consumeMe.contains(startTwoPair),
            preamble + "Illegal center box specification " + consumeMe
        );
        qDirs.resize(2);
        quantities.resize(2);
        {
            Vector<Quantity> qs = _extractNQuantityPairs(consumeMe, preamble);
            qDirs[0] = qs[0];
            qDirs[1] = qs[1];
            quantities[0] = qs[2];
            quantities[1] = qs[3];
        }
        break;
    case AnnotationBase::ROTATED_BOX:
        ThrowIf(
            ! consumeMe.contains(startTwoPairOneSingle),
            preamble + "Illegal rotated box specification " + consumeMe
        );
        qDirs.resize(2);
        quantities.resize(3);
        {
            Vector<Quantity> qs = _extractTwoQuantityPairsAndSingleQuantity(consumeMe, preamble);
            qDirs[0] = qs[0];
            qDirs[1] = qs[1];
            quantities[0] = qs[2];
            quantities[1] = qs[3];
            quantities[2] = qs[4];
        }
        break;
    case AnnotationBase::POLYGON:
        // Polygon definitions can be very long with many points.
        // Testing entire polygon string syntax causes regex seg fault.
        ThrowIf(
            ! (
               consumeMe.contains(Regex("^ *\\[ *\\["))
               && consumeMe.contains(Regex("\\] *\\]"))
            ), preamble + "Illegal polygon specification " + consumeMe
        );
        {
            Vector<Quantity> qs = _extractNQuantityPairs(consumeMe, preamble);
            qDirs.resize(qs.size());
            qDirs = qs;
        }
        break;
    case AnnotationBase::CIRCLE:
        ThrowIf(
            ! consumeMe.contains(startOnePairOneSingle),
            preamble + "Illegal circle specification " + consumeMe
        );
        qDirs.resize(2);
        quantities.resize(1);
        {
            Vector<Quantity> qs = _extractQuantityPairAndSingleQuantity(
                consumeMe, preamble
            );
            qDirs[0] = qs[0];
            qDirs[1] = qs[1];
            quantities[0] = qs[2];
        }
        break;
    case AnnotationBase::ANNULUS:
        ThrowIf(
            ! consumeMe.contains(startTwoPair),
            preamble + "Illegal annulus specification " + consumeMe
        );
        qDirs.resize(2);
        quantities.resize(2);
        {
            Vector<Quantity> qs = _extractNQuantityPairs(
                consumeMe, preamble
            );
            qDirs[0] = qs[0];
            qDirs[1] = qs[1];
            quantities[0] = qs[2];
            quantities[1] = qs[3];
        }
        break;
    case AnnotationBase::ELLIPSE:
        if (! consumeMe.contains(startTwoPairOneSingle)) {
            *_log << preamble << "Illegal ellipse specification "
                << consumeMe << LogIO::EXCEPTION;
        }
        qDirs.resize(2);
        quantities.resize(3);
        {
            Vector<Quantity> qs = _extractTwoQuantityPairsAndSingleQuantity(
                consumeMe, preamble
            );
            qDirs[0] = qs[0];
            qDirs[1] = qs[1];
            quantities[0] = qs[2];
            quantities[1] = qs[3];
            quantities[2] = qs[4];
        }
        break;
    case AnnotationBase::LINE:
        if (! consumeMe.contains(startTwoPair)) {
            *_log << preamble << "Illegal line specification "
                << consumeMe << LogIO::EXCEPTION;
        }
        qDirs.resize(4);
        qDirs = _extractNQuantityPairs(consumeMe, preamble);
        if (qDirs.size() != 4) {
            throw AipsError(preamble
                + "line spec must contain exactly 2 direction pairs but it has "
                + String::toString(qDirs.size())
            );
        }
        break;
    case AnnotationBase::VECTOR:
        if (! consumeMe.contains(startTwoPair)) {
            *_log << preamble << "Illegal vector specification "
                << consumeMe << LogIO::EXCEPTION;
        }
        qDirs.resize(4);
        qDirs = _extractNQuantityPairs(consumeMe, preamble);
        if (qDirs.size() != 4) {
            throw AipsError(preamble
                + "line spec must contain exactly 2 direction pairs but it has "
                + String::toString(qDirs.size())
            );
        }
        break;
    case AnnotationBase::TEXT:
        if (! consumeMe.contains(startOnePairAndText)) {
            *_log << preamble << "Illegal text specification "
                << consumeMe << LogIO::EXCEPTION;
        }
        qDirs.resize(2);
        _extractQuantityPairAndString(
            myPair, textString, consumeMe, preamble, true
        );
        qDirs[0] = myPair.first;
        qDirs[1] = myPair.second;
        break;
    case AnnotationBase::SYMBOL:
        if (! consumeMe.contains(startOnePairOneSingle)) {
            *_log << preamble << "Illegal symbol specification "
                << consumeMe << LogIO::EXCEPTION;
        }
        qDirs.resize(2);
        _extractQuantityPairAndString(
            myPair, textString, consumeMe, preamble, false
        );
        qDirs[0] = myPair.first;
        qDirs[1] = myPair.second;
        textString.trim();
        if (textString.length() > 1) {
            throw AipsError(
                preamble
                    + ": A symbol is defined by a single character. The provided string ("
                    + textString
                    + ") has more than one"
            );
        }
        break;
    default:
        ThrowCc(
            preamble + "Unable to determine annotation type"
        );
    }
    return annotationType;
}

RegionTextParser::ParamSet RegionTextParser::getParamSet(
    Bool& spectralParmsUpdated, LogIO& log,
    const String& text, const String& preamble,
    const CoordinateSystem& csys,
    std::shared_ptr<std::pair<MFrequency, MFrequency> > overridingFreqRange,
    std::shared_ptr<Vector<Stokes::StokesTypes> > overridingCorrRange
) {
    ParamSet parms;
    spectralParmsUpdated = false;
    auto consumeMe = text;
    // get key-value pairs on the line
    while (consumeMe.size() > 0) {
        ParamValue paramValue;
        auto key = AnnotationBase::UNKNOWN_KEYWORD;
        consumeMe.trim();
        consumeMe.ltrim(',');
        consumeMe.trim();
        ThrowIf(
            ! consumeMe.contains('='),
            preamble + "Illegal extra characters on line ("
                + consumeMe + "). Did you forget a '='?"
        );
        const auto equalPos = consumeMe.find('=');
        auto keyword = consumeMe.substr(0, equalPos);
        keyword.trim();
        keyword.downcase();
        consumeMe.del(0, (Int)equalPos + 1);
        consumeMe.trim();
        if (keyword == "label") {
            key = AnnotationBase::LABEL;
            paramValue.stringVal = _doLabel(consumeMe, preamble);
        }
        else {
            paramValue.stringVal = _getKeyValue(consumeMe, preamble);
            if (keyword == "coord") {
                key = AnnotationBase::COORD;
            }
            else if (keyword == "corr" && ! overridingCorrRange) {
                if (csys.hasPolarizationCoordinate()) {
                    key = AnnotationBase::CORR;
                    paramValue.stokes = _stokesFromString(
                        paramValue.stringVal, preamble
                    );
                }
                else {
                    log << LogIO::WARN << preamble
                        << "Keyword " << keyword << " specified but will be ignored "
                        << "because the coordinate system has no polarization axis."
                        << LogIO::POST;
                }
            }
            else if (
                ! overridingFreqRange
                && (
                    keyword == "frame" || keyword == "range"
                    || keyword == "veltype" || keyword == "restfreq"
                )
            ) {
                spectralParmsUpdated = true;
                if (! csys.hasSpectralAxis()) {
                    spectralParmsUpdated = false;
                    log << LogIO::WARN << preamble
                        << "Keyword " << keyword << " specified but will be ignored "
                        << "because the coordinate system has no spectral axis."
                        << LogIO::POST;
                }
                else if (keyword == "frame") {
                    key = AnnotationBase::FRAME;
                }
                else if (keyword == "range") {
                    key = AnnotationBase::RANGE;
                }
                else if (keyword == "veltype") {
                    key = AnnotationBase::VELTYPE;
                }
                else if (keyword == "restfreq") {
                    key = AnnotationBase::RESTFREQ;
                    Quantity qRestfreq;
                    ThrowIf(
                        ! readQuantity(qRestfreq, paramValue.stringVal),
                        "Could not convert rest frequency "
                        + paramValue.stringVal + " to quantity"
                    );
                }
            }
            else if (keyword == "linewidth") {
                key = AnnotationBase::LINEWIDTH;
                if (! paramValue.stringVal.matches(Regex("^[1-9]+$"))) {
                    log << preamble << "linewidth (" << paramValue.stringVal
                        << ") must be a positive integer but is not." << LogIO::EXCEPTION;
                }
                paramValue.intVal = String::toInt(paramValue.stringVal);
            }
            else if (keyword == "linestyle") {
                key = AnnotationBase::LINESTYLE;
                paramValue.lineStyleVal = AnnotationBase::lineStyleFromString(
                    paramValue.stringVal
                );
            }
            else if (keyword == "symsize") {
                key = AnnotationBase::SYMSIZE;
                if (! paramValue.stringVal.matches(Regex("^[1-9]+$"))) {
                    log << preamble << "symsize (" << paramValue.stringVal
                        << ") must be a positive integer but is not." << LogIO::EXCEPTION;
                }
                paramValue.intVal = String::toInt(paramValue.stringVal);
            }
            else if (keyword == "symthick") {
                key = AnnotationBase::SYMTHICK;
                if (! paramValue.stringVal.matches(Regex("^[1-9]+$"))) {
                    log << preamble << "symthick (" << paramValue.stringVal
                        << ") must be a positive integer but is not." << LogIO::EXCEPTION;
                }
                paramValue.intVal = String::toInt(paramValue.stringVal);
            }
            else if (keyword == "color") {
                key = AnnotationBase::COLOR;
            }
            else if (keyword == "font") {
                key = AnnotationBase::FONT;
            }
            else if (keyword == "fontsize") {
                key = AnnotationBase::FONTSIZE;
                paramValue.intVal = String::toInt(paramValue.stringVal);
            }
            else if (keyword == "fontstyle") {
                key = AnnotationBase::FONTSTYLE;
                paramValue.fontStyleVal = AnnotationBase::fontStyleFromString(
                    paramValue.stringVal
                );
            }
            else if (keyword == "usetex") {
                String v = paramValue.stringVal;
                v.downcase();
                key = AnnotationBase::USETEX;
                if (
                    v != "true"  && v != "t"
                    && v != "false" && v != "f"
                ) {
                    log << preamble << "Cannot determine boolean value of usetex"
                        << paramValue.stringVal << LogIO::EXCEPTION;
                }
                paramValue.boolVal = (v == "true" || v == "t");
            }
            else if (keyword == "labelcolor") {
                key = AnnotationBase::LABELCOLOR;
            }
            else if (keyword == "labelpos") {
                key = AnnotationBase::LABELPOS;
            }
            else if (keyword == "labeloff") {
                auto v = paramValue.stringVal;
                static const String sInt("[-+]?[0-9]+");
                static const Regex rInt(sInt);
                if (
                    ! v.contains(
                        Regex(
                            sInt + "[[:space:]]*,[[:space:]]*" + sInt
                        )
                    )
                ) {
                    log << preamble << "Illegal label offset specification \""
                        << v << "\"" << LogIO::EXCEPTION;
                }
                // the brackets have been stripped, add them back to make it easier
                // to parse with a method already in existence
                auto pair = _extractSinglePair("[" + v + "]");
                paramValue.intVec = vector<Int>();

                for (
                    auto iter=pair.begin();
                    iter != pair.end(); ++iter
                ) {
                    if (! iter->matches(rInt)) {
                        log << preamble << "Illegal label offset specification, "
                            << *iter << " is not an integer" << LogIO::EXCEPTION;
                    }
                    paramValue.intVec.push_back(String::toInt(*iter));
                }
                key = AnnotationBase::LABELOFF;
            }
            else {
                ThrowCc(preamble + "Unrecognized key " + keyword);
            }
        }
        consumeMe.trim();
        if (key != AnnotationBase::UNKNOWN_KEYWORD) {
            parms[key] = paramValue;
        }
    }
    return parms;
}

RegionTextParser::ParamSet
RegionTextParser::_getCurrentParamSet(
    Bool& spectralParmsUpdated, ParamSet& newParams,
    String& consumeMe, const String& preamble
) const {
    auto currentParams = _currentGlobals;
    newParams = getParamSet(
        spectralParmsUpdated,
        *_log, consumeMe, preamble, _csys, _overridingFreqRange,
        _overridingCorrRange
    );
    for (const auto& p: newParams) {
        currentParams[p.first] = p.second;
    }
    ThrowIf(
        currentParams.find(AnnotationBase::RANGE) == currentParams.end()
        && currentParams.find(AnnotationBase::FRAME) != currentParams.end(),
        preamble + "Frame specified but frequency range not specified"
    );
    ThrowIf(
        currentParams.find(AnnotationBase::RANGE) == currentParams.end()
        && currentParams.find(AnnotationBase::RESTFREQ) != currentParams.end(),
        preamble + "Rest frequency specified but velocity range not specified"
    );
    return currentParams;
}

std::pair<Quantity, Quantity> RegionTextParser::_quantitiesFromFrequencyString(
    const String& freqString, const String& preamble
) const {
    // the brackets have been stripped, add them back to make it easier
    // to parse with a method already in existence
    auto cString = "[" + freqString + "]";
    ThrowIf(! cString.contains(startOnePair),
        preamble + "Incorrect spectral range specification ("
        + freqString + ")"
    );
    return _extractSingleQuantityPair(
        cString, preamble
    );
}

void RegionTextParser::_createAnnotation(
    const AnnotationBase::Type annType,
    const Vector<Quantity>& qDirs,
    const std::pair<Quantity, Quantity>& qFreqs,
    const vector<Quantity>& quantities,
    const String& textString,
    const ParamSet& currentParamSet,
    const Bool annOnly, const Bool isDifference,
    const String& preamble, Bool requireImageRegion
) {
    CountedPtr<AnnotationBase> annotation;
    Vector<Stokes::StokesTypes> stokes(0);
    if (
        currentParamSet.find(AnnotationBase::CORR) != currentParamSet.end()
        && _csys.hasPolarizationCoordinate()
    ) {
        stokes.resize(currentParamSet.at(AnnotationBase::CORR).stokes.size());
        stokes = currentParamSet.at(AnnotationBase::CORR).stokes;
    }
    auto dirRefFrame = currentParamSet.at(AnnotationBase::COORD).stringVal;
    auto freqRefFrame = currentParamSet.find(AnnotationBase::FRAME) == currentParamSet.end()
        ? "" : currentParamSet.at(AnnotationBase::FRAME).stringVal;
    auto doppler = currentParamSet.find(AnnotationBase::VELTYPE) == currentParamSet.end()
        ? "" :    currentParamSet.at(AnnotationBase::VELTYPE).stringVal;
    Quantity restfreq;
    if (
        currentParamSet.find(AnnotationBase::RESTFREQ) != currentParamSet.end()
        && ! readQuantity(
            restfreq, currentParamSet.at(AnnotationBase::RESTFREQ).stringVal
        )
    ) {
        *_log << preamble << "restfreq value "
            << currentParamSet.at(AnnotationBase::RESTFREQ).stringVal << " is not "
            << "a valid quantity." << LogIO::EXCEPTION;
    }
    try {
    switch (annType) {
        case AnnotationBase::RECT_BOX:
            annotation = new AnnRectBox(
                qDirs[0], qDirs[1], qDirs[2], qDirs[3],
                dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq, stokes,
                annOnly, requireImageRegion
            );
            break;
        case AnnotationBase::CENTER_BOX:
            annotation = new AnnCenterBox(
                qDirs[0], qDirs[1], quantities[0], quantities[1],
                dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq, stokes,
                annOnly, requireImageRegion
            );
            break;
        case AnnotationBase::ROTATED_BOX:
            annotation = new AnnRotBox(
                qDirs[0], qDirs[1], quantities[0], quantities[1],
                quantities[2], dirRefFrame, _csys, _imShape, qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
            );
            break;
        case AnnotationBase::POLYGON:
            {
                Vector<Quantity> x(qDirs.size()/2);
                Vector<Quantity> y(qDirs.size()/2);
                for (uInt i=0; i<x.size(); ++i) {
                    x[i] = qDirs[2*i];
                    y[i] = qDirs[2*i + 1];
                }
                annotation = new AnnPolygon(
                    x, y, dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
                    freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
                );
            }
            break;
        case AnnotationBase::CIRCLE:
            if (
                quantities[0].getUnit() == "pix"
                && ! _csys.directionCoordinate().hasSquarePixels()
            ) {
                // radius specified in pixels and pixels are not square, use
                // an AnnEllipse
                annotation = new AnnEllipse(
                    qDirs[0], qDirs[1], quantities[0], quantities[0], Quantity(0, "deg"),
                    dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
                    freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
                );
            }
            else {
                annotation = new AnnCircle(
                    qDirs[0], qDirs[1], quantities[0],
                    dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
                    freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
                );
            }
            break;
        case AnnotationBase::ANNULUS:
            annotation = new AnnAnnulus(
                qDirs[0], qDirs[1], quantities[0], quantities[1],
                dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
            );
            break;
        case AnnotationBase::ELLIPSE:
            annotation = new AnnEllipse(
                qDirs[0], qDirs[1], quantities[0], quantities[1], quantities[2],
                dirRefFrame,  _csys, _imShape, qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq,  stokes, annOnly, requireImageRegion
            );
            break;
        case AnnotationBase::LINE:
            annotation = new AnnLine(
                qDirs[0], qDirs[1], qDirs[2],
                qDirs[3], dirRefFrame,  _csys,
                qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq,  stokes
            );
            break;
        case AnnotationBase::VECTOR:
            annotation = new AnnVector(
                qDirs[0], qDirs[1], qDirs[2],
                qDirs[3], dirRefFrame,  _csys,
                qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq,  stokes
            );
            break;
        case AnnotationBase::TEXT:
            annotation = new AnnText(
                qDirs[0], qDirs[1], dirRefFrame,
                _csys, textString, qFreqs.first, qFreqs.second,
                freqRefFrame, doppler, restfreq,  stokes
            );
            break;
        case AnnotationBase::SYMBOL:
            annotation = new AnnSymbol(
                qDirs[0], qDirs[1], dirRefFrame,
                _csys, textString.firstchar(),
                qFreqs.first, qFreqs.second, freqRefFrame,
                doppler, restfreq,  stokes
            );
            break;
        default:
            ThrowCc(
                preamble + "Logic error. Unhandled type "
                    +  String::toString(annType) + " in switch statement"
            );
    }
    }
    catch (const WorldToPixelConversionError& x) {
        *_log << LogIO::WARN << preamble
            << "Error converting one or more world coordinates to pixel coordinates. "
            << "This could mean, among other things, that (part of) the region or "
            << "annotation lies far outside the image. This region/annotation will "
            << "be ignored. The related message is: " << x.getMesg() << LogIO::POST;
        return;
    }
    catch (const ToLCRegionConversionError& x) {
        *_log << LogIO::WARN << preamble
            << "Error converting world region to lattice region which probably indicates "
            << "the region lies outside of the image. This region will be ignored."
            << "The related message is: " << x.getMesg() << LogIO::POST;
        return;
    }
    catch (const AipsError& x) {
        ThrowCc(preamble + x.getMesg());
    }
    if (annotation->isRegion()) {
        dynamic_cast<AnnRegion *>(annotation.get())->setDifference(isDifference);
        if (! annOnly) {
            ++_regions;
        }
    }
    annotation->setLineWidth(currentParamSet.at(AnnotationBase::LINEWIDTH).intVal);
    annotation->setLineStyle(
        AnnotationBase::lineStyleFromString(
            currentParamSet.at(AnnotationBase::LINESTYLE).stringVal
        )
    );
    annotation->setSymbolSize(currentParamSet.at(AnnotationBase::SYMSIZE).intVal);
    annotation->setSymbolThickness(currentParamSet.at(AnnotationBase::SYMTHICK).intVal);
    annotation->setColor(currentParamSet.at(AnnotationBase::COLOR).stringVal);
    annotation->setFont(currentParamSet.at(AnnotationBase::FONT).stringVal);
    annotation->setFontSize(currentParamSet.at(AnnotationBase::FONTSIZE).intVal);
    annotation->setFontStyle(
        AnnotationBase::fontStyleFromString(
            currentParamSet.at(AnnotationBase::FONTSTYLE).stringVal
        )
    );
    annotation->setUseTex(currentParamSet.at(AnnotationBase::USETEX).boolVal);
    if (
        currentParamSet.find(AnnotationBase::LABEL) != currentParamSet.end()
        && ! currentParamSet.find(AnnotationBase::LABEL)->second.stringVal.empty()
    ) {
        annotation->setLabel(currentParamSet.at(AnnotationBase::LABEL).stringVal);
        annotation->setLabelColor(currentParamSet.at(AnnotationBase::LABELCOLOR).stringVal);
        annotation->setLabelColor(currentParamSet.at(AnnotationBase::LABELCOLOR).stringVal);
        annotation->setLabelPosition(currentParamSet.at(AnnotationBase::LABELPOS).stringVal);
        annotation->setLabelOffset(currentParamSet.at(AnnotationBase::LABELOFF).intVec);
    }
    annotation->setGlobals(_globalKeysToApply);
    AsciiAnnotationFileLine line(annotation);
    _addLine(line);
}

Array<String> RegionTextParser::_extractTwoPairs(uInt& end, const String& string) const {
    end = 0;
    Int firstBegin = string.find('[', 1);
    Int firstEnd = string.find(']', firstBegin);
    auto firstPair = string.substr(firstBegin, firstEnd - firstBegin + 1);
    Int secondBegin = string.find('[', firstEnd);
    Int secondEnd = string.find(']', secondBegin);
    auto secondPair = string.substr(secondBegin, secondEnd - secondBegin + 1);
    auto first = _extractSinglePair(firstPair);
    auto second = _extractSinglePair(secondPair);

    end = secondEnd;
    Array<String> ret(IPosition(2, 2, 2));
    ret(IPosition(2, 0, 0)) = first[0];
    ret(IPosition(2, 0, 1)) = first[1];
    ret(IPosition(2, 1, 0)) = second[0];
    ret(IPosition(2, 1, 1)) = second[1];
    return ret;
}

Vector<String> RegionTextParser::_extractSinglePair(const String& string) {
    Char quotes[2];
    quotes[0] = '\'';
    quotes[1] = '"';
    Int firstBegin = string.find('[', 0) + 1;
    Int firstEnd = string.find(',', firstBegin);
    auto first = string.substr(firstBegin, firstEnd - firstBegin);
    first.trim();
    first.trim(quotes, 2);
    Int secondBegin = firstEnd + 1;
    Int secondEnd = string.find(']', secondBegin);
    auto second = string.substr(secondBegin, secondEnd - secondBegin);
    second.trim();
    second.trim(quotes, 2);
    Vector<String> ret(2);
    ret[0] = first;
    ret[1] = second;
    return ret;
}

String RegionTextParser::_doLabel(
    String& consumeMe, const String& preamble
) {
    const auto firstChar = consumeMe.firstchar();
    if (firstChar != '\'' && firstChar != '"') {
        ostringstream oss;
        oss << preamble << "keyword 'label' found but first non-whitespace "
            << "character after the '=' is not a quote. It must be.";
        throw AipsError(oss.str());
    }
    const auto posCloseQuote = consumeMe.find(firstChar, 1);
    if (posCloseQuote == String::npos) {
        ostringstream err;
        err << preamble << "Could not find closing quote ("
            << String(firstChar) << ") for label";
        throw AipsError(err.str());
    }
    const auto label = consumeMe.substr(1, posCloseQuote - 1);
    consumeMe.del(0, (Int)posCloseQuote + 1);
    return label;
}

String RegionTextParser::_getKeyValue(
    String& consumeMe, const String& preamble
) {
    String value;
    if (consumeMe.startsWith("[")) {
        if (! consumeMe.contains("]")) {
            ostringstream err;
            err << preamble << "Unmatched open bracket: "
                << consumeMe;
            ThrowCc(err.str());
        }
        Int closeBracketPos = consumeMe.find("]");
        // don't save the open and close brackets
        value = consumeMe.substr(1, closeBracketPos - 1);
        consumeMe.del(0, closeBracketPos + 1);
    }
    if (consumeMe.contains(",")) {
        Int commaPos = consumeMe.find(",");
        if (value.empty()) {
            value = consumeMe.substr(0, commaPos);
        }
        consumeMe.del(0, commaPos);
    }
    else if (value.empty()) {
        // last key-value pair on the line
        value = consumeMe;
        consumeMe = "";
    }
    consumeMe.trim();
    Char quotes[2];
    quotes[0] = '\'';
    quotes[1] = '"';
    value.trim();
    value.trim(quotes, 2);
    value.trim();
    return value;
}

Vector<Quantity> RegionTextParser::_extractTwoQuantityPairsAndSingleQuantity(
    String& consumeMe, const String& preamble
) const {
    Vector<Quantity> quantities = _extractTwoQuantityPairs(
        consumeMe, preamble
    );
    consumeMe.trim();
    consumeMe.ltrim(',');
    consumeMe.trim();
    Char quotes[2];
    quotes[0] = '\'';
    quotes[1] = '"';

    Int end = consumeMe.find(']', 0);
    String qString = consumeMe.substr(0, end);
    qString.trim();

    qString.trim(quotes, 2);
    quantities.resize(5, true);
    if (! readQuantity(quantities[4], qString)) {
        *_log << preamble + "Could not convert "
            << qString << " to quantity." << LogIO::EXCEPTION;
    }
    consumeMe.del(0, end + 1);
    return quantities;
}

void RegionTextParser::_extractQuantityPairAndString(
    std::pair<Quantity, Quantity>& quantities, String& string,
    String& consumeMe, const String& preamble,
    const Bool requireQuotesAroundString
) const {
    // erase the left '['
    consumeMe.del(0, 1);
    SubString pairString = consumeMe.through(startOnePair);
    quantities = _extractSingleQuantityPair(pairString, preamble);
    consumeMe.del(0, (Int)pairString.length() + 1);
    consumeMe.trim();
    consumeMe.ltrim(',');
    consumeMe.trim();
    Int end = 0;
    String::size_type startSearchPos = 0;
    if (requireQuotesAroundString) {
        Char quoteChar = consumeMe.firstchar();
        if (quoteChar != '\'' && quoteChar != '"') {
            *_log << preamble
                << "Quotes around string required but no quotes were found";
        }
        startSearchPos = consumeMe.find(quoteChar, 1);
        if (startSearchPos == String::npos) {
            *_log << preamble
                << "Quotes required around string but no matching close quote found"
                << LogIO::EXCEPTION;
        }
    }
    end = consumeMe.find(']', startSearchPos);
    string = consumeMe.substr(0, end);
    consumeMe.del(0, end + 1);
    string.trim();
    Char quotes[2];
    quotes[0] = '\'';
    quotes[1] = '"';
    string.trim(quotes, 2);
    string.trim();
}

Vector<Quantity> RegionTextParser::_extractQuantityPairAndSingleQuantity(
    String& consumeMe, const String& preamble
) const {
    String qString;

    std::pair<Quantity, Quantity> myPair;
    _extractQuantityPairAndString(
        myPair, qString, consumeMe, preamble, false
    );
    Vector<Quantity> quantities(3);
    quantities[0] = myPair.first;
    quantities[1] = myPair.second;
    ThrowIf(
        ! readQuantity(quantities[2], qString),
        preamble + "Could not convert "
        + qString + " to quantity"
    );
    return quantities;
}

Vector<Quantity> RegionTextParser::_extractTwoQuantityPairs(
    String& consumeMe, const String& preamble
) const {
    const Regex startbTwoPair("^" + bTwoPair);
    String mySubstring = String(consumeMe).through(startbTwoPair);
    uInt end = 0;
    Array<String> pairs = _extractTwoPairs(end, mySubstring);
    Vector<Quantity> quantities(4);

    for (uInt i=0; i<4; ++i) {
        String desc("string " + String::toString(i));
        String value = pairs(IPosition(2, i/2, i%2));
        if (! readQuantity(quantities[i], value)) {
            *_log << preamble << "Could not convert " << desc
                << " (" << value << ") to quantity." << LogIO::EXCEPTION;
        }
    }
    consumeMe.del(0, (Int)end + 1);
    return quantities;
}

Vector<Quantity> RegionTextParser::_extractNQuantityPairs (
        String& consumeMe, const String& preamble
) const {
    String pairs = consumeMe.through(Regex("\\] *\\]"));
    String nPairs(pairs);
    consumeMe.del(0, (Int)pairs.length() + 1);
    pairs.trim();
    // remove the left most [
    pairs.del(0, 1);
    pairs.trim();
    Vector<Quantity> qs(0);

    try {
        while (pairs.length() > 1) {
            std::pair<Quantity, Quantity> myqs = _extractSingleQuantityPair(pairs, preamble);
            qs.resize(qs.size() + 2, true);
            qs[qs.size() - 2] = myqs.first;
            qs[qs.size() - 1] = myqs.second;
            pairs.del(0, (Int)pairs.find(']', 0) + 1);
            pairs.trim();
            pairs.ltrim(',');
            pairs.trim();
        }
    }
    catch (const AipsError&) {
        ThrowCc(
            preamble + "Illegal polygon specification " + nPairs
        );
    }
    return qs;
}

std::pair<Quantity, Quantity> RegionTextParser::_extractSingleQuantityPair(
    const String& pairString, const String& preamble
) const {
    String mySubstring = String(pairString).through(sOnePair, 0);
    Vector<String> pair = _extractSinglePair(mySubstring);
    std::pair<Quantity, Quantity> quantities;
    for (uInt i=0; i<2; ++i) {
        String value = pair[i];
        ThrowIf(
            ! readQuantity(
                i == 0
                    ? quantities.first
                    : quantities.second,
                value
            ),
            preamble + "Could not convert ("
            + value + ") to quantity."
        );
    }
    return quantities;
}

void RegionTextParser::_setOverridingCorrelations(const String& globalOverrideStokes) {
    if (globalOverrideStokes.empty() || ! _csys.hasPolarizationAxis()) {
        // no global override specified
        return;
    }
    String mycopy = globalOverrideStokes;
    vector<String> myStokes = ParameterParser::stokesFromString(mycopy);
    String myCommaSeperatedString;
    vector<String>::const_iterator iter = myStokes.begin();
    vector<String>::const_iterator end = myStokes.end();
    uInt count = 0;
    while(iter != end) {
        myCommaSeperatedString += *iter;
        if (count < myStokes.size() - 1) {
            myCommaSeperatedString += ",";
        }
        ++count;
        ++iter;
    }
    ParamValue corr;
    corr.stokes = _stokesFromString(myCommaSeperatedString, String(__func__));
    _currentGlobals[AnnotationBase::CORR] = corr;
    _overridingCorrRange.reset((new Vector<Stokes::StokesTypes>(corr.stokes)));
}

void RegionTextParser::_setOverridingChannelRange(
    const String& globalOverrideChans
) {
    if (globalOverrideChans.empty() || ! _csys.hasSpectralAxis()) {
        // no global override specified
        return;
    }
    uInt nSelectedChannels = 0;
    uInt nChannels = _imShape[_csys.spectralAxisNumber(false)];
    std::vector<uInt> myChanRange =  ParameterParser::spectralRangesFromChans(
        nSelectedChannels, globalOverrideChans,
        nChannels
    );
    uInt nRanges = myChanRange.size();
    if (nRanges == 0) {
        // no channel range specified
        return;
    }
    ThrowIf(
        nRanges > 2,
        "Overriding spectral specification must be "
        "limited to a sngle channel range"
    );
    MFrequency first, second;
    const SpectralCoordinate specCoord = _csys.spectralCoordinate();
    specCoord.toWorld(first, myChanRange[0]);
    specCoord.toWorld(second, myChanRange[1]);
    _overridingFreqRange.reset(new std::pair<MFrequency, MFrequency>(first, second));
    ParamValue range;
    range.freqRange = _overridingFreqRange;
    _currentGlobals[AnnotationBase::RANGE] = range;

    ParamValue frame;
    frame.intVal = specCoord.frequencySystem(false);
    _currentGlobals[AnnotationBase::FRAME] = frame;

    ParamValue veltype;
    veltype.intVal = specCoord.velocityDoppler();
    _currentGlobals[AnnotationBase::VELTYPE] = veltype;

    ParamValue restfreq;
    restfreq.stringVal = String::toString(
        specCoord.restFrequency()
    ) + "Hz";
    _currentGlobals[AnnotationBase::RESTFREQ] = restfreq;
}

Vector<Stokes::StokesTypes>
RegionTextParser::_stokesFromString(
    const String& stokes, const String& preamble
) {
    const auto maxn = Stokes::NumberOfTypes;
    std::unique_ptr<string[]> res(new string[maxn]);
    Int nStokes = split(stokes, res.get( ), maxn, ",");
    Vector<Stokes::StokesTypes> myTypes(nStokes);
    for (Int i=0; i<nStokes; ++i) {
        String x(res.get( )[i]);
        x.trim();
        myTypes[i] = Stokes::type(x);
        if (myTypes[i] == Stokes::Undefined) {
            throw AipsError(preamble + "Unknown correlation type " + x);
        }
    }
    return myTypes;
}

void RegionTextParser::_setInitialGlobals() {
    ParamValue coord;
    coord.intVal = _csys.directionCoordinate(
        _csys.findCoordinate(Coordinate::DIRECTION)
    ).directionType(false);
    coord.stringVal = MDirection::showType(coord.intVal);
    _currentGlobals[AnnotationBase::COORD] = coord;

    ParamValue range;
    range.freqRange.reset();
    _currentGlobals[AnnotationBase::RANGE] = range;

    ParamValue corr;
    corr.stokes = Vector<Stokes::StokesTypes>(0);
    _currentGlobals[AnnotationBase::CORR] = corr;

    if (_csys.hasSpectralAxis()) {
        SpectralCoordinate spectral = _csys.spectralCoordinate(
            _csys.findCoordinate(Coordinate::SPECTRAL)
        );

        ParamValue frame;
        frame.intVal = spectral.frequencySystem(false);
        _currentGlobals[AnnotationBase::FRAME] = frame;

        ParamValue veltype;
        veltype.intVal = spectral.velocityDoppler();
        _currentGlobals[AnnotationBase::VELTYPE] = veltype;

        ParamValue restfreq;
        // truncates value, not enough precision
        // restfreq.stringVal = String::toString(spectral.restFrequency()) + "Hz";
        ostringstream oss;
        oss << std::setprecision(20) << spectral.restFrequency() << "Hz";
        restfreq.stringVal = oss.str();
        _currentGlobals[AnnotationBase::RESTFREQ] = restfreq;
    }
    ParamValue linewidth;
    linewidth.intVal = AnnotationBase::DEFAULT_LINEWIDTH;
    _currentGlobals[AnnotationBase::LINEWIDTH] = linewidth;

    ParamValue linestyle;
    linestyle.lineStyleVal = AnnotationBase::DEFAULT_LINESTYLE;
    _currentGlobals[AnnotationBase::LINESTYLE] = linestyle;

    ParamValue symsize;
    symsize.intVal = AnnotationBase::DEFAULT_SYMBOLSIZE;
    _currentGlobals[AnnotationBase::SYMSIZE] = symsize;

    ParamValue symthick;
    symthick.intVal = AnnotationBase::DEFAULT_SYMBOLTHICKNESS;
    _currentGlobals[AnnotationBase::SYMTHICK] = symthick;

    ParamValue color;
    color.color = AnnotationBase::DEFAULT_COLOR;
    color.stringVal = AnnotationBase::colorToString(color.color);
    _currentGlobals[AnnotationBase::COLOR] = color;

    ParamValue font;
    font.stringVal = AnnotationBase::DEFAULT_FONT;
    _currentGlobals[AnnotationBase::FONT] = font;

    ParamValue fontsize;
    fontsize.intVal = AnnotationBase::DEFAULT_FONTSIZE;
    _currentGlobals[AnnotationBase::FONTSIZE] = fontsize;

    ParamValue fontstyle;
    fontstyle.fontStyleVal = AnnotationBase::DEFAULT_FONTSTYLE;
    _currentGlobals[AnnotationBase::FONTSTYLE] = fontstyle;

    ParamValue usetex;
    usetex.boolVal = AnnotationBase::DEFAULT_USETEX;
    _currentGlobals[AnnotationBase::USETEX] = usetex;

    ParamValue labelcolor;
    labelcolor.color = AnnotationBase::DEFAULT_LABELCOLOR;
    labelcolor.stringVal = AnnotationBase::colorToString(labelcolor.color);
    _currentGlobals[AnnotationBase::LABELCOLOR] = labelcolor;

    ParamValue labelpos;
    labelpos.stringVal = AnnotationBase::DEFAULT_LABELPOS;
    _currentGlobals[AnnotationBase::LABELPOS] = labelpos;

    ParamValue labeloff;
    labeloff.intVec = AnnotationBase::DEFAULT_LABELOFF;
    _currentGlobals[AnnotationBase::LABELOFF] = labeloff;
}

}