#include "nestedcoladapterimp.hpp"

#include <components/esm/loadregn.hpp>
#include <components/esm/loadfact.hpp>

#include "idcollection.hpp"
#include "pathgrid.hpp"
#include "info.hpp"
#include "usertype.hpp"

namespace CSMWorld
{
    PathgridPointListAdapter::PathgridPointListAdapter () {}

    void PathgridPointListAdapter::addRow(Record<Pathgrid>& record, int position) const
    {
        Pathgrid pathgrid = record.get();

        ESM::Pathgrid::PointList& points = pathgrid.mPoints;

        // blank row
        ESM::Pathgrid::Point point;
        point.mX = 0;
        point.mY = 0;
        point.mZ = 0;
        point.mAutogenerated = 0;
        point.mConnectionNum = 0;
        point.mUnknown = 0;

        // inserting a point should trigger re-indexing of the edges
        //
        // FIXME: does not auto refresh edges table view
        std::vector<ESM::Pathgrid::Edge>::iterator iter = pathgrid.mEdges.begin();
        for (;iter != pathgrid.mEdges.end(); ++iter)
        {
            if ((*iter).mV0 >= position)
                (*iter).mV0++;
            if ((*iter).mV1 >= position)
                (*iter).mV1++;
        }

        points.insert(points.begin()+position, point);
        pathgrid.mData.mS2 += 1; // increment the number of points

        record.setModified (pathgrid);
    }

    void PathgridPointListAdapter::removeRow(Record<Pathgrid>& record, int rowToRemove) const
    {
        Pathgrid pathgrid = record.get();

        ESM::Pathgrid::PointList& points = pathgrid.mPoints;

        if (rowToRemove < 0 || rowToRemove >= static_cast<int> (points.size()))
            throw std::runtime_error ("index out of range");

        // deleting a point should trigger re-indexing of the edges
        // dangling edges are not allowed and hence removed
        //
        // FIXME: does not auto refresh edges table view
        std::vector<ESM::Pathgrid::Edge>::iterator iter = pathgrid.mEdges.begin();
        for (; iter != pathgrid.mEdges.end();)
        {
            if (((*iter).mV0 == rowToRemove) || ((*iter).mV1 == rowToRemove))
                iter = pathgrid.mEdges.erase(iter);
            else
            {
                if ((*iter).mV0 > rowToRemove)
                    (*iter).mV0--;

                if ((*iter).mV1 > rowToRemove)
                    (*iter).mV1--;

                ++iter;
            }
        }
        points.erase(points.begin()+rowToRemove);
        pathgrid.mData.mS2 -= 1; // decrement the number of points

        record.setModified (pathgrid);
    }

    void PathgridPointListAdapter::setTable(Record<Pathgrid>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        Pathgrid pathgrid = record.get();

        pathgrid.mPoints =
            static_cast<const PathgridPointsWrap &>(nestedTable).mRecord.mPoints;
        pathgrid.mData.mS2 =
            static_cast<const PathgridPointsWrap &>(nestedTable).mRecord.mData.mS2;
        // also update edges in case points were added/removed
        pathgrid.mEdges =
            static_cast<const PathgridPointsWrap &>(nestedTable).mRecord.mEdges;

        record.setModified (pathgrid);
    }

    NestedTableWrapperBase* PathgridPointListAdapter::table(const Record<Pathgrid>& record) const
    {
        // deleted by dtor of NestedTableStoring
        return new PathgridPointsWrap(record.get());
    }

    QVariant PathgridPointListAdapter::getData(const Record<Pathgrid>& record,
            int subRowIndex, int subColIndex) const
    {
        ESM::Pathgrid::Point point = record.get().mPoints[subRowIndex];
        switch (subColIndex)
        {
            case 0: return subRowIndex;
            case 1: return point.mX;
            case 2: return point.mY;
            case 3: return point.mZ;
            default: throw std::runtime_error("Pathgrid point subcolumn index out of range");
        }
    }

    void PathgridPointListAdapter::setData(Record<Pathgrid>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        Pathgrid pathgrid = record.get();
        ESM::Pathgrid::Point point = pathgrid.mPoints[subRowIndex];
        switch (subColIndex)
        {
            case 0: return; // return without saving
            case 1: point.mX = value.toInt(); break;
            case 2: point.mY = value.toInt(); break;
            case 3: point.mZ = value.toInt(); break;
            default: throw std::runtime_error("Pathgrid point subcolumn index out of range");
        }

        pathgrid.mPoints[subRowIndex] = point;

        record.setModified (pathgrid);
    }

    int PathgridPointListAdapter::getColumnsCount(const Record<Pathgrid>& record) const
    {
        return 4;
    }

    int PathgridPointListAdapter::getRowsCount(const Record<Pathgrid>& record) const
    {
        return static_cast<int>(record.get().mPoints.size());
    }

    PathgridEdgeListAdapter::PathgridEdgeListAdapter () {}

    // ToDo: seems to be auto-sorted in the dialog table display after insertion
    void PathgridEdgeListAdapter::addRow(Record<Pathgrid>& record, int position) const
    {
        Pathgrid pathgrid = record.get();

        ESM::Pathgrid::EdgeList& edges = pathgrid.mEdges;

        // blank row
        ESM::Pathgrid::Edge edge;
        edge.mV0 = 0;
        edge.mV1 = 0;

        // NOTE: inserting a blank edge does not really make sense, perhaps this should be a
        // logic_error exception
        //
        // Currently the code assumes that the end user to know what he/she is doing.
        // e.g. Edges come in pairs, from points a->b and b->a
        edges.insert(edges.begin()+position, edge);

        record.setModified (pathgrid);
    }

    void PathgridEdgeListAdapter::removeRow(Record<Pathgrid>& record, int rowToRemove) const
    {
        Pathgrid pathgrid = record.get();

        ESM::Pathgrid::EdgeList& edges = pathgrid.mEdges;

        if (rowToRemove < 0 || rowToRemove >= static_cast<int> (edges.size()))
            throw std::runtime_error ("index out of range");

        edges.erase(edges.begin()+rowToRemove);

        record.setModified (pathgrid);
    }

    void PathgridEdgeListAdapter::setTable(Record<Pathgrid>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        Pathgrid pathgrid = record.get();

        pathgrid.mEdges =
            static_cast<const NestedTableWrapper<ESM::Pathgrid::EdgeList> &>(nestedTable).mNestedTable;

        record.setModified (pathgrid);
    }

    NestedTableWrapperBase* PathgridEdgeListAdapter::table(const Record<Pathgrid>& record) const
    {
        // deleted by dtor of NestedTableStoring
        return new NestedTableWrapper<ESM::Pathgrid::EdgeList>(record.get().mEdges);
    }

    QVariant PathgridEdgeListAdapter::getData(const Record<Pathgrid>& record,
            int subRowIndex, int subColIndex) const
    {
        Pathgrid pathgrid = record.get();

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (pathgrid.mEdges.size()))
            throw std::runtime_error ("index out of range");

        ESM::Pathgrid::Edge edge = pathgrid.mEdges[subRowIndex];
        switch (subColIndex)
        {
            case 0: return subRowIndex;
            case 1: return edge.mV0;
            case 2: return edge.mV1;
            default: throw std::runtime_error("Pathgrid edge subcolumn index out of range");
        }
    }

    // ToDo: detect duplicates in mEdges
    void PathgridEdgeListAdapter::setData(Record<Pathgrid>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        Pathgrid pathgrid = record.get();

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (pathgrid.mEdges.size()))
            throw std::runtime_error ("index out of range");

        ESM::Pathgrid::Edge edge = pathgrid.mEdges[subRowIndex];
        switch (subColIndex)
        {
            case 0: return; // return without saving
            case 1: edge.mV0 = value.toInt(); break;
            case 2: edge.mV1 = value.toInt(); break;
            default: throw std::runtime_error("Pathgrid edge subcolumn index out of range");
        }

        pathgrid.mEdges[subRowIndex] = edge;

        record.setModified (pathgrid);
    }

    int PathgridEdgeListAdapter::getColumnsCount(const Record<Pathgrid>& record) const
    {
        return 3;
    }

    int PathgridEdgeListAdapter::getRowsCount(const Record<Pathgrid>& record) const
    {
        return static_cast<int>(record.get().mEdges.size());
    }

    FactionReactionsAdapter::FactionReactionsAdapter () {}

    void FactionReactionsAdapter::addRow(Record<ESM::Faction>& record, int position) const
    {
        ESM::Faction faction = record.get();

        std::map<std::string, int>& reactions = faction.mReactions;

        // blank row
        reactions.insert(std::make_pair("", 0));

        record.setModified (faction);
    }

    void FactionReactionsAdapter::removeRow(Record<ESM::Faction>& record, int rowToRemove) const
    {
        ESM::Faction faction = record.get();

        std::map<std::string, int>& reactions = faction.mReactions;

        if (rowToRemove < 0 || rowToRemove >= static_cast<int> (reactions.size()))
            throw std::runtime_error ("index out of range");

        // FIXME: how to ensure that the map entries correspond to table indicies?
        // WARNING: Assumed that the table view has the same order as std::map
        std::map<std::string, int>::iterator iter = reactions.begin();
        for(int i = 0; i < rowToRemove; ++i)
            iter++;
        reactions.erase(iter);

        record.setModified (faction);
    }

    void FactionReactionsAdapter::setTable(Record<ESM::Faction>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        ESM::Faction faction = record.get();

        faction.mReactions =
            static_cast<const NestedTableWrapper<std::map<std::string, int> >&>(nestedTable).mNestedTable;

        record.setModified (faction);
    }

    NestedTableWrapperBase* FactionReactionsAdapter::table(const Record<ESM::Faction>& record) const
    {
        // deleted by dtor of NestedTableStoring
        return new NestedTableWrapper<std::map<std::string, int> >(record.get().mReactions);
    }

    QVariant FactionReactionsAdapter::getData(const Record<ESM::Faction>& record,
            int subRowIndex, int subColIndex) const
    {
        ESM::Faction faction = record.get();

        std::map<std::string, int>& reactions = faction.mReactions;

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (reactions.size()))
            throw std::runtime_error ("index out of range");

        // FIXME: how to ensure that the map entries correspond to table indicies?
        // WARNING: Assumed that the table view has the same order as std::map
        std::map<std::string, int>::const_iterator iter = reactions.begin();
        for(int i = 0; i < subRowIndex; ++i)
            iter++;
        switch (subColIndex)
        {
            case 0: return QString((*iter).first.c_str());
            case 1: return (*iter).second;
            default: throw std::runtime_error("Faction reactions subcolumn index out of range");
        }
    }

    void FactionReactionsAdapter::setData(Record<ESM::Faction>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        ESM::Faction faction = record.get();

        std::map<std::string, int>& reactions = faction.mReactions;

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (reactions.size()))
            throw std::runtime_error ("index out of range");

        // FIXME: how to ensure that the map entries correspond to table indicies?
        // WARNING: Assumed that the table view has the same order as std::map
        std::map<std::string, int>::iterator iter = reactions.begin();
        for(int i = 0; i < subRowIndex; ++i)
            iter++;

        std::string factionId = (*iter).first;
        int reaction = (*iter).second;

        switch (subColIndex)
        {
            case 0:
            {
                reactions.erase(iter);
                reactions.insert(std::make_pair(value.toString().toUtf8().constData(), reaction));
                break;
            }
            case 1:
            {
                reactions[factionId] = value.toInt();
                break;
            }
            default: throw std::runtime_error("Faction reactions subcolumn index out of range");
        }

        record.setModified (faction);
    }

    int FactionReactionsAdapter::getColumnsCount(const Record<ESM::Faction>& record) const
    {
        return 2;
    }

    int FactionReactionsAdapter::getRowsCount(const Record<ESM::Faction>& record) const
    {
        return static_cast<int>(record.get().mReactions.size());
    }

    RegionSoundListAdapter::RegionSoundListAdapter () {}

    void RegionSoundListAdapter::addRow(Record<ESM::Region>& record, int position) const
    {
        ESM::Region region = record.get();

        std::vector<ESM::Region::SoundRef>& soundList = region.mSoundList;

        // blank row
        ESM::Region::SoundRef soundRef;
        soundRef.mSound.assign("");
        soundRef.mChance = 0;

        soundList.insert(soundList.begin()+position, soundRef);

        record.setModified (region);
    }

    void RegionSoundListAdapter::removeRow(Record<ESM::Region>& record, int rowToRemove) const
    {
        ESM::Region region = record.get();

        std::vector<ESM::Region::SoundRef>& soundList = region.mSoundList;

        if (rowToRemove < 0 || rowToRemove >= static_cast<int> (soundList.size()))
            throw std::runtime_error ("index out of range");

        soundList.erase(soundList.begin()+rowToRemove);

        record.setModified (region);
    }

    void RegionSoundListAdapter::setTable(Record<ESM::Region>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        ESM::Region region = record.get();

        region.mSoundList =
            static_cast<const NestedTableWrapper<std::vector<ESM::Region::SoundRef> >&>(nestedTable).mNestedTable;

        record.setModified (region);
    }

    NestedTableWrapperBase* RegionSoundListAdapter::table(const Record<ESM::Region>& record) const
    {
        // deleted by dtor of NestedTableStoring
        return new NestedTableWrapper<std::vector<ESM::Region::SoundRef> >(record.get().mSoundList);
    }

    QVariant RegionSoundListAdapter::getData(const Record<ESM::Region>& record,
            int subRowIndex, int subColIndex) const
    {
        ESM::Region region = record.get();

        std::vector<ESM::Region::SoundRef>& soundList = region.mSoundList;

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (soundList.size()))
            throw std::runtime_error ("index out of range");

        ESM::Region::SoundRef soundRef = soundList[subRowIndex];
        switch (subColIndex)
        {
            case 0: return QString(soundRef.mSound.toString().c_str());
            case 1: return soundRef.mChance;
            default: throw std::runtime_error("Region sounds subcolumn index out of range");
        }
    }

    void RegionSoundListAdapter::setData(Record<ESM::Region>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        ESM::Region region = record.get();

        std::vector<ESM::Region::SoundRef>& soundList = region.mSoundList;

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (soundList.size()))
            throw std::runtime_error ("index out of range");

        ESM::Region::SoundRef soundRef = soundList[subRowIndex];
        switch (subColIndex)
        {
            case 0: soundRef.mSound.assign(value.toString().toUtf8().constData()); break;
            case 1: soundRef.mChance = static_cast<unsigned char>(value.toInt()); break;
            default: throw std::runtime_error("Region sounds subcolumn index out of range");
        }

        region.mSoundList[subRowIndex] = soundRef;

        record.setModified (region);
    }

    int RegionSoundListAdapter::getColumnsCount(const Record<ESM::Region>& record) const
    {
        return 2;
    }

    int RegionSoundListAdapter::getRowsCount(const Record<ESM::Region>& record) const
    {
        return static_cast<int>(record.get().mSoundList.size());
    }

    InfoListAdapter::InfoListAdapter () {}

    void InfoListAdapter::addRow(Record<Info>& record, int position) const
    {
        throw std::logic_error ("cannot add a row to a fixed table");
    }

    void InfoListAdapter::removeRow(Record<Info>& record, int rowToRemove) const
    {
        throw std::logic_error ("cannot remove a row to a fixed table");
    }

    void InfoListAdapter::setTable(Record<Info>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        throw std::logic_error ("table operation not supported");
    }

    NestedTableWrapperBase* InfoListAdapter::table(const Record<Info>& record) const
    {
        throw std::logic_error ("table operation not supported");
    }

    QVariant InfoListAdapter::getData(const Record<Info>& record,
            int subRowIndex, int subColIndex) const
    {
        Info info = record.get();

        if (subColIndex == 0)
            return QString(info.mResultScript.c_str());
        else
            throw std::runtime_error("Trying to access non-existing column in the nested table!");
    }

    void InfoListAdapter::setData(Record<Info>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        Info info = record.get();

        if (subColIndex == 0)
            info.mResultScript = value.toString().toStdString();
        else
            throw std::runtime_error("Trying to access non-existing column in the nested table!");

        record.setModified (info);
    }

    int InfoListAdapter::getColumnsCount(const Record<Info>& record) const
    {
        return 1;
    }

    int InfoListAdapter::getRowsCount(const Record<Info>& record) const
    {
        return 1; // fixed at size 1
    }

    // ESM::DialInfo::SelectStruct.mSelectRule
    // 012345...
    // ^^^ ^^
    // ||| ||
    // ||| |+------------- condition variable string
    // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc
    // ||+---------------- function index (encoded, where function == '1')
    // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc
    // +------------------ unknown
    //
    InfoConditionAdapter::InfoConditionAdapter () {}

    void InfoConditionAdapter::addRow(Record<Info>& record, int position) const
    {
        Info info = record.get();

        std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects;

        // blank row
        ESM::DialInfo::SelectStruct condStruct;
        condStruct.mSelectRule = "00000";
        condStruct.mValue = ESM::Variant();
        condStruct.mValue.setType(ESM::VT_Int); // default to ints

        conditions.insert(conditions.begin()+position, condStruct);

        record.setModified (info);
    }

    void InfoConditionAdapter::removeRow(Record<Info>& record, int rowToRemove) const
    {
        Info info = record.get();

        std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects;

        if (rowToRemove < 0 || rowToRemove >= static_cast<int> (conditions.size()))
            throw std::runtime_error ("index out of range");

        conditions.erase(conditions.begin()+rowToRemove);

        record.setModified (info);
    }

    void InfoConditionAdapter::setTable(Record<Info>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        Info info = record.get();

        info.mSelects =
            static_cast<const NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct> >&>(nestedTable).mNestedTable;

        record.setModified (info);
    }

    NestedTableWrapperBase* InfoConditionAdapter::table(const Record<Info>& record) const
    {
        // deleted by dtor of NestedTableStoring
        return new NestedTableWrapper<std::vector<ESM::DialInfo::SelectStruct> >(record.get().mSelects);
    }

    // See the mappings in MWDialogue::SelectWrapper::getArgument
    // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI)
    static std::map<const std::string, std::string> populateEncToInfoFunc()
    {
        std::map<const std::string, std::string> funcMap;
        funcMap["00"] = "Rank Low";
        funcMap["01"] = "Rank High";
        funcMap["02"] = "Rank Requirement";
        funcMap["03"] = "Reputation";
        funcMap["04"] = "Health Percent";
        funcMap["05"] = "PC Reputation";
        funcMap["06"] = "PC Level";
        funcMap["07"] = "PC Health Percent";
        funcMap["08"] = "PC Magicka";
        funcMap["09"] = "PC Fatigue";
        funcMap["10"] = "PC Strength";
        funcMap["11"] = "PC Block";
        funcMap["12"] = "PC Armoror";
        funcMap["13"] = "PC Medium Armor";
        funcMap["14"] = "PC Heavy Armor";
        funcMap["15"] = "PC Blunt Weapon";
        funcMap["16"] = "PC Long Blade";
        funcMap["17"] = "PC Axe";
        funcMap["18"] = "PC Spear";
        funcMap["19"] = "PC Athletics";
        funcMap["20"] = "PC Enchant";
        funcMap["21"] = "PC Destruction";
        funcMap["22"] = "PC Alteration";
        funcMap["23"] = "PC Illusion";
        funcMap["24"] = "PC Conjuration";
        funcMap["25"] = "PC Mysticism";
        funcMap["26"] = "PC Restoration";
        funcMap["27"] = "PC Alchemy";
        funcMap["28"] = "PC Unarmored";
        funcMap["29"] = "PC Security";
        funcMap["30"] = "PC Sneak";
        funcMap["31"] = "PC Acrobatics";
        funcMap["32"] = "PC Light Armor";
        funcMap["33"] = "PC Short Blade";
        funcMap["34"] = "PC Marksman";
        funcMap["35"] = "PC Merchantile";
        funcMap["36"] = "PC Speechcraft";
        funcMap["37"] = "PC Hand To Hand";
        funcMap["38"] = "PC Sex";
        funcMap["39"] = "PC Expelled";
        funcMap["40"] = "PC Common Disease";
        funcMap["41"] = "PC Blight Disease";
        funcMap["42"] = "PC Clothing Modifier";
        funcMap["43"] = "PC Crime Level";
        funcMap["44"] = "Same Sex";
        funcMap["45"] = "Same Race";
        funcMap["46"] = "Same Faction";
        funcMap["47"] = "Faction Rank Difference";
        funcMap["48"] = "Detected";
        funcMap["49"] = "Alarmed";
        funcMap["50"] = "Choice";
        funcMap["51"] = "PC Intelligence";
        funcMap["52"] = "PC Willpower";
        funcMap["53"] = "PC Agility";
        funcMap["54"] = "PC Speed";
        funcMap["55"] = "PC Endurance";
        funcMap["56"] = "PC Personality";
        funcMap["57"] = "PC Luck";
        funcMap["58"] = "PC Corpus";
        funcMap["59"] = "Weather";
        funcMap["60"] = "PC Vampire";
        funcMap["61"] = "Level";
        funcMap["62"] = "Attacked";
        funcMap["63"] = "Talked To PC";
        funcMap["64"] = "PC Health";
        funcMap["65"] = "Creature Target";
        funcMap["66"] = "Friend Hit";
        funcMap["67"] = "Fight";
        funcMap["68"] = "Hello";
        funcMap["69"] = "Alarm";
        funcMap["70"] = "Flee";
        funcMap["71"] = "Should Attack";
        funcMap["72"] = "Werewolf";
        funcMap["73"] = "PC Werewolf Kills";
        return funcMap;
    }
    static const std::map<const std::string, std::string> sEncToInfoFunc = populateEncToInfoFunc();

    QVariant InfoConditionAdapter::getData(const Record<Info>& record,
            int subRowIndex, int subColIndex) const
    {
        Info info = record.get();

        std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects;

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (conditions.size()))
            throw std::runtime_error ("index out of range");

        switch (subColIndex)
        {
            case 0:
            {
                char condType = conditions[subRowIndex].mSelectRule[1];
                switch (condType)
                {
                    case '0': return 0;  // blank space
                    case '1': return 1;  // Function
                    case '2': return 2;  // Global
                    case '3': return 3;  // Local
                    case '4': return 4;  // Journal
                    case '5': return 5;  // Item
                    case '6': return 6;  // Dead
                    case '7': return 7;  // Not ID
                    case '8': return 8;  // Not Factio
                    case '9': return 9;  // Not Class
                    case 'A': return 10; // Not Race
                    case 'B': return 11; // Not Cell
                    case 'C': return 12; // Not Local
                    default: return QVariant(); // TODO: log an error?
                }
            }
            case 1:
            {
                if (conditions[subRowIndex].mSelectRule[1] == '1')
                {
                    // throws an exception if the encoding is not found
                    return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str();
                }
                else
                    return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str());
            }
            case 2:
            {
                char compType = conditions[subRowIndex].mSelectRule[4];
                switch (compType)
                {
                    case '0': return 3; // =
                    case '1': return 0; // !=
                    case '2': return 4; // >
                    case '3': return 5; // >=
                    case '4': return 1; // <
                    case '5': return 2; // <=
                    default: return QVariant(); // TODO: log an error?
                }
            }
            case 3:
            {
                switch (conditions[subRowIndex].mValue.getType())
                {
                    case ESM::VT_String:
                    {
                        return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str());
                    }
                    case ESM::VT_Int:
                    case ESM::VT_Short:
                    case ESM::VT_Long:
                    {
                        return conditions[subRowIndex].mValue.getInteger();
                    }
                    case ESM::VT_Float:
                    {
                        return conditions[subRowIndex].mValue.getFloat();
                    }
                    default: return QVariant();
                }
            }
            default: throw std::runtime_error("Info condition subcolumn index out of range");
        }
    }

    void InfoConditionAdapter::setData(Record<Info>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        Info info = record.get();

        std::vector<ESM::DialInfo::SelectStruct>& conditions = info.mSelects;

        if (subRowIndex < 0 || subRowIndex >= static_cast<int> (conditions.size()))
            throw std::runtime_error ("index out of range");

        switch (subColIndex)
        {
            case 0:
            {
                // See sInfoCondFunc in columns.cpp for the enum values
                switch (value.toInt())
                {
                    // FIXME: when these change the values of the other columns need to change
                    // correspondingly (and automatically)
                    case 1:
                    {
                        conditions[subRowIndex].mSelectRule[1] = '1';             // Function
                        // default to "Rank Low"
                        conditions[subRowIndex].mSelectRule[2] = '0';
                        conditions[subRowIndex].mSelectRule[3] = '0';
                        break;
                    }
                    case 2:  conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global
                    case 3:  conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local
                    case 4:  conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal
                    case 5:  conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item
                    case 6:  conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead
                    case 7:  conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID
                    case 8:  conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction
                    case 9:  conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class
                    case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race
                    case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell
                    case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local
                    default: return; // return without saving
                }
                break;
            }
            case 1:
            {
                if (conditions[subRowIndex].mSelectRule[1] == '1')
                {
                    std::map<const std::string, std::string>::const_iterator it = sEncToInfoFunc.begin();
                    for (;it != sEncToInfoFunc.end(); ++it)
                    {
                        if (it->second == value.toString().toUtf8().constData())
                        {
                            std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2);
                            rule.append(it->first);
                            // leave old values for undo (NOTE: may not be vanilla's behaviour)
                            rule.append(conditions[subRowIndex].mSelectRule.substr(4));
                            conditions[subRowIndex].mSelectRule = rule;
                            break;
                        }
                    }

                    if (it == sEncToInfoFunc.end())
                        return; // return without saving; TODO: maybe log an error here
                }
                else
                {
                    // FIXME: validate the string values before saving, based on the current function
                    std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5);
                    conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData());
                }
                break;
            }
            case 2:
            {
                // See sInfoCondComp in columns.cpp for the enum values
                switch (value.toInt())
                {
                    case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // !=
                    case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // <
                    case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <=
                    case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // =
                    case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // >
                    case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >=
                    default: return; // return without saving
                }
                break;
            }
            case 3:
            {
                switch (conditions[subRowIndex].mValue.getType())
                {
                    case ESM::VT_String:
                    {
                        conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData());
                        break;
                    }
                    case ESM::VT_Int:
                    case ESM::VT_Short:
                    case ESM::VT_Long:
                    {
                        conditions[subRowIndex].mValue.setInteger (value.toInt());
                        break;
                    }
                    case ESM::VT_Float:
                    {
                        conditions[subRowIndex].mValue.setFloat (value.toFloat());
                        break;
                    }
                    default: break;
                }
                break;
            }
            default: throw std::runtime_error("Info condition subcolumn index out of range");
        }

        record.setModified (info);
    }

    int InfoConditionAdapter::getColumnsCount(const Record<Info>& record) const
    {
        return 4;
    }

    int InfoConditionAdapter::getRowsCount(const Record<Info>& record) const
    {
        return static_cast<int>(record.get().mSelects.size());
    }

    RaceAttributeAdapter::RaceAttributeAdapter () {}

    void RaceAttributeAdapter::addRow(Record<ESM::Race>& record, int position) const
    {
        // Do nothing, this table cannot be changed by the user
    }

    void RaceAttributeAdapter::removeRow(Record<ESM::Race>& record, int rowToRemove) const
    {
        // Do nothing, this table cannot be changed by the user
    }

    void RaceAttributeAdapter::setTable(Record<ESM::Race>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        ESM::Race race = record.get();

        race.mData =
            static_cast<const NestedTableWrapper<std::vector<ESM::Race::RADTstruct> >&>(nestedTable).mNestedTable.at(0);

        record.setModified (race);
    }

    NestedTableWrapperBase* RaceAttributeAdapter::table(const Record<ESM::Race>& record) const
    {
        std::vector<typename ESM::Race::RADTstruct> wrap;
        wrap.push_back(record.get().mData);
        // deleted by dtor of NestedTableStoring
        return new NestedTableWrapper<std::vector<ESM::Race::RADTstruct> >(wrap);
    }

    QVariant RaceAttributeAdapter::getData(const Record<ESM::Race>& record,
            int subRowIndex, int subColIndex) const
    {
        ESM::Race race = record.get();

        if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length)
            throw std::runtime_error ("index out of range");

        switch (subColIndex)
        {
            case 0: return QString(ESM::Attribute::sAttributeNames[subRowIndex].c_str());
            case 1: return race.mData.mAttributeValues[subRowIndex].mMale;
            case 2: return race.mData.mAttributeValues[subRowIndex].mFemale;
            default: throw std::runtime_error("Race Attribute subcolumn index out of range");
        }
    }

    void RaceAttributeAdapter::setData(Record<ESM::Race>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        ESM::Race race = record.get();

        if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length)
            throw std::runtime_error ("index out of range");

        switch (subColIndex)
        {
            case 0: return; // throw an exception here?
            case 1: race.mData.mAttributeValues[subRowIndex].mMale = value.toInt(); break;
            case 2: race.mData.mAttributeValues[subRowIndex].mFemale = value.toInt(); break;
            default: throw std::runtime_error("Race Attribute subcolumn index out of range");
        }

        record.setModified (race);
    }

    int RaceAttributeAdapter::getColumnsCount(const Record<ESM::Race>& record) const
    {
        return 3; // attrib, male, female
    }

    int RaceAttributeAdapter::getRowsCount(const Record<ESM::Race>& record) const
    {
        return ESM::Attribute::Length; // there are 8 attributes
    }

    RaceSkillsBonusAdapter::RaceSkillsBonusAdapter () {}

    void RaceSkillsBonusAdapter::addRow(Record<ESM::Race>& record, int position) const
    {
        // Do nothing, this table cannot be changed by the user
    }

    void RaceSkillsBonusAdapter::removeRow(Record<ESM::Race>& record, int rowToRemove) const
    {
        // Do nothing, this table cannot be changed by the user
    }

    void RaceSkillsBonusAdapter::setTable(Record<ESM::Race>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        ESM::Race race = record.get();

        race.mData =
            static_cast<const NestedTableWrapper<std::vector<ESM::Race::RADTstruct> >&>(nestedTable).mNestedTable.at(0);

        record.setModified (race);
    }

    NestedTableWrapperBase* RaceSkillsBonusAdapter::table(const Record<ESM::Race>& record) const
    {
        std::vector<typename ESM::Race::RADTstruct> wrap;
        wrap.push_back(record.get().mData);
        // deleted by dtor of NestedTableStoring
        return new NestedTableWrapper<std::vector<ESM::Race::RADTstruct> >(wrap);
    }

    QVariant RaceSkillsBonusAdapter::getData(const Record<ESM::Race>& record,
            int subRowIndex, int subColIndex) const
    {
        ESM::Race race = record.get();

        if (subRowIndex < 0 || subRowIndex >= static_cast<int>(sizeof(race.mData.mBonus)/sizeof(race.mData.mBonus[0])))
            throw std::runtime_error ("index out of range");

        switch (subColIndex)
        {
            case 0: return race.mData.mBonus[subRowIndex].mSkill; // can be -1
            case 1: return race.mData.mBonus[subRowIndex].mBonus;
            default: throw std::runtime_error("Race skill bonus subcolumn index out of range");
        }
    }

    void RaceSkillsBonusAdapter::setData(Record<ESM::Race>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        ESM::Race race = record.get();

        if (subRowIndex < 0 || subRowIndex >= static_cast<int>(sizeof(race.mData.mBonus)/sizeof(race.mData.mBonus[0])))
            throw std::runtime_error ("index out of range");

        switch (subColIndex)
        {
            case 0: race.mData.mBonus[subRowIndex].mSkill = value.toInt(); break; // can be -1
            case 1: race.mData.mBonus[subRowIndex].mBonus = value.toInt(); break;
            default: throw std::runtime_error("Race skill bonus subcolumn index out of range");
        }

        record.setModified (race);
    }

    int RaceSkillsBonusAdapter::getColumnsCount(const Record<ESM::Race>& record) const
    {
        return 2; // skill, bonus
    }

    int RaceSkillsBonusAdapter::getRowsCount(const Record<ESM::Race>& record) const
    {
        // there are 7 skill bonuses
        return static_cast<int>(sizeof(record.get().mData.mBonus)/sizeof(record.get().mData.mBonus[0]));
    }

    CellListAdapter::CellListAdapter () {}

    void CellListAdapter::addRow(Record<CSMWorld::Cell>& record, int position) const
    {
        throw std::logic_error ("cannot add a row to a fixed table");
    }

    void CellListAdapter::removeRow(Record<CSMWorld::Cell>& record, int rowToRemove) const
    {
        throw std::logic_error ("cannot remove a row to a fixed table");
    }

    void CellListAdapter::setTable(Record<CSMWorld::Cell>& record,
            const NestedTableWrapperBase& nestedTable) const
    {
        throw std::logic_error ("table operation not supported");
    }

    NestedTableWrapperBase* CellListAdapter::table(const Record<CSMWorld::Cell>& record) const
    {
        throw std::logic_error ("table operation not supported");
    }

    QVariant CellListAdapter::getData(const Record<CSMWorld::Cell>& record,
            int subRowIndex, int subColIndex) const
    {
        CSMWorld::Cell cell = record.get();

        bool isInterior = (cell.mData.mFlags & ESM::Cell::Interior) != 0;
        bool behaveLikeExterior = (cell.mData.mFlags & ESM::Cell::QuasiEx) != 0;
        bool interiorWater = (cell.mData.mFlags & ESM::Cell::HasWater) != 0;

        switch (subColIndex)
        {
            case 0: return isInterior;
            case 1:
            {
                if (isInterior && !behaveLikeExterior)
                    return cell.mAmbi.mAmbient;
                else
                {
                    UserInt i(cell.mAmbi.mAmbient);
                    return QVariant(QVariant::fromValue(i));
                }
            }
            case 2:
            {
                if (isInterior && !behaveLikeExterior)
                    return cell.mAmbi.mSunlight;
                else
                {
                    UserInt i(cell.mAmbi.mSunlight);
                    return QVariant(QVariant::fromValue(i));
                }
            }
            case 3:
            {
                if (isInterior && !behaveLikeExterior)
                    return cell.mAmbi.mFog;
                else
                {
                    UserInt i(cell.mAmbi.mFog);
                    return QVariant(QVariant::fromValue(i));
                }
            }
            case 4:
            {
                if (isInterior && !behaveLikeExterior)
                    return cell.mAmbi.mFogDensity;
                else
                {
                    UserFloat f(cell.mAmbi.mFogDensity);
                    return QVariant(QVariant::fromValue(f));
                }
            }
            case 5:
            {
                if (isInterior && !behaveLikeExterior && interiorWater)
                    return cell.mWater;
                else
                {
                    UserFloat f(cell.mWater);
                    return QVariant(QVariant::fromValue(f));
                }
            }
            case 6:
            {
                if (isInterior)
                {
                    UserInt i(cell.mMapColor);
                    return QVariant(QVariant::fromValue(i));
                }
                else
                    return cell.mMapColor; // TODO: how to select?
            }
            //case 7: return isInterior ?
                    //behaveLikeExterior : QVariant(QVariant::UserType);
            default: throw std::runtime_error("Cell subcolumn index out of range");
        }
    }

    void CellListAdapter::setData(Record<CSMWorld::Cell>& record,
            const QVariant& value, int subRowIndex, int subColIndex) const
    {
        CSMWorld::Cell cell = record.get();

        bool isInterior = (cell.mData.mFlags & ESM::Cell::Interior) != 0;
        bool behaveLikeExterior = (cell.mData.mFlags & ESM::Cell::QuasiEx) != 0;
        bool interiorWater = (cell.mData.mFlags & ESM::Cell::HasWater) != 0;

        switch (subColIndex)
        {
            case 0:
            {
                if (value.toBool())
                    cell.mData.mFlags |= ESM::Cell::Interior;
                else
                    cell.mData.mFlags &= ~ESM::Cell::Interior;
                break;
            }
            case 1:
            {
                if (isInterior && !behaveLikeExterior)
                    cell.mAmbi.mAmbient = static_cast<int32_t>(value.toInt());
                else
                    return; // return without saving
                break;
            }
            case 2:
            {
                if (isInterior && !behaveLikeExterior)
                    cell.mAmbi.mSunlight = static_cast<int32_t>(value.toInt());
                else
                    return; // return without saving
                break;
            }
            case 3:
            {
                if (isInterior && !behaveLikeExterior)
                    cell.mAmbi.mFog = static_cast<int32_t>(value.toInt());
                else
                    return; // return without saving
                break;
            }
            case 4:
            {
                if (isInterior && !behaveLikeExterior)
                    cell.mAmbi.mFogDensity = value.toFloat();
                else
                    return; // return without saving
                break;
            }
            case 5:
            {
                if (isInterior && !behaveLikeExterior && interiorWater)
                    cell.mWater = value.toFloat();
                else
                    return; // return without saving
                break;
            }
            case 6:
            {
                if (!isInterior)
                    cell.mMapColor = value.toInt();
                else
                    return; // return without saving
                break;
            }
#if 0
            // redundant since this flag is shown in the main table as "Interior Sky"
            // keep here for documenting the logic based on vanilla
            case 7:
            {
                if (isInterior)
                {
                    if (value.toBool())
                        cell.mData.mFlags |= ESM::Cell::QuasiEx;
                    else
                        cell.mData.mFlags &= ~ESM::Cell::QuasiEx;
                }
                else
                    return; // return without saving
                break;
            }
#endif
            default: throw std::runtime_error("Cell subcolumn index out of range");
        }

        record.setModified (cell);
    }

    int CellListAdapter::getColumnsCount(const Record<CSMWorld::Cell>& record) const
    {
        return 7;
    }

    int CellListAdapter::getRowsCount(const Record<CSMWorld::Cell>& record) const
    {
        return 1; // fixed at size 1
    }
}