// $Id: gc_locus.cpp,v 1.36 2018/01/03 21:32:55 mkkuhner Exp $

/*
Copyright 2002-2005 Mary K. Kuhner, Peter Beerli, and Joseph Felsenstein

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

/* Authors: Mary Kuhner, Jon Yamato, and Joseph Felsenstein */


#include <cassert>

#include "gc_creation_info.h"
#include "gc_errhandling.h"
#include "gc_data.h"            // for ToWxString
#include "gc_default.h"
#include "gc_locus.h"
#include "gc_locus_err.h"
#include "gc_strings.h"
#include "gc_strings_locus.h"
#include "gc_structures_err.h"
#include "wx/log.h"
#include "wx/string.h"
#include "wx/tokenzr.h"

//------------------------------------------------------------------------------------

gcLocus::gcLocus()
    :
    m_blessed(false),
    m_hasRegion(false),
    m_regionId(gcdefault::badIndex),
    m_inducedByFile(false),
    m_fileId(gcdefault::badIndex),
    m_dataType(sdatatype_NONE_SET),
    m_hasNumMarkers(false),
    m_numMarkers(0),
    m_hasTotalLength(false),
    m_totalLength(0),
    m_hasLinkedUserValue(false),
    m_linkedUserValue(false),
    m_hasOffset(false),
    m_offset(1),
    m_hasMapPosition(false),
    m_mapPosition(1),
    m_hasUnphasedMarkers(false)
{
    assert(m_unphasedMarkers.NumMarkers() == 0);
    m_locations.clear();    // shouldn't be necessary
}

gcLocus::~gcLocus()
{
}

bool
gcLocus::GetBlessed() const
{
    return m_blessed;
}

void
gcLocus::DebugDump(wxString prefix) const
{
    wxLogDebug("%slocus %s (locus id %ld)", // EWDUMPOK
               prefix.c_str(),
               GetName().c_str(),
               (long)GetId());
    wxLogDebug("%sdatatype %s",(prefix+gcstr::indent).c_str(),ToWxString(GetDataType()).c_str());  // EWDUMPOK
    wxLogDebug("%s%s markers",(prefix+gcstr::indent).c_str(),GetNumMarkersString().c_str());  // EWDUMPOK
    wxLogDebug("%s%s total length",(prefix+gcstr::indent).c_str(),GetLengthString().c_str());   // EWDUMPOK
    wxLogDebug("%s%s offset",(prefix+gcstr::indent).c_str(),GetOffsetString().c_str()); // EWDUMPOK
    wxLogDebug("%s%s map position",(prefix+gcstr::indent).c_str(),GetMapPositionString().c_str());  // EWDUMPOK
    wxLogDebug("%slocations:%s",(prefix+gcstr::indent).c_str(),GetLocationsAsString().c_str()); // EWDUMPOK
    if(GetLinked())
    {
        wxLogDebug("%slinked",(prefix+gcstr::indent).c_str());  // EWDUMPOK
    }
    else
    {
        wxLogDebug("%sunlinked",(prefix+gcstr::indent).c_str());    // EWDUMPOK
    }
}

bool
gcLocus::HasLocations() const
{
    return !(m_locations.empty());
}

std::vector<long>
gcLocus::GetLocations() const
{
    return m_locations;
}

wxString
gcLocus::GetLocationsAsString() const
{
    wxString locationString = " ";
    for(size_t index=0; index < m_locations.size(); index++)
    {
        locationString += wxString::Format("%ld ",m_locations[index]);
    }
    return locationString;
}

bool
gcLocus::HasLocationZero() const
{
    for(size_t i = 0; i < m_locations.size(); i++)
    {
        long loc = m_locations[i];
        if(loc == 0) return true;
    }
    return false;
}

wxString
gcLocus::GetDataTypeString() const
{
    return ToWxString(m_dataType);
}

gcSpecificDataType
gcLocus::GetDataType() const
{
    return m_dataType;
}

void
gcLocus::SetDataType(gcSpecificDataType type)
{
    m_dataType = type;
}

bool
gcLocus::HasLength() const
{
    if(HasTotalLength())
    {
        return true;
    }
    if(GetDataType() == sdatatype_DNA)
    {
        return HasNumMarkers();
    }
    return false;
}

size_t
gcLocus::GetLength() const
{
    if(HasTotalLength())
    {
        return GetTotalLength();
    }
    if(HasNumMarkers())
    {
        if(GetDataType() == sdatatype_DNA)
        {
            return GetNumMarkers();
        }
        if(GetNumMarkers() == 1)
        {
            return 1;
        }
        else
        {
            throw gc_locus_without_length(GetName());
        }
    }
    return gcdefault::badLength;
}

wxString
gcLocus::GetLengthString() const
{
    if(HasLength())
    {
        return wxString::Format("%d",(int)GetLength());
    }
    return gcstr::unknown;
}

bool
gcLocus::HasTotalLength() const
{
    return m_hasTotalLength;
}

size_t
gcLocus::GetTotalLength() const
{
    assert(HasTotalLength());
    return m_totalLength;
}

wxString
gcLocus::GetTotalLengthString() const
{
    if(HasTotalLength())
    {
        return wxString::Format("%d",(int)GetTotalLength());
    }
    return gcstr::unknown;
}

void
gcLocus::SetTotalLength(size_t totalLength)
{
    if(HasNumMarkers())
    {
        if(totalLength < GetNumMarkers())
        {
            throw gc_locus_err(wxString::Format(gcerr::lengthTooShort,(int)totalLength,(int)GetNumMarkers()).c_str());
        }
    }
    m_hasTotalLength = true;
    m_totalLength = totalLength;
}

void
gcLocus::UnsetTotalLength()
{
    m_hasTotalLength = false;
}

bool
gcLocus::HasNumMarkers() const
{
    return m_hasNumMarkers;
}

size_t
gcLocus::GetNumMarkers() const
{
    if(!HasNumMarkers())
    {
        wxString msg = wxString::Format(gcerr_locus::unsetNumMarkers,GetName().c_str());
        throw gc_implementation_error(msg.c_str());
    }
    return m_numMarkers;
}

wxString
gcLocus::GetNumMarkersString() const
{
    if(HasNumMarkers())
    {
        return wxString::Format("%d",(int)GetNumMarkers());
    }
    return gcstr::unknown;
}

void
gcLocus::SetNumMarkers(size_t numMarkers)
{
    if(numMarkers == 0)
    {
        throw gc_num_markers_zero();
    }
    m_hasNumMarkers = true;
    m_numMarkers = numMarkers;
}

bool
gcLocus::HasOffset() const
{
    return m_hasOffset;
}

long
gcLocus::GetOffset() const
{
    if(!HasOffset())
    {
        wxString msg = wxString::Format(gcerr_locus::unsetOffset,GetName().c_str());
        throw gc_implementation_error(msg.c_str());
    }
    return m_offset;
}

wxString
gcLocus::GetOffsetString() const
{
    if(HasOffset())
    {
        return wxString::Format("%d",(int)GetOffset());
    }
    return gcstr::unknown;
}

void
gcLocus::UnsetNumMarkers()
{
    m_hasNumMarkers = false;
}

bool
gcLocus::HasRegion() const
{
    return m_hasRegion;
}

size_t
gcLocus::GetRegionId() const
{
    if(!HasRegion())
    {
        wxString msg = wxString::Format(gcerr::unsetRegionId,GetName().c_str());
        throw gc_implementation_error(msg.c_str());
    }
    return m_regionId;
}

void
gcLocus::SetFileId(size_t id)
{
    m_inducedByFile = true;
    m_fileId = id;
}

void
gcLocus::UnsetFileId()
{
    m_inducedByFile = false;
}

void
gcLocus::SetRegionId(size_t id)
{
    m_hasRegion = true;
    m_regionId = id;
}

void
gcLocus::UnsetRegionId()
{
    m_hasRegion = false;
}

bool
gcLocus::HasLinkedUserValue() const
{
    return m_hasLinkedUserValue;
}

bool
gcLocus::GetLinkedUserValue() const
{
    if(!HasLinkedUserValue())
    {
        wxString msg = wxString::Format(gcerr_locus::unsetLinkedUserValue,GetName().c_str());
        throw gc_implementation_error(msg.c_str());
    }
    return m_linkedUserValue;
}

bool
gcLocus::GetLinked() const
{
    if(HasLinkedUserValue())
    {
        return GetLinkedUserValue();
    }
    if(HasTotalLength())
    {
        return true;
    }
    if(HasLocations())
    {
        return true;
    }
    switch(GetDataType())
    {
        case sdatatype_NONE_SET:
            // EWFIX.P4 -- should be able to determine values
            // by reading data structures
            return true;
            break;
        case sdatatype_DNA:
        case sdatatype_SNP:
            return true;
            break;
        case sdatatype_KALLELE:
        case sdatatype_MICROSAT:
            return false;
            break;
    }
    assert(false);
    return true;
}

wxString
gcLocus::GetLinkedString() const
{
    bool val = GetLinked();
    if(val) return gcstr::linkageYes;
    return gcstr::linkageNo;
}

wxString
gcLocus::GetLinkedUserValueString() const
{
    if(HasLinkedUserValue())
    {
        bool val = GetLinkedUserValue();
        if(val) return gcstr::linkageYes;
        return gcstr::linkageNo;
    }
    else
    {
        return gcstr::unknown;
    }
}

void
gcLocus::SetLinkedUserValue(bool linked)
{
    if(! linked)
    {
        if(GetDataType() == sdatatype_DNA || GetDataType() == sdatatype_SNP)
        {
            throw gc_unlinked_nuc();
        }
    }
    m_hasLinkedUserValue = true;
    m_linkedUserValue = linked;
}

bool
gcLocus::HasMapPosition() const
{
    return m_hasMapPosition;
}

long
gcLocus::GetMapPosition() const
{
    if(!HasMapPosition())
    {
        wxString msg = wxString::Format(gcerr::unsetMapPosition,GetName().c_str());
        throw gc_implementation_error(msg.c_str());
    }
    return m_mapPosition;
}

wxString
gcLocus::GetMapPositionString() const
{
    if(HasMapPosition())
    {
        return wxString::Format("%d",(int)GetMapPosition());
    }
    return gcstr::unknown;
}

void
gcLocus::SetMapPosition(long mapPosition)
{
    // we don't test the value since during use of the
    // GUI, the user needs to pass through impossible
    // states
    m_hasMapPosition = true;
    m_mapPosition = mapPosition;
}

void
gcLocus::UnsetMapPosition()
{
    m_hasMapPosition = false;
}

bool
gcLocus::HasUnphasedMarkers() const
{
    return m_hasUnphasedMarkers;
}

const gcUnphasedMarkers *
gcLocus::GetUnphasedMarkers() const
{
    assert(HasUnphasedMarkers());
    return &m_unphasedMarkers;
}

wxString
gcLocus::GetUnphasedMarkersAsString() const
{
    assert(HasUnphasedMarkers());
    return m_unphasedMarkers.AsString();
}

void
gcLocus::SetUnphasedMarkers(gcUnphasedMarkers markers)
{
    m_hasUnphasedMarkers = true;
    m_unphasedMarkers = markers;
}

void
gcLocus::SetBlessed(bool blessed)
{
    m_blessed = blessed;
}

void
gcLocus::SetCreationInfo(const gcCreationInfo & creationInfo)
{
    m_creationInfo = creationInfo;
}

void
gcLocus::SetOffset(long offset)
{
    // we don't test the value since during use of the
    // GUI, the user needs to pass through impossible
    // states
    m_offset = offset;
    m_hasOffset = true;
}

void
gcLocus::SetLocations(wxString locationString)
{
    wxStringTokenizer tokenizer(locationString);
    m_locations.clear();
    while(tokenizer.HasMoreTokens())
    {
        wxString token = tokenizer.GetNextToken();
        long location;
        if(!token.ToLong(&location))
        {
            wxString msg = wxString::Format(gcerr::notALocation,locationString.c_str());
            throw gc_data_error(msg.c_str());
        }
        if(!m_locations.empty())
            // cannot check the first one!
        {
            long previous = m_locations.back();
            if(location <= previous)
            {
                throw gc_locations_out_of_order(GetName(),previous,location);
            }
        }
        m_locations.push_back(location);
    }
    if(HasNumMarkers())
    {
        if(m_locations.size() != GetNumMarkers())
        {
            throw gc_set_locations_err(GetName(),locationString,GetNumMarkers(),m_locations.size());
        }
    }
    else
    {
        SetNumMarkers(m_locations.size());
    }
}

void
gcLocus::LocusMergeLogic(bool doSettings, gcLocus & locusRef)
{
    // data type
    if(GetDataType() != sdatatype_NONE_SET)
    {
        if(locusRef.GetDataType() != sdatatype_NONE_SET)
        {
            if(locusRef.GetDataType() != GetDataType())
            {
                throw gc_locus_user_data_type_mismatch(GetName(),locusRef.GetName(),GetDataType(),locusRef.GetDataType());
            }
        }
    }

    // num Markers
    if(HasNumMarkers())
    {
        if(locusRef.HasNumMarkers())
        {
            if(locusRef.GetNumMarkers() != GetNumMarkers())
            {
                throw gc_locus_site_count_mismatch(GetName(),locusRef.GetName(),GetNumMarkers(),locusRef.GetNumMarkers());
            }
        }
    }

    // EWFIX.P3 -- this is probably not correct
    // total length
    if(HasTotalLength())
    {
        if(locusRef.HasTotalLength())
        {
            if(locusRef.GetLength() != GetLength())
            {
                throw gc_locus_length_mismatch(GetName(),locusRef.GetName(),GetLength(),locusRef.GetLength());
            }
        }
    }

    // user-set linked value
    if(HasLinkedUserValue())
    {
        if(locusRef.HasLinkedUserValue())
        {
            if(locusRef.GetLinkedUserValue() != GetLinkedUserValue())
            {
                throw gc_locus_user_linked_mismatch(GetName(),locusRef.GetName(),GetLinkedUserValue(),locusRef.GetLinkedUserValue());
            }
        }
    }

    // offset
    if(HasOffset())
    {
        if(locusRef.HasOffset())
        {
            if(locusRef.GetOffset() != GetOffset())
            {
                throw gc_locus_offset_mismatch(GetName(),locusRef.GetName(),GetOffset(),locusRef.GetOffset());
            }
        }
    }

    // map position
    if(HasMapPosition())
    {
        if(locusRef.HasMapPosition())
        {
            if(locusRef.GetMapPosition() != GetMapPosition())
            {
                throw gc_locus_map_position_mismatch(GetName(),locusRef.GetName(),GetMapPosition(),locusRef.GetMapPosition());
            }
        }
    }

    if(doSettings)
        // this is a real merge
    {
        // order is important -- setting data type and then num Markers can
        // result in setting the length
        if(GetDataType() == sdatatype_NONE_SET)             SetDataType(locusRef.GetDataType());
        if(!HasNumMarkers() && locusRef.HasNumMarkers())    SetNumMarkers(locusRef.GetNumMarkers());
        if(!HasTotalLength() && locusRef.HasTotalLength())  SetTotalLength(locusRef.GetTotalLength());
        if(!HasLinkedUserValue() && locusRef.HasLinkedUserValue()) SetLinkedUserValue(locusRef.GetLinkedUserValue());
        if(!HasOffset() && locusRef.HasOffset())            SetOffset(locusRef.GetOffset());
        if(!HasMapPosition() && locusRef.HasMapPosition())  SetMapPosition(locusRef.GetMapPosition());
    }
}

bool
gcLocus::CanMergeWith(gcLocus & locus)
{
    try
    {
        LocusMergeLogic(false,locus);
    }
    catch (const gc_locus_err& e)
    {
        return false;
    }
    return true;
}

void
gcLocus::MergeWith(gcLocus & locus)
{
    LocusMergeLogic(true,locus);
}

wxString
gcLocus::GetLongName() const
{
    return wxString::Format("%s %s",GetName().c_str(),m_creationInfo.GetDescriptiveName().c_str());
}

//____________________________________________________________________________________
