#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest
from pydna import _PydnaWarning

def test_orfs():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("atgaaattttaa")

    assert s.orfs(2) == (s,)

def test_cas9():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("gattcatgcatgtagcttacgtagtct")

    RNA = "catgcatgtagcttacgtag"

    (f1, f2), (f3,) = s.cas9(RNA)

    assert f1.seq == Dseqrecord("gattcatgcatgtagcttacg").seq
    assert f2.seq == Dseqrecord("tagtct").seq
    assert f3.seq == Dseqrecord("gattcatgcatgtagcttacgtagtct").seq

def test_FadiBakoura():
    from Bio.SeqFeature import SeqFeature, FeatureLocation
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord

    dseq_record = Dseqrecord(Dseq("ACTCTTTCTCTCTCT", circular=True))
    dseq_record.features = [SeqFeature(FeatureLocation(start=2, end=4))]
    # below assert fails (not anymore)
    assert len(dseq_record[6:1].features) == 0

def test_IPython_missing(monkeypatch):
    import IPython
    from pydna import dseqrecord

    # assert dseqrecord._display is IPython.display.display
    assert dseqrecord._display_html == IPython.display.display_html
    import sys
    import copy

    fakesysmodules = copy.copy(sys.modules)
    fakesysmodules["IPython.display"] = None
    monkeypatch.delitem(sys.modules, "IPython.display")
    monkeypatch.setattr("sys.modules", fakesysmodules)
    from importlib import reload

    reload(dseqrecord)
    from pydna import dseqrecord

    assert dseqrecord._display_html("item") == "item"
    # assert dseqrecord._HTML("item") == "item"

def test_initialization():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec

    a = []

    a.append(Dseqrecord("attt", id="1"))
    a.append(Dseqrecord(Dseq("attt"), id="2"))
    a.append(Dseqrecord(Seq("attt"), id="3"))
    a.append(Dseqrecord(Srec(Seq("attt"), id="4")))
    a.append(Dseqrecord(Dseqrecord("attt"), id="5"))
    a.append(Dseqrecord(Dseqrecord(Dseq("attt", circular=True)), id="6", circular=False))

    for b in a:
        assert type(b.seq) == Dseq
        assert str(b.seq.watson) == "attt"
        assert str(b.seq.crick) == "aaat"
        assert str(b.seq) == "attt"
        assert str(b.seq) == "attt"
        assert b.seq.circular == False
        assert b.circular == False

    a = []
    a.append(Dseqrecord("attt", circular=True))
    a.append(Dseqrecord(Dseq("attt"), circular=True))
    a.append(Dseqrecord(Seq("attt"), circular=True))
    a.append(Dseqrecord(Srec(Seq("attt")), circular=True))
    a.append(Dseqrecord(Dseqrecord("attt"), circular=True))

    for b in a:
        assert type(b.seq) == Dseq
        assert str(b.seq.watson) == "attt"
        assert str(b.seq.crick) == "aaat"
        assert str(b.seq) == "attt"
        assert str(b.seq) == "attt"
        assert b.circular == True
        assert b.seq.circular == True

    a = []
    a.append(Dseqrecord(Dseq("attt", circular=True), circular=True))
    a.append(Dseqrecord(Dseq("attt", circular=False), circular=True))
    a.append(Dseqrecord(Dseq("attt", circular=True), circular=False))
    a.append(Dseqrecord(Dseq("attt", circular=False), circular=False))

    circular = [True, True, False, False]

    for b, ci in zip(a, circular):
        assert type(b.seq) == Dseq
        assert str(b.seq.watson) == "attt"
        assert str(b.seq.crick) == "aaat"
        assert str(b.seq) == "attt"
        assert str(b.seq) == "attt"
        assert b.circular == ci
        assert b.seq.circular == ci

    a = []
    ds = Dseq("attt", "taaa")
    assert ds.ovhg == -1
    assert str(ds.watson) == "attt"
    assert str(ds.crick) == "taaa"

    #   attt
    #    aaat

    dsr = Dseqrecord(ds, circular=False)

    assert type(dsr.seq) == Dseq
    assert dsr.seq.watson == "attt"
    assert dsr.seq.crick == "taaa"
    assert dsr.circular == False
    assert dsr.seq.circular == False
    assert str(dsr.seq) == "attta"

    dsr = Dseqrecord(ds, circular=True)

    assert type(dsr.seq) == Dseq
    assert dsr.seq.watson == "attt"
    assert dsr.seq.crick == "aaat"
    assert dsr.circular == True
    assert dsr.seq.circular == True
    assert str(dsr.seq) == "attt"

    a = []
    ds = Dseq("attt", "caaa")
    assert ds.circular == False
    assert ds.ovhg == -1

    a.append(Dseqrecord(ds, circular=False))

    with pytest.raises(TypeError):
        Dseqrecord(ds, circular=True)

    with pytest.raises(TypeError):
        Dseqrecord(ds, circular=True)

    with pytest.raises(ValueError):
        b = Dseqrecord([])

    with pytest.raises(ValueError):
        b = Dseqrecord(("a",))

    with pytest.raises(ValueError):
        b = Dseqrecord(0)

    input = """
            LOCUS       New_DNA                    4 bp ds-DNA     linear       30-MAR-2013
            DEFINITION  .
            ACCESSION
            VERSION
            SOURCE      .
              ORGANISM  .
            COMMENT
            COMMENT     ApEinfo:methylated:1
            FEATURES             Location/Qualifiers
                 misc_feature    2..3
                                 /label=NewFeature
                                 /ApEinfo_fwdcolor=cyan
                                 /ApEinfo_revcolor=green
                                 /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                                 width 5 offset 0
            ORIGIN
                    1 acgt
            //
            """
    a = read(input)

    assert a.features[0].extract(a).seq.watson == "CG"

    b = a + a

    for f in b.features:
        assert b.features[0].extract(a).seq.watson == "CG"
    feature = a.features[0]

    s = Dseq("agctt", "agcta")
    # print s.fig()
    # Dseq(-6)
    # agctt
    # atcga
    b = Dseqrecord(s)
    b.features.append(feature)
    cb = Dseqrecord(b, circular=True)
    assert b.features[0].extract(b).seq.watson.lower() == cb.features[0].extract(b).seq.watson.lower()
    assert b.features[0].extract(b).seq.crick.lower() == cb.features[0].extract(b).seq.crick.lower()
    s = Dseq("aagct", "aagct")
    # print s.fig()
    # Dseq(-6)
    # aagct
    # tcgaa
    b = Dseqrecord(s)
    with pytest.raises(TypeError):
        cb = Dseqrecord(b, circular=True)

    s = Dseq("agctt", "agcta")
    # print s.fig()
    # Dseq(-6)
    # agcta
    # ttcga

    b = Dseqrecord(s)
    b.features.append(feature)
    cb = Dseqrecord(b, circular=True)
    assert b.features[0].extract(b).seq.watson.lower() == cb.features[0].extract(b).seq.watson.lower()
    assert b.features[0].extract(b).seq.crick.lower() == cb.features[0].extract(b).seq.crick.lower()

def test_linear_circular():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec

    """ test Dseqrecord linear & circular property"""
    a = Dseqrecord("attt")
    a.stamp("useguid")
    assert a.stamp("useguid")
    a = Dseqrecord("attt", circular=False)
    assert a.circular == False
    assert a.rc().circular == False

    a = Dseqrecord("attt", circular=True)

    assert a.circular == True
    assert a.rc().circular == True
    assert a.seq.circular == True

    a = Dseqrecord("attt", circular=True)

    assert a.circular == True
    assert a.rc().circular == True
    assert a.seq.circular == True

    a = Dseqrecord("attt", circular=False)

    assert a.circular == False
    assert a.rc().circular == False
    assert a.seq.circular == False

import pydna

def test_stamp():
    from pydna.dseqrecord import Dseqrecord

    lin = Dseqrecord("attt")
    assert lin.stamp("useguid") == "ot6JPLeAeMmfztW1736Kc6DAqlo"
    assert lin.stamp("useguid")[:35] == "uSEGUID ot6JPLeAeMmfztW1736Kc6DAqlo"

    crc = Dseqrecord("attt", circular=True)
    assert crc.stamp("cseguid") == "oopV-6158nHJqedi8lsshIfcqYA"
    crc.annotations["comment"] = "cSEGUID_oopV-6158nHJqedi8lsshIfcqYZ"
    with pytest.warns(_PydnaWarning):
        crc.stamp("cseguid")

    from pydna.dseq import Dseq

    blunt = Dseqrecord(Dseq("aa"))

    assert blunt.stamp("useguid") == "gBw0Jp907Tg_yX3jNgS4qQWttjU"

    staggered = Dseqrecord(Dseq("aa", "tta"))
    assert staggered.stamp("lseguid") == "qlQKjCD04EhPHtsdnEQ0DriOr10"

    staggered = Dseqrecord(Dseq("aa", "att"))
    assert staggered.stamp("lseguid") == "mvhPR1scFLQfgDn3olxegJmckgg"

    staggered = Dseqrecord(Dseq("aa", "atta"))
    assert staggered.stamp("lseguid") == "hPVQIzC3k29tDuAu_C5xEgLsFZs"

def test_revcomp():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec

    #      ----
    #     attcccgggg
    #     taagggcccc
    #
    #      ttcc
    #
    #
    #     ccccgggaat
    #     ggggccctta
    #          ----

    a = Dseqrecord("attcccgggg")

    a.add_feature(1, 5)

    assert str(a.features[0].extract(a).seq) == "ttcc"

    assert a.features[0].location.strand == 1

    rc = a.rc()

    assert str(rc.features[0].extract(rc).seq) == "ttcc"

    assert rc.features[0].location.strand == -1

    a = Dseqrecord("attcccgggg")

    a.add_feature(1, 5)

    a.features[0].location.strand = None

    rc = a.rc()

    assert rc.features[0].strand == None

def test_m():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("A" * 5000)
    assert f'{s.m():.3e}' == '1.544e-07'

def test_extract_feature():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa")
    s.add_feature(3, 9)
    s.extract_feature(0).seq == Dseqrecord("GGATCC").seq

def test_find():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa")
    assert s.find(Dseqrecord("ggatcc")) == 3
    assert s.find(Dseqrecord("ggatTc")) == -1
    s = Dseqrecord("tttGGATCCaaa", circular=True)
    assert s.find(Dseqrecord("aaattt")) == 9

def test_find_aa():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa")
    assert s.find_aa("FGSK") == slice(0, 12, None)
    assert s.find_aa("MMMM") == None

def test_str():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa")
    s.annotations = {"date": "03-JAN-2018"}
    assert (
        str(s)
        == "Dseqrecord\ncircular: False\nsize: 12\nID: id\nName: name\nDescription: description\nNumber of features: 0\n/date=03-JAN-2018\nDseq(-12)\ntttGGATCCaaa\naaaCCTAGGttt"
    )
    s = s.looped()
    assert (
        str(s)
        == "Dseqrecord\ncircular: True\nsize: 12\nID: id\nName: name\nDescription: description\nNumber of features: 0\n/date=03-JAN-2018\nDseq(o12)\ntttGGATCCaaa\naaaCCTAGGttt"
    )

def test___contains__():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa")
    assert "ggatcc" in s

def test_cseguid():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa")
    with pytest.raises(TypeError):
        s.cseguid()
    s = Dseqrecord("tttGGATCCaaa", circular=True)
    assert s.cseguid() == "54zt6fixMmNwFTsUVILdrdIRi2U"

def test_lseguid():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("tttGGATCCaaa", circular=True)
    with pytest.raises(TypeError):
        s.lseguid()
    s = Dseqrecord("tttGGATCCaaa")
    assert s.lseguid() == "qr7ePrqTrFyQt0fsTsiltN20oEk"

def test_format():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=True)
    s.format("gb")
    s.format("genbank")
    s = Dseqrecord("GGATCC", circular=False)
    s.format("gb")
    s.format("genbank")

    s = Dseqrecord("GGATCC", circular=True)
    s.format("fasta")
    s = Dseqrecord("GGATCC", circular=False)
    s.format("fasta")

def test_write():
    from unittest.mock import patch
    from unittest.mock import mock_open
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=True)
    m = mock_open()
    with patch("pydna.dseqrecord.open", m):
        s.write(filename="AAA.gb")
    m.mock_calls
    m.assert_called_once_with("AAA.gb", "w", encoding='utf8')
    handle = m()
    handle.write.assert_called_once_with(Dseqrecord("GGATCC", circular=True).format())

    m = mock_open()
    with patch("pydna.dseqrecord.open", m):
        s.write()
    m.mock_calls
    m.assert_called_once_with("name.gb", "w", encoding='utf8')
    handle = m()
    handle.write.assert_called_once_with(Dseqrecord("GGATCC", circular=True).format())

    with pytest.raises(TypeError):
        s.write(filename=123)

def test_write_same_seq_to_existing_file(monkeypatch):
    import builtins
    from unittest.mock import patch
    from unittest.mock import mock_open
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read

    s = Dseqrecord("Ggatcc", circular=True)

    monkeypatch.setattr("pydna.dseqrecord._os.path.isfile", lambda x: True)
    m = mock_open(read_data=s.format())

    with patch("builtins.open", m) as d:
        s.write(filename="Ggatcc.gb")

def test_write_different_file_to_existing_file(monkeypatch):
    import builtins
    from unittest.mock import patch
    from unittest.mock import mock_open
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read

    s = Dseqrecord("Ggatcc", circular=True)
    d = Dseqrecord("Ggatcn", circular=True)

    monkeypatch.setattr("pydna.dseqrecord._os.path.isfile", lambda x: True)
    monkeypatch.setattr("pydna.dseqrecord._os.rename", lambda x, y: True)
    m = mock_open(read_data=d.format())

    with patch("builtins.open", m) as d:
        s.write(filename="Ggatcc.gb")

def test_write_different_file_to_stamped_existing_file(monkeypatch):
    import builtins
    from unittest.mock import patch
    from unittest.mock import mock_open
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read

    new = Dseqrecord("Ggatcc", circular=True)
    new.stamp("cSEGUID")
    old = Dseqrecord("Ggatcc", circular=True)
    old.stamp("cSEGUID")

    assert new.description[:35] == old.description[:35]

    monkeypatch.setattr("pydna.dseqrecord._os.path.isfile", lambda x: True)
    monkeypatch.setattr("pydna.dseqrecord._os.rename", lambda x, y: True)
    m = mock_open(read_data=old.format())

    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    new = Dseqrecord("Ggatcc", circular=True)

    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    new.description = "cSEGUID_NNNNNNNNNNNNNNNNNNNNNNNNNNN_2018-06-01T05:05:51.778066"

    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    new.description = "cSEGUID_N"

    m = mock_open(read_data=old.format())
    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    assert m.called
    # m.write().assert_called_once_with(new.format())
    assert m.call_count == 6
    assert m.mock_calls[0]
    assert m.mock_calls[4]

def test_write_different_file_to_stamped_existing_file2(monkeypatch):
    import builtins
    from unittest.mock import patch
    from unittest.mock import mock_open
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read

    new = Dseqrecord("Ggatcc", circular=True)
    new.stamp("cSEGUID")
    old = Dseqrecord("Ggatcc", circular=True)
    old.stamp("cSEGUID")

    assert new.description[:35] == old.description[:35]

    monkeypatch.setattr("pydna.dseqrecord._os.path.isfile", lambda x: True)
    monkeypatch.setattr("pydna.dseqrecord._os.rename", lambda x, y: True)
    m = mock_open(read_data=old.format())

    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    new = Dseqrecord("Ggatcc", circular=True)

    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    new.description = "cSEGUID_NNNNNNNNNNNNNNNNNNNNNNNNNNN_2018-06-01T05:05:51.778066"

    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    new.description = "cSEGUID_N"

    m = mock_open(read_data=old.format())
    with patch("builtins.open", m) as d:
        new.write(filename="Ggatcc.gb")

    assert m.called
    # m.write().assert_called_once_with(new.format())
    assert m.call_count == 6
    assert m.mock_calls[0]
    assert m.mock_calls[4]

# [call('Ggatcc.gb', 'r', encoding='utf-8'),
# call().__enter__(),
# call().read(),
# call().__exit__(None, None, None),
# call('Ggatcc.gb', 'w'),
# call().__enter__(),
# call().write('LOCUS       name                       6 bp    DNA     circular UNK 01-JUN-2018\nDEFINITION  cSEGUID_N\n            cSEGUID_6WVYnCK97MOPMOlbLHvMnd4XIEY_2018-06-01T06:05:08.951398.\nACCESSION   id\nVERSION     id\nKEYWORDS    .\nSOURCE      .\n  ORGANISM  .\n            .\nFEATURES             Location/Qualifiers\nORIGIN\n        1 ggatcc\n//'),
# call().__exit__(None, None, None)]

# monkeypatch.setitem(readers.__builtins__, 'open', open)
# def test_write_to_existing_file():
#    from unittest.mock import patch
#    from unittest.mock import mock_open
#    from pydna.dseqrecord  import Dseqrecord
#    from pydna.readers     import read
#
#    s = Dseqrecord("GGATCC", circular=True)
#    m = mock_open()
#    m.read_data=Dseqrecord("GGATCC", circular=True).format()
#    with patch('pydna.dseqrecord.open', m):
#        s.write(filename="AAA.gb")
#    m.mock_calls
#    m.assert_called_once_with("AAA.gb", 'w')
#    handle = m()
#    handle.write.assert_called_once_with(Dseqrecord("GGATCC", circular=True).format())
#
#    s = Dseqrecord("GGATCC", circular=True)
#    m = mock_open(read_data = s.format())
#    with patch('pydna.readers.open')as d:
#        x=read("AAA.gb")
#
#    #"ABC.gb"
#    m.mock_calls
#    m.assert_called_once_with("AAA.gb", 'w')
#    handle = m()
#    handle.write.assert_called_once_with(Dseqrecord("GGATCC", circular=True).format())

def test_cut_args():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC")
    from Bio.Restriction import BamHI, BglII, RestrictionBatch

    rb = RestrictionBatch((BamHI, BglII))
    assert s.cut(BamHI)[0].seq == s.cut(BamHI + BglII)[0].seq == s.cut(rb)[0].seq
    assert s.cut(BamHI)[1].seq == s.cut(BamHI + BglII)[1].seq == s.cut(rb)[1].seq

def test_cut_circular():
    from pydna.dseqrecord import Dseqrecord
    from Bio.Restriction import BsaI, KpnI, Acc65I, NotI

    test = "aaaaaaGGTACCggtctcaaaa"

    for i in range(len(test)):
        nt = test[i:] + test[:i]

        a = Dseqrecord(nt, circular=True).cut(Acc65I)[0]
        assert a.seq.watson.upper() == "GTACCGGTCTCAAAAAAAAAAG"
        assert a.seq.crick.upper() == "GTACCTTTTTTTTTTGAGACCG"
        assert a.seq.ovhg == -4
        b = Dseqrecord(nt, circular=True).cut(KpnI)[0]
        assert b.seq.watson.upper() == "CGGTCTCAAAAAAAAAAGGTAC"
        assert b.seq.crick.upper() == "CTTTTTTTTTTGAGACCGGTAC"
        assert b.seq.ovhg == 4
        c = Dseqrecord(nt, circular=True).cut(BsaI)[0]
        assert c.seq.watson.upper() == "AAAAAAAAAGGTACCGGTCTCA"
        assert c.seq.crick.upper() == "TTTTTGAGACCGGTACCTTTTT"
        assert c.seq.ovhg == -4
        d = Dseqrecord(nt, circular=True).cut(NotI)
        assert d == ()

def test_cut_add():
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec

    from Bio.Seq import Seq
    from Bio.Restriction import BamHI, EcoRI, PstI, EcoRV, SmaI

    from Bio.SeqUtils.CheckSum import seguid

    a = Dseqrecord("GGATCCtcatctactatcatcgtagcgtactgatctattctgctgctcatcatcggtactctctataattatatatatatgcgcgtGGATCC").seq
    b = a.cut(BamHI)[1]
    c = Dseqrecord("nCTGCAGtcatctactatcatcgtagcgtactgatctattctgctgctcatcatcggtactctctataattatatatatatgcgcgtGAATTCn").seq
    f, d, l = c.cut((EcoRI, PstI))

    pUC19 = read("pUC19.gb")

    assert pUC19.circular == True

    assert len(pUC19) == 2686
    assert len(pUC19.seq.watson) == 2686
    assert len(pUC19.seq.crick) == 2686

    assert pUC19.seq.circular == True

    pUC19_SmaI = pUC19.cut(SmaI)
    assert len(pUC19_SmaI) == 1
    pUC19_SmaI = pUC19_SmaI[0]

    assert not pUC19_SmaI.circular
    assert len(pUC19_SmaI) == 2686

    pUC19_SmaI_a = pUC19_SmaI.seq + a

    assert not pUC19_SmaI_a.circular
    assert pUC19_SmaI_a.circular == False

    pUC19_SmaI_a = pUC19_SmaI_a.looped()
    assert len(pUC19_SmaI_a) == 2778

    assert pUC19_SmaI_a.circular
    assert eq(pUC19_SmaI_a, read("pUC19-SmaI-a.gb"))

    """ sticky end cloning """

    pUC19_BamHI = pUC19.cut(BamHI)

    assert len(pUC19_BamHI) == 1

    pUC19_BamHI = pUC19_BamHI[0].seq

    assert len(pUC19_BamHI.watson) == len(pUC19_BamHI.crick) == 2686

    pUC19_BamHI_a = pUC19_BamHI + b

    assert len(pUC19_BamHI_a.watson) == len(pUC19_BamHI_a.crick) == 2772

    assert pUC19_BamHI_a.circular == False

    pUC19_BamHI_a = pUC19_BamHI_a.looped()

    assert pUC19_BamHI_a.circular == True

    assert eq(pUC19_BamHI_a, read("pUC19-BamHI-a.gb"))

    pUC19_BamHI_a_rc = pUC19_BamHI + b.rc()

    pUC19_BamHI_a_rc = pUC19_BamHI_a_rc.looped()

    assert pUC19_BamHI_a.circular == True

    assert eq(pUC19_BamHI_a_rc, read("pUC19-BamHI-a-rc.gb"))

    """ adding (ligating) dsDNA objects """
    with pytest.raises(TypeError) as excinfo:
        pUC19 + a
    assert "circular" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        a + pUC19
    assert "circular" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        a + b
    assert "compatible" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        b + a
    assert "compatible" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        d + d
    assert "compatible" in str(excinfo.value)

    """ directional cloning """

    pUC19_EcoRI_PstI = pUC19.cut(EcoRI, PstI)[1]

    with pytest.raises(TypeError) as excinfo:
        pUC19_EcoRI_PstI + d
    assert "compatible" in str(excinfo.value)

    pUC19_EcoRI_PstI_d = pUC19_EcoRI_PstI + d.rc()

    pUC19_EcoRI_PstI_d = pUC19_EcoRI_PstI_d.looped()

    assert eq(pUC19_EcoRI_PstI_d, read("pUC19-EcoRI_PstI-d-rc.gb"))
    assert eq(pUC19_EcoRI_PstI_d.rc(), read("pUC19-EcoRI_PstI-d-rc.gb"))

    from Bio.Restriction import Bsu36I, BstAPI

    pCAPs = read("pCAPs.gb")
    a, b = pCAPs.cut(Bsu36I, BstAPI)
    c = (a + b).looped()
    assert eq(c, pCAPs)

def test_Dseqrecord_cutting_adding_2():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec

    a = (
        Dseqrecord(
            Dseq(
                "AATTCACANGGTACCNGGTACCNGCGGATATC",
                "GTGTNCCATGGNCCATGGNCGCCTATAG"[::-1],
                -4,
            )
        ),
        Dseqrecord(Dseq("CACANGGTACCNGGTACCNGCGGATATC", "GTGTNCCATGGNCCATGGNCGCCTATAG"[::-1], 0)),
        Dseqrecord(
            Dseq(
                "CACANGGTACCNGGTACCNGCGGATATC",
                "AATTGTGTNCCATGGNCCATGGNCGCCTATAG"[::-1],
                4,
            )
        ),
    )

    from Bio.Restriction import KpnI, Acc65I, NlaIV

    enzymes = [Acc65I, NlaIV, KpnI]

    for enz in enzymes:
        for f in a:
            b, c, d = f.cut(enz)
            e = b + c + d
            assert str(e.seq).lower() == str(f.seq).lower()

def test_Dseqrecord_cutting_adding_3():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec
    from Bio.Restriction import KpnI, BamHI, Acc65I, NlaIV, EcoRI, EcoRV

    a = read(
        """

LOCUS       New_DNA                   10 bp ds-DNA     linear       02-APR-2013
DEFINITION
ACCESSION   New_DNA
VERSION     New_DNA
KEYWORDS    .
SOURCE
  ORGANISM  . .
COMMENT
COMMENT     ApEinfo:methylated:1
FEATURES             Location/Qualifiers
     misc_feature    1..1
                     /label=1
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    2..2
                     /label=2
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    3..3
                     /label=3
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    4..4
                     /label=4
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    5..5
                     /label=5
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    6..6
                     /label=6
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    7..7
                     /label=7
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    8..8
                     /label=8
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    9..9
                     /label=9
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    10..10
                     /label=10
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
ORIGIN
        1 ttGGTACCgg
//"""
    )

    b, c = a.cut(Acc65I)

    assert [f.qualifiers["label"] for f in b.features] == [
        ["1"],
        ["2"],
        ["3"],
        ["4"],
        ["5"],
        ["6"],
        ["7"],
    ]
    assert [f.qualifiers["label"] for f in c.features] == [
        ["4"],
        ["5"],
        ["6"],
        ["7"],
        ["8"],
        ["9"],
        ["10"],
    ]

def test_Dseqrecord_cutting_adding_4():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec
    from Bio.Restriction import KpnI, Acc65I, NlaIV, EcoRI, EcoRV

    a = read(
        """
LOCUS       New_DNA                   33 bp ds-DNA     linear       08-NOV-2012
DEFINITION  .
ACCESSION   .
VERSION     .
SOURCE      .
  ORGANISM  .
COMMENT
COMMENT     ApEinfo:methylated:1
FEATURES             Location/Qualifiers
     misc_feature    1..11
                     /label=Acc65I-1
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    12..18
                     /label=Acc65I-2
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    19..33
                     /label=Acc65I-3
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    1..15
                     /label=KpnI-1
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    16..22
                     /label=KpnI-2
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    23..33
                     /label=KpnI-3
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    1..13
                     /label=NlaIV-1
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    14..20
                     /label=NlaIV-2
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
     misc_feature    21..33
                     /label=NlaIV-3
                     /ApEinfo_fwdcolor=cyan
                     /ApEinfo_revcolor=green
                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                     width 5 offset 0
ORIGIN
        1 GAATTCacan ggtaccnGGT ACCngcgGAT ATC
//

    """
    )

    assert a.lseguid() == "di3hL8t2G4iQQsxlm_CtvnUMBz8"

    assert [x.qualifiers["label"][0] for x in a.features] == [
        "Acc65I-1",
        "Acc65I-2",
        "Acc65I-3",
        "KpnI-1",
        "KpnI-2",
        "KpnI-3",
        "NlaIV-1",
        "NlaIV-2",
        "NlaIV-3",
    ]

    b, c, d = a.cut(Acc65I)

    assert [x.qualifiers["label"][0] for x in b.features] == [
        "Acc65I-1",
        "KpnI-1",
        "NlaIV-1",
    ]
    assert [x.qualifiers["label"][0] for x in c.features] == [
        "Acc65I-2",
        "KpnI-2",
        "NlaIV-2",
    ]
    assert [x.qualifiers["label"][0] for x in d.features] == [
        "Acc65I-3",
        "KpnI-3",
        "NlaIV-3",
    ]
    e = b + c + d
    assert sorted([x.qualifiers["label"][0] for x in e.features]) == [x.qualifiers["label"][0] for x in a.features]
    assert str(a.seq) == str(e.seq)

    b, c, d = a.cut(KpnI)
    assert [x.qualifiers["label"][0] for x in b.features] == [
        "Acc65I-1",
        "KpnI-1",
        "NlaIV-1",
    ]
    assert [x.qualifiers["label"][0] for x in c.features] == [
        "Acc65I-2",
        "KpnI-2",
        "NlaIV-2",
    ]  # ???
    assert [x.qualifiers["label"][0] for x in d.features] == [
        "Acc65I-3",
        "KpnI-3",
        "NlaIV-3",
    ]
    e = b + c + d
    assert sorted([x.qualifiers["label"][0] for x in e.features]) == [x.qualifiers["label"][0] for x in a.features]

    b, c, d = a.cut(NlaIV)
    assert [x.qualifiers["label"][0] for x in b.features] == ["Acc65I-1", "NlaIV-1"]
    assert [x.qualifiers["label"][0] for x in c.features] == ["NlaIV-2"]
    assert [x.qualifiers["label"][0] for x in d.features] == ["KpnI-3", "NlaIV-3"]
    e = b + c + d
    assert str(a.seq) == str(e.seq)

    b, c = a.cut(EcoRI)
    e = b + c
    assert str(a.seq) == str(e.seq)

    b, c = a.cut(EcoRV)
    e = b + c
    assert str(a.seq) == str(e.seq)

    b, c, d = a.cut(EcoRI, EcoRV)
    e = b + c + d

    assert str(a.seq) == str(e.seq)

    b, c, d, f = a.cut(Acc65I, EcoRI)
    e = b + c + d + f
    assert str(a.seq) == str(e.seq)

    b, c, d, f = a.cut(EcoRI, Acc65I)
    e = b + c + d + f
    assert str(a.seq) == str(e.seq)

def test_features_on_slice():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from Bio.SeqFeature import SeqFeature
    from Bio.SeqFeature import SimpleLocation

    dseq_record = Dseqrecord(Dseq("ACTCTTTCTCTCTCT", circular=True))
    dseq_record.features = [SeqFeature(SimpleLocation(start=2, end=4))]
    assert len(dseq_record[6:1].features) == 0
    assert len(dseq_record[6:3].features) == 0
    assert len(dseq_record[6:4].features) == 1
    assert len(dseq_record[6:5].features) == 1
    assert len(dseq_record[:].features) == 1

    dseq_record2 = Dseqrecord(Dseq("ACTCTTTCTCTCTCT"))
    assert dseq_record2[6:1].seq == Dseq("")

def test_features_change_ori():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    s1 = read(
        """
        LOCUS       New_DNA                   13 bp ds-DNA     circular     08-JAN-2018
        DEFINITION  .
        SOURCE      .
        COMMENT     feature copied in ApE version 3.1.2 yields: tcccgtttt
        COMMENT     ApEinfo:methylated:1
        FEATURES             Location/Qualifiers
             misc_feature    join(2..3,5..7,9..12)
                             /locus_tag="hej"
                             /label="hej"
                             /ApEinfo_label="hej"
                             /ApEinfo_fwdcolor="cyan"
                             /ApEinfo_revcolor="green"
                             /ApEinfo_graphicformat="arrow_data {{0 1 2 0 0 -1} {} 0}
                             width 5 offset 0"
        ORIGIN
                1 atcaccgatt tta
        //"""
    )

    for i in range(1, len(s1)):
        b = s1.shifted(i)
        assert str(b.features[0].extract(b).seq).lower() == "tcccgtttt"

    s2 = read(
        """
        LOCUS       New_DNA                   13 bp ds-DNA     circular     08-JAN-2018
        DEFINITION  .
        SOURCE      .
        COMMENT
        COMMENT     feature copied in ApE version 3.1.2 yields: aaaacggga
        COMMENT     should be tcccgtttt, since feature is on the other strand
        COMMENT     ApEinfo:methylated:1
        FEATURES             Location/Qualifiers
             misc_feature    complement(join(2..5,7..9,11..12))
                             /locus_tag="hej"
                             /label="hej"
                             /ApEinfo_label="hej"
                             /ApEinfo_fwdcolor="cyan"
                             /ApEinfo_revcolor="green"
                             /ApEinfo_graphicformat="arrow_data {{0 1 2 0 0 -1} {} 0}
                             width 5 offset 0"
        ORIGIN
                1 taaaatcggt gat
        //

        """
    )

    assert str(s2.features[0].extract(s2).seq).lower() == "tcccgtttt"

    for i in range(0, len(s2)):
        b = s2.shifted(i)
        assert str(b.features[0].extract(b).seq).lower() == "tcccgtttt"

    # for x in b.features[0].location.parts:
    #     print(x.extract(b.seq))

    # from pydna.utils import shift_location
    # from Bio.SeqFeature import SimpleLocation
    # from Bio.SeqFeature import CompoundLocation
    # from Bio.SeqFeature import ExactPosition

    # ml6 = s2.shifted(6).features[0].location
    # ml7 = s2.shifted(7).features[0].location

    # ccc = shift_location(s2.features[0].location, -6, 13)
    # ddd = shift_location(s2.features[0].location, -7, 13)

    # print(b.seq, b.features[0].extract(b).seq, b.features[0].location)

    s3 = read(
        """
            LOCUS       New_DNA                   21 bp ds-DNA     circular     03-APR-2013
            DEFINITION  a
            ACCESSION
            VERSION
            SOURCE      .
              ORGANISM  .
            COMMENT
            COMMENT     ApEinfo:methylated:1
            FEATURES             Location/Qualifiers
                 misc_feature    join(18..21,1..4)
                                 /label=bb
                                 /ApEinfo_fwdcolor=cyan
                                 /ApEinfo_revcolor=green
                                 /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                                 width 5 offset 0
                 misc_feature    5..17
                                 /label=ins
                                 /ApEinfo_fwdcolor=#e03c2b
                                 /ApEinfo_revcolor=green
                                 /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                                 width 5 offset 0
            ORIGIN
                    1 aaagGTACCT TTGGATCcggg
            //
        """
    )

    bbfeat = Dseq.from_representation("cgggaaag\n" "gccctttc")
    insfeat = Dseq.from_representation("GTACCTTTGGATC\n" "CATGGAAACCTAG")

    assert str(s3.features[0].extract(s3).seq) == str(bbfeat).upper()  # bb
    assert str(s3.features[1].extract(s3).seq) == str(insfeat).upper()  # ins
    assert s3.features[0].extract(s3).seq == bbfeat  # bb
    assert s3.features[1].extract(s3).seq == insfeat  # ins

    for i in range(1, len(s3)):
        b = s3.shifted(i)

        assert [str(f.extract(b).seq) for f in b.features if f.qualifiers["label"][0] == "ins"][0] == "GTACCTTTGGATC"
        assert [str(f.extract(b).seq) for f in b.features if f.qualifiers["label"][0] == "bb"][0] == "CGGGAAAG"

    from Bio.Restriction import Acc65I, BamHI

    inseq = Dseq.from_representation("GTACCTTTG\n" "    GAAACCTAG")

    bbseq = Dseq.from_representation("GATCCGGGAAAG\n" "    GCCCTTTCCATG")

    assert s3.seq.cut(Acc65I, BamHI) == (inseq, bbseq)

    bb1, ins1 = sorted(s3.cut(Acc65I, BamHI), key=len, reverse=True)

    assert str(bbfeat).upper() in bbseq
    assert str(insfeat).upper() in inseq

    for i in range(0, len(s3)):
        b = s3.shifted(i)

        """        11
                   |
        ATCcgggaaagGTACCTTTGG
        taggccctttccatggaaacc

                   GTACCTTTGGATCCGGGAAAG
                       GAAACCTAGGCCCTTTCCATG

                   GTACCTTTG
                       GAAACCTAG

                            GATCCGGGAAAG
                                GCCCTTTCCATG

        """

        bb, ins = sorted(b.cut(Acc65I, BamHI), key=len, reverse=True)

        assert eq(bb1, bb)
        assert eq(ins1, ins)

        assert bb.features[0].extract(bb).seq == bbfeat
        assert str(ins.features[0].extract(ins).seq) == str(insfeat)

def test_amijalis():
    # Thanks to https://github.com/amijalis
    from pydna.dseqrecord import Dseqrecord

    test_seq = 'ATCGATCGATCGATCGATCGATCGATCGATCGATCG'

    # length of test_seq is 36.

    test_seq_dseqrecord = Dseqrecord(test_seq).looped()
    test_seq_dseqrecord.add_feature(0, 36, type_='test', label='test')

    # print(f'Features before shifting: {test_seq_dseqrecord.features}')

    f1 = test_seq_dseqrecord.features[0]

    test_seq_shifted = test_seq_dseqrecord.shifted(10)

    f2 = test_seq_shifted.features[0]

    assert f1.extract(test_seq_dseqrecord).seq == f2.extract(test_seq_shifted).seq

    # print(f'Features after shifting: {test_seq_shifted.features}')

def test_figure():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from Bio.Restriction import Acc65I, KpnI, ApaI, Bsp120I
    from Bio.SeqFeature import SimpleLocation
    from Bio.SeqFeature import SeqFeature

    # broken feature linear

    linearDseq = Dseq.from_representation(
        """
    gatcggtaccgatcATGAAATAAgatcGGGCCCgatc
    ctagccatggctagTACTTTATTctagCCCGGGctag
    """
    )

    linearDseqrecord = Dseqrecord(linearDseq)

    assert (
        linearDseqrecord.figure()
        == 'Dseqrecord(-37)\n\x1b[48;5;11m\x1b[0mgatcggtaccgatcATGAAATAAgatcGGGCCCgatc\nctagccatggctagTACTTTATTctagCCCGGGctag'
    )

    linearDseqrecord.features.append(SeqFeature(SimpleLocation(14, 17, 1) + SimpleLocation(20, 23, 1), type="test"))

    expect = 'Dseqrecord(-37)\ngatcggtaccgatc\x1b[48;5;11mATG\x1b[0mAAA\x1b[48;5;11mTAA\x1b[0mgatcGGGCCCgatc\nctagccatggctagTACTTTATTctagCCCGGGctag'
    assert linearDseqrecord.figure() == expect

    # short feature linear

    linearDseqrecord = Dseqrecord(linearDseq)
    linearDseqrecord.add_feature(14, 23)

    assert (
        linearDseqrecord.figure()
        == 'Dseqrecord(-37)\ngatcggtaccgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcGGGCCCgatc\nctagccatggctagTACTTTATTctagCCCGGGctag'
    )

    feat = Dseq("ATGAAATAA")
    assert linearDseqrecord.features[0].extract(linearDseqrecord).seq == feat

    a1, b1, c1 = linearDseqrecord.cut(Acc65I, Bsp120I)
    assert (
        b1.figure() == 'Dseqrecord(-27)\ngtaccgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcG    \n    gctagTACTTTATTctagCCCGG'
    )
    assert b1.extract_feature(0).seq == feat
    assert b1.rc().extract_feature(0).seq == feat
    assert (
        b1.rc().figure()
        == 'Dseqrecord(-27)\nGGCCCgatcTTATTTCATgatcg    \n    Gctag\x1b[48;5;11mAATAAAGTA\x1b[0mctagccatg'
    )

    a2, b2, c2 = linearDseqrecord.cut(KpnI, Bsp120I)
    assert (
        b2.figure() == 'Dseqrecord(-27)\n    cgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcG    \ncatggctagTACTTTATTctagCCCGG'
    )
    assert b2.extract_feature(0).seq == feat
    assert b2.rc().extract_feature(0).seq == feat
    assert (
        b2.rc().figure()
        == 'Dseqrecord(-27)\nGGCCCgatcTTATTTCATgatcggtac\n    Gctag\x1b[48;5;11mAATAAAGTA\x1b[0mctagc    '
    )

    a3, b3, c3 = linearDseqrecord.cut(Acc65I, ApaI)
    assert (
        b3.figure() == 'Dseqrecord(-27)\ngtaccgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcGGGCC\n    gctagTACTTTATTctagC    '
    )
    assert b3.extract_feature(0).seq == feat
    assert b3.rc().extract_feature(0).seq == feat
    assert (
        b3.rc().figure()
        == 'Dseqrecord(-27)\n    CgatcTTATTTCATgatcg    \nCCGGGctag\x1b[48;5;11mAATAAAGTA\x1b[0mctagccatg'
    )

    a4, b4, c4 = linearDseqrecord.cut(KpnI, ApaI)
    assert (
        b4.figure() == 'Dseqrecord(-27)\n    cgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcGGGCC\ncatggctagTACTTTATTctagC    '
    )
    assert b4.extract_feature(0).seq == feat
    assert b4.rc().extract_feature(0).seq == feat
    assert (
        b4.rc().figure()
        == 'Dseqrecord(-27)\n    CgatcTTATTTCATgatcggtac\nCCGGGctag\x1b[48;5;11mAATAAAGTA\x1b[0mctagc    '
    )

    # short feature circular

    circularDseqrecord = linearDseqrecord.looped()
    assert (
        circularDseqrecord.figure()
        == 'Dseqrecord(o37)\ngatcggtaccgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcGGGCCCgatc\nctagccatggctagTACTTTATTctagCCCGGGctag'
    )
    assert circularDseqrecord.features[0].extract(circularDseqrecord).seq == feat

    a5, b5 = circularDseqrecord.cut(Acc65I, Bsp120I)
    assert (
        a5.figure() == 'Dseqrecord(-27)\ngtaccgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcG    \n    gctagTACTTTATTctagCCCGG'
    )
    assert a5.extract_feature(0).seq == feat

    a6, b6 = circularDseqrecord.cut(KpnI, Bsp120I)
    assert (
        a6.figure() == 'Dseqrecord(-27)\n    cgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcG    \ncatggctagTACTTTATTctagCCCGG'
    )
    assert a6.extract_feature(0).seq == feat

    a7, b7 = circularDseqrecord.cut(Acc65I, ApaI)
    assert (
        a7.figure() == 'Dseqrecord(-27)\ngtaccgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcGGGCC\n    gctagTACTTTATTctagC    '
    )
    assert a7.extract_feature(0).seq == feat

    a8, b8 = circularDseqrecord.cut(KpnI, ApaI)
    assert (
        a8.figure() == 'Dseqrecord(-27)\n    cgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcGGGCC\ncatggctagTACTTTATTctagC    '
    )
    assert a8.extract_feature(0).seq == feat

    a9, b9 = circularDseqrecord.cut(Bsp120I, KpnI)
    assert (
        a9.figure() == 'Dseqrecord(-27)\n    cgatc\x1b[48;5;11mATGAAATAA\x1b[0mgatcG    \ncatggctagTACTTTATTctagCCCGG'
    )
    assert a9.extract_feature(0).seq == feat

    # longer feature linear

    linearDseqrecord = Dseqrecord(linearDseq)
    linearDseqrecord.add_feature(5, 32)
    assert (
        linearDseqrecord.figure()
        == 'Dseqrecord(-37)\ngatcg\x1b[48;5;11mgtaccgatcATGAAATAAgatcGGGCC\x1b[0mCgatc\nctagccatggctagTACTTTATTctagCCCGGGctag'
    )
    feat = Dseq.from_representation(
        """
    gtaccgatcATGAAATAAgatcGGGCC
    catggctagTACTTTATTctagCCCGG
    """
    )
    assert linearDseqrecord.features[0].extract(linearDseqrecord).seq == feat

    a10, b10, c10 = linearDseqrecord.cut(Acc65I, Bsp120I)
    assert (
        b10.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11mgtaccgatcATGAAATAAgatcG    \x1b[0m\n    gctagTACTTTATTctagCCCGG'
    )
    feat10 = Dseq.from_representation(
        """
    gtaccgatcATGAAATAAgatcG
        gctagTACTTTATTctagCCCGG
    """
    )
    assert b10.extract_feature(0).seq == feat10
    assert b10.rc().extract_feature(0).seq == feat10
    assert (
        b10.rc().figure()
        == 'Dseqrecord(-27)\nGGCCCgatcTTATTTCATgatcg    \n\x1b[48;5;11m    GctagAATAAAGTActagccatg\x1b[0m'
    )

    a11, b11, c11 = linearDseqrecord.cut(KpnI, Bsp120I)
    assert (
        b11.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11m    cgatcATGAAATAAgatcG    \x1b[0m\ncatggctagTACTTTATTctagCCCGG'
    )
    feat11 = Dseq.from_representation(
        """
        cgatcATGAAATAAgatcG
    catggctagTACTTTATTctagCCCGG
    """
    )
    assert b11.extract_feature(0).seq == feat11
    assert b11.rc().extract_feature(0).seq == feat11
    assert (
        b11.rc().figure()
        == 'Dseqrecord(-27)\nGGCCCgatcTTATTTCATgatcggtac\n\x1b[48;5;11m    GctagAATAAAGTActagc    \x1b[0m'
    )

    a12, b12, c12 = linearDseqrecord.cut(Acc65I, ApaI)
    assert (
        b12.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11mgtaccgatcATGAAATAAgatcGGGCC\x1b[0m\n    gctagTACTTTATTctagC    '
    )
    feat12 = Dseq.from_representation(
        """
    gtaccgatcATGAAATAAgatcGGGCC
        gctagTACTTTATTctagC
    """
    )
    assert b12.extract_feature(0).seq == feat12
    assert b12.rc().extract_feature(0).seq == feat12
    assert (
        b12.rc().figure()
        == 'Dseqrecord(-27)\n    CgatcTTATTTCATgatcg    \n\x1b[48;5;11mCCGGGctagAATAAAGTActagccatg\x1b[0m'
    )

    a13, b13, c13 = linearDseqrecord.cut(KpnI, ApaI)
    assert (
        b13.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11m    cgatcATGAAATAAgatcGGGCC\x1b[0m\ncatggctagTACTTTATTctagC    '
    )
    feat13 = Dseq.from_representation(
        """
        cgatcATGAAATAAgatcGGGCC
    catggctagTACTTTATTctagC
    """
    )
    assert b13.extract_feature(0).seq == feat13
    assert b13.rc().extract_feature(0).seq == feat13
    assert (
        b13.rc().figure()
        == 'Dseqrecord(-27)\n    CgatcTTATTTCATgatcggtac\n\x1b[48;5;11mCCGGGctagAATAAAGTActagc    \x1b[0m'
    )

    # longer feature circular

    circularDseqrecord = linearDseqrecord.looped()
    circularDseqrecord.figure()
    a14, b14 = circularDseqrecord.cut(KpnI, Bsp120I)
    assert (
        a14.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11m    cgatcATGAAATAAgatcG    \x1b[0m\ncatggctagTACTTTATTctagCCCGG'
    )
    feat14 = Dseq.from_representation(
        """
        cgatcATGAAATAAgatcG
    catggctagTACTTTATTctagCCCGG
    """
    )
    assert a14.extract_feature(0).seq == feat14

    a15, b15 = circularDseqrecord.cut(KpnI, ApaI)
    assert (
        a15.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11m    cgatcATGAAATAAgatcGGGCC\x1b[0m\ncatggctagTACTTTATTctagC    '
    )
    feat15 = Dseq.from_representation(
        """
        cgatcATGAAATAAgatcGGGCC
    catggctagTACTTTATTctagC
    """
    )
    assert a15.extract_feature(0).seq == feat15

    a16, b16 = circularDseqrecord.cut(Acc65I, Bsp120I)
    assert (
        a16.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11mgtaccgatcATGAAATAAgatcG    \x1b[0m\n    gctagTACTTTATTctagCCCGG'
    )
    feat16 = Dseq.from_representation(
        """
    gtaccgatcATGAAATAAgatcG
        gctagTACTTTATTctagCCCGG
    """
    )
    assert a16.extract_feature(0).seq == feat16

    a17, b17 = circularDseqrecord.cut(Acc65I, ApaI)
    assert (
        a17.figure() == 'Dseqrecord(-27)\n\x1b[48;5;11mgtaccgatcATGAAATAAgatcGGGCC\x1b[0m\n    gctagTACTTTATTctagC    '
    )
    feat17 = Dseq.from_representation(
        """
    gtaccgatcATGAAATAAgatcGGGCC
        gctagTACTTTATTctagC
    """
    )
    assert a17.extract_feature(0).seq == feat17

    # Wrap around feature circular on watson

    circularDseqrecord = Dseqrecord(linearDseq, circular=True)
    circularDseqrecord.add_feature(32, 5)
    assert (
        circularDseqrecord.figure()
        == 'Dseqrecord(o37)\n\x1b[48;5;11mgatcg\x1b[0mgtaccgatcATGAAATAAgatcGGGCC\x1b[48;5;11mCgatc\x1b[0m\nctagccatggctagTACTTTATTctagCCCGGGctag'
    )

    feat = Dseq.from_representation(
        """
    Cgatcgatcg
    Gctagctagc
    """
    )
    assert circularDseqrecord.extract_feature(0).seq == feat

    a18, b18 = circularDseqrecord.cut(KpnI, Bsp120I)
    assert b18.figure() == 'Dseqrecord(-18)\nGGCC\x1b[48;5;11mCgatcgatcg\x1b[0mgtac\n    Gctagctagc    '
    assert b18.extract_feature(0).seq == feat

    a19, b19 = circularDseqrecord.cut(KpnI, ApaI)
    assert b19.figure() == 'Dseqrecord(-18)\n    \x1b[48;5;11mCgatcgatcg\x1b[0mgtac\nCCGGGctagctagc    '
    assert b19.extract_feature(0).seq == feat

    a20, b20 = circularDseqrecord.cut(Acc65I, Bsp120I)
    assert b20.figure() == 'Dseqrecord(-18)\nGGCC\x1b[48;5;11mCgatcgatcg\x1b[0m    \n    Gctagctagccatg'
    assert b20.extract_feature(0).seq == feat

    a21, b21 = circularDseqrecord.cut(Acc65I, ApaI)
    assert b21.figure() == 'Dseqrecord(-18)\n    \x1b[48;5;11mCgatcgatcg\x1b[0m    \nCCGGGctagctagccatg'
    assert b21.extract_feature(0).seq == feat

    # Wrap around feature circular on crick

    circularDseqrecord = Dseqrecord(linearDseq, circular=True)
    circularDseqrecord.add_feature(32, 5)
    circularDseqrecord = circularDseqrecord.rc()
    assert (
        circularDseqrecord.figure()
        == 'Dseqrecord(o37)\ngatcGGGCCCgatcTTATTTCATgatcggtaccgatc\n\x1b[48;5;11mctagC\x1b[0mCCGGGctagAATAAAGTActagccatg\x1b[48;5;11mgctag\x1b[0m'
    )

    feat = Dseq.from_representation(
        """
    Cgatcgatcg
    Gctagctagc
    """
    )
    assert circularDseqrecord.extract_feature(0).seq == feat

    a22, b22 = circularDseqrecord.cut(KpnI, Bsp120I)
    assert (
        b22.figure()
        == 'Dseqrecord(-18)\n    cgatcgatcG    \ncatg\x1b[48;5;11mgctag\x1b[0m\x1b[48;5;11mctagC\x1b[0mCCGG'
    )
    assert b22.extract_feature(0).seq == feat

    a23, b23 = circularDseqrecord.cut(KpnI, ApaI)
    assert (
        b23.figure()
        == 'Dseqrecord(-18)\n    cgatcgatcGGGCC\ncatg\x1b[48;5;11mgctag\x1b[0m\x1b[48;5;11mctagC\x1b[0m    '
    )
    assert b23.extract_feature(0).seq == feat

    a24, b24 = circularDseqrecord.cut(Acc65I, Bsp120I)
    assert (
        b24.figure()
        == 'Dseqrecord(-18)\ngtaccgatcgatcG    \n    \x1b[48;5;11mgctag\x1b[0m\x1b[48;5;11mctagC\x1b[0mCCGG'
    )
    assert b24.extract_feature(0).seq == feat

    a25, b25 = circularDseqrecord.cut(Acc65I, ApaI)
    assert (
        b25.figure()
        == 'Dseqrecord(-18)\ngtaccgatcgatcGGGCC\n    \x1b[48;5;11mgctag\x1b[0m\x1b[48;5;11mctagC\x1b[0m    '
    )
    assert b25.extract_feature(0).seq == feat

def test_jan_glx():
    # Thanks to https://github.com/jan-glx
    from Bio.Restriction import NdeI, BamHI
    from pydna.readers import read

    # from pydna.genbank import Genbank
    # gb = Genbank("bjornjobb@gmail.com")
    # puc19 = gb.nucleotide("M77789.2")
    # assert puc19.cseguid() == "n-NZfWfjHgA7wKoEBU6zfoXib_0"
    # puc19.write("pUC19_M77789.gb")
    puc19 = read("pUC19_M77789.gb")
    assert puc19.cseguid() == "n-NZfWfjHgA7wKoEBU6zfoXib_0"
    insert, bb = puc19.cut(NdeI, BamHI)  # Note the order !

    puc19_ = (bb + insert).looped().synced(puc19)
    assert puc19_.cseguid() == "n-NZfWfjHgA7wKoEBU6zfoXib_0"

    # print(puc19_.extract_feature(2), "\n")
    # print(puc19.extract_feature(6))

    assert puc19_.extract_feature(2).seq == puc19.extract_feature(6).seq

def test_synced():
    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read

    bb_ins = Dseqrecord("tcgcgcgtttcgATGTAgtgatgacggtgaA", circular=True)

    for i in range(1, len(bb_ins)):
        cand = bb_ins.shifted(i)
        assert str(cand.synced("tcgcgcgtttcg", limit=11).seq).upper() == str(bb_ins.seq.upper())
        assert str(cand.synced(Dseq("tcgcgcgtttcg"), limit=11).seq).upper() == str(bb_ins.seq.upper())
        assert str(cand.synced(Seq("tcgcgcgtttcg"), limit=11).seq).upper() == str(bb_ins.seq.upper())
        assert str(cand.synced(SeqRecord(Seq("tcgcgcgtttcg")), limit=11).seq).upper() == str(bb_ins.seq.upper())

    pGUP1 = read("pGUP1_correct.gb")
    pGREG505 = read("pGREG505.gb")
    pGUP1_not_synced = read("pGUP1_not_synced.gb")
    assert pGUP1_not_synced.synced(pGREG505).useguid() == "42wIByERn2kSe_Exn405RYwhffU" == pGUP1.useguid()

    bb_ins = Dseqrecord("tcgcgcgtttcgAgtgatgacggtgaA", circular=True)

    for i in range(1, len(bb_ins)):
        cand = bb_ins.shifted(i)
        assert str(cand.synced("tcgcgcgtttcg", limit=11).seq).upper() == str(bb_ins.seq.upper())

    a = Dseqrecord("gtatcgttctagctattggctagta", circular=True)
    b = Dseqrecord("ggttagtcagttatatcggcttatc", circular=True)

    with pytest.raises(TypeError):
        a.synced(b)

    a = Dseqrecord("gtatcgttctagctattggctagta", circular=False)
    with pytest.raises(TypeError):
        a.synced(b)

    a = Dseqrecord("gtatcgttctagctattggctagta", circular=True)
    b = Dseqrecord("acgatactactagccaatagctaga", circular=True)

    assert str(a.synced(b).seq) == "acgatactactagccaatagctaga"
    assert str(a.synced(a).seq) == "gtatcgttctagctattggctagta"
    assert str(a.rc().synced(a).seq) == "gtatcgttctagctattggctagta"

def test_map_pCR_MCT1_HA46():
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read

    pCR_MCT1_HA46 = read("pCR_MCT1_HA46.gb")

    slc = pCR_MCT1_HA46.find_aa("VFFKE YPYDVPDYA IEG".replace(" ", ""))

    pCR_MCT1_HA46.map_target = slc

    with pytest.raises(ValueError):
        pCR_MCT1_HA46.map_trace_files("/subfolder/*.ab1")

    map_ = pCR_MCT1_HA46.map_trace_files("*.ab1")

    assert set(map_) == set(["28-1rev_D04_026.ab1", "32-3rev_H04_018.ab1", "36-5rev_D05_041.ab1"])

    assert set([x.fname for x in pCR_MCT1_HA46.matching_reads]) == set(
        ["28-1rev_D04_026.ab1", "32-3rev_H04_018.ab1", "36-5rev_D05_041.ab1"]
    )

    assert set([x.fname for x in pCR_MCT1_HA46.not_matching_reads]) == set(["02-G1_B01_013.ab1"])

    assert pCR_MCT1_HA46.find_aa("YPYDVPDYA".replace(" ", "")) == slice(1088, 1115, None)

    assert pCR_MCT1_HA46.find_aa("VFFKE YPYDVPDYA IEG".replace(" ", "")) == slice(1073, 1124, None)

    pCR_MCT1_HA46.add_feature(1073, 1124)
    mt = pCR_MCT1_HA46.features[-1]
    pCR_MCT1_HA46.map_target = mt

    map_ = pCR_MCT1_HA46.map_trace_files("*.ab1")
    assert set(map_) == set(["28-1rev_D04_026.ab1", "32-3rev_H04_018.ab1", "36-5rev_D05_041.ab1"])

    pCR_MCT1_HA46.map_target = None

    map_ = pCR_MCT1_HA46.map_trace_files("*.ab1")

    assert set(map_) == set(["32-3rev_H04_018.ab1", "36-5rev_D05_041.ab1", "28-1rev_D04_026.ab1"])

def test_map_short():
    from pydna.dseqrecord import Dseqrecord

    t = Dseqrecord("AAGTTAAAATAAGGCTAGTCCGTTAT")
    t.map_target = slice(0, 26)
    t.map_trace_files("*.ab1")
    assert t.map_trace_files("*.ab1") == ["02-G1_B01_013.ab1"]

def test_map_too_short():
    from pydna.dseqrecord import Dseqrecord

    t = Dseqrecord("AAGTTAAAATAAGGCTAGTCCGTT")
    # t.map_target = slice(0,23)
    t.map_trace_files("*.ab1")
    assert t.map_trace_files("*.ab1") == []

def test_map_no_match():
    from pydna.dseqrecord import Dseqrecord

    t = Dseqrecord(
        "AGGAGGGGCCCACACCGAGGAAGTAGACTGTTATACGTCGGCGATGGTGGTAGCTAACTATGTTGCCTGCCACTACAACAGTATCTAAGCCGTGTAAAGG"
    )  # random DNA!
    t.map_trace_files("*.ab1")
    assert t.map_trace_files("*.ab1") == []

def test_slicing2():
    from pydna.dseqrecord import Dseqrecord
    from Bio.Restriction import BamHI, EcoRI, KpnI

    a = Dseqrecord("aaGGATCCnnnnnnnnnGAATTCccc", circular=True)
    assert (
        a.cut(
            EcoRI,
            BamHI,
            KpnI,
        )[0].seq
        == a.cut(
            BamHI,
            EcoRI,
            KpnI,
        )[0].seq
    )

def test_rogerstager():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.utils import eq
    from Bio.Seq import Seq
    from Bio.Restriction import BsaI

    answ = []
    answ.append(Dseq("aaaaaaaaaaaaggtctca", "ttttttttccagagttttt"[::-1]))
    answ.append(Dseq("aaaaaaaaaggtctca", "tttttccagagttttt"[::-1]))

    tests = [Seq("aaaaaaggtctcaaaaaaa"), Seq("aaaaaaggtctcaaaa")]

    for s in tests:
        d = Dseqrecord(s).looped()
        for f in d.cut(BsaI):
            a = answ.pop(0)
            assert f.seq.watson == a.watson
            assert f.seq.crick == a.crick
            assert f.seq.ovhg == a.ovhg
            assert eq(f.seq, a)

def test___mul__():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    assert s * 3 == Dseqrecord("GGATCCGGATCCGGATCC", circular=False)
    assert s * 0 == Dseqrecord("")
    s = Dseqrecord("GGATCC", circular=True)
    with pytest.raises(TypeError):
        s * 3
    with pytest.raises(TypeError):
        s * 3.1

def test___repr__():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    assert repr(s) == "Dseqrecord(-6)"
    s = Dseqrecord("GGATCC", circular=True)
    assert repr(s) == "Dseqrecord(o6)"

def test__repr_pretty_():
    from unittest.mock import MagicMock
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    pp = MagicMock()
    s._repr_pretty_(pp, None)
    pp.text.assert_called_with("Dseqrecord(-6)")
    s = Dseqrecord("GGATCC", circular=True)
    s._repr_pretty_(pp, None)
    pp.text.assert_called_with("Dseqrecord(o6)")

def test___getitem__():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    assert s[1:-1].seq == Dseqrecord("GATC", circular=False).seq
    t = Dseqrecord("GGATCC", circular=True)
    assert t[1:5].seq == Dseqrecord("GATC").seq
    #    assert t[1:5].__dict__ == Dseqrecord("GATC").__dict__
    #    assert str(t[1:5].__dict__) == str(Dseqrecord("GATC").__dict__)
    assert s[1:5].seq == Dseqrecord("GATC").seq
    assert s[1:5].seq == Dseqrecord("GATC", circular=False).seq
    assert s[5:1:-1].seq == Dseqrecord("CCTA", circular=False).seq

    assert t[1:1].seq == Dseqrecord("").seq
    assert t[5:1].seq == Dseqrecord("CG", circular=False).seq
    assert t[9:1].seq == Dseqrecord("").seq
    assert t[1:9].seq == Dseqrecord("").seq
    assert t[9:10].seq == Dseqrecord("").seq
    assert t[10:9].seq == Dseqrecord("").seq

def test___eq__():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    t = Dseqrecord("GGATCC", circular=False)
    u = Dseqrecord("GGATCC", circular=True)
    v = Dseqrecord("GGATCC", circular=True)

    assert s == t
    assert u == v
    assert s != u

    assert s != 123

def test___ne__():
    pass

def test___hash__():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    t = Dseqrecord("GGATCC", circular=False)
    u = Dseqrecord("GGATCC", circular=True)
    v = Dseqrecord("GGATCC", circular=True)

    assert hash(s) == hash(t)
    assert hash(u) == hash(v)
    assert hash(s) != hash(u)

def test_linearize():
    from Bio.Restriction import BamHI, BglII
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord

    u = Dseqrecord("GGATCC", circular=True)
    frag = u.linearize(BamHI)
    assert frag.seq == Dseq("GATCCG", "GCCTAG"[::-1], -4)
    result = Dseqrecord(Dseq("GATCCG", "GCCTAG"[::-1], -4))
    assert frag.seq == result.seq

    t = Dseqrecord("GGATCC", circular=False)
    with pytest.raises(TypeError):
        t.linearize(BamHI)
    with pytest.raises(TypeError):
        u.linearize(BglII)
    u = Dseqrecord("GGATCCagatct", circular=True)
    u.linearize(BglII)
    u.linearize(BamHI)
    with pytest.raises(TypeError):
        u.linearize(BamHI, BglII)
    with pytest.raises(TypeError):
        u.linearize(BamHI + BglII)
    u = Dseqrecord("GGATCCGGATCC", circular=True)
    with pytest.raises(TypeError):
        u.linearize(BamHI)

def test_cutters():
    from pydna.dseqrecord import Dseqrecord

    obj = Dseqrecord("ggatcc")
    from Bio.Restriction import BglII, BamHI

    assert BglII in obj.no_cutters()
    assert BamHI not in obj.no_cutters()

    assert BamHI in obj.unique_cutters()

    assert BamHI in obj.once_cutters()

    assert BamHI in (obj + obj).twice_cutters()
    assert BamHI not in obj.twice_cutters()

    assert BamHI in obj.n_cutters(1)
    assert BamHI in obj.cutters()

def test_number_of_cuts():
    from Bio.Restriction import EcoRI, BamHI, BglII
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCCgaattc", circular=False)
    s.cut(BamHI)
    s.cut(EcoRI)
    s.cut(BglII)
    from Bio.Restriction import RestrictionBatch

    rb = RestrictionBatch((EcoRI, BamHI, BglII))
    assert s.number_of_cuts(BamHI) == 1
    assert s.number_of_cuts(EcoRI) == 1
    assert s.number_of_cuts(BglII) == 0
    assert s.number_of_cuts(rb) == 2
    assert s.number_of_cuts(EcoRI + BamHI) == 2

def test_reverse_complement():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCC", circular=False)
    assert s.reverse_complement() == s.rc()
    assert s.rc().seq == s.seq
    assert s.reverse_complement().seq == s.seq

    from pydna.readers import read

    s = read(
        """
                LOCUS       New_DNA                   13 bp ds-DNA     circular     12-NOV-2013
                DEFINITION  .
                ACCESSION
                VERSION
                SOURCE      .
                ORGANISM  .
                COMMENT
                COMMENT     ApEinfo:methylated:1
                FEATURES             Location/Qualifiers
                     misc_feature    join(9..10,12..13,1..1,3..6)
                                     /label=hej
                                     /ApEinfo_fwdcolor=cyan
                                     /ApEinfo_revcolor=green
                                     /ApEinfo_graphicformat=arrow_data {{0 1 2 0 0 -1} {} 0}
                                     width 5 offset 0
                ORIGIN
                        1 gattttaatc acc
                //"""
    )

def test_shifted():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("GGATCCgaattc", circular=False)
    with pytest.raises(TypeError):
        s.shifted(1)

def test_looped():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord

    a = Dseqrecord("aAAa")
    a.add_feature()
    b = a.looped()
    assert a.features[0].extract(a).seq == b.features[0].extract(b).seq
    assert str(a.features[0].extract(a).seq) == str(b.features[0].extract(b).seq)
    assert a.features == b.features

    a = Dseqrecord("aAAa")
    a.add_feature(0, 4)
    b = a.looped()
    assert a.features[0].extract(a).seq == b.features[0].extract(b).seq
    assert str(a.features[0].extract(a).seq) == str(b.features[0].extract(b).seq)
    assert a.features == b.features

    a = Dseqrecord("aAAa")
    a.add_feature(2, 4)
    b = a.looped()
    assert a.features[0].extract(a).seq == b.features[0].extract(b).seq
    assert str(a.features[0].extract(a).seq) == str(b.features[0].extract(b).seq)
    assert a.features == b.features

    a = Dseqrecord("aAAa")
    a.add_feature(2, 4)
    b = a.looped()
    assert a.features[0].extract(a).seq == b.features[0].extract(b).seq
    assert str(a.features[0].extract(a).seq) == str(b.features[0].extract(b).seq)
    assert a.features == b.features

    a = Dseqrecord("aAAa")
    a.add_feature(2, 4)
    b = a.looped()
    assert a.features[0].extract(a).seq == b.features[0].extract(b).seq
    assert str(a.features[0].extract(a).seq) == str(b.features[0].extract(b).seq)
    assert a.features == b.features

    a = Dseqrecord(Dseq("gAAa", "cTTt", ovhg=-1))
    a.add_feature(2, 4)
    b = a.looped()
    assert a.features[0].extract(a).seq == b.features[0].extract(b).seq
    assert str(a.features[0].extract(a).seq) == str(b.features[0].extract(b).seq)
    assert a.features == b.features

    a = Dseqrecord(Dseq("caaa", "gttt", ovhg=-1))
    a.add_feature(0, 5)
    b = a.looped()
    assert a.features == b.features

    a = Dseqrecord(Dseq("caaa", "gttt", ovhg=-1))
    a.add_feature(0, 5, strand=-1)
    b = a.looped()
    assert a.features == b.features

    a = Dseqrecord(Dseq("aaac", "tttg", ovhg=1))
    a.add_feature(2, 4)
    b = a.looped()
    assert a.features == b.features

    a = Dseqrecord(Dseq("aaaa", "tttt", ovhg=1))
    a.add_feature(0, 5)
    b = a.looped()
    assert a.features == b.features

    a = Dseqrecord(Dseq("aaaa", "tttt", ovhg=1))
    a.add_feature(0, 5, strand=-1)
    b = a.looped()
    assert a.features == b.features

    a = Dseqrecord(Dseq("aaaa", "tttt", ovhg=-1))
    a.add_feature(0, 6)
    b = a.looped()
    assert a.features == b.features

def test_upper():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("Gc")
    s.annotations["sample"] = ["sample"]
    u = s.upper()
    assert s.upper() == u
    assert s.seq.upper() == u.seq.upper()
    del u.__dict__["_seq"]
    del s.__dict__["_seq"]
    assert u.__dict__ == s.__dict__

def test_lower():
    from pydna.dseqrecord import Dseqrecord

    s = Dseqrecord("Gc")
    s.annotations["sample"] = ["sample"]
    l = s.lower()
    assert s.lower() == l
    assert s.seq.upper() == l.seq.upper()
    del l.__dict__["_seq"]
    del s.__dict__["_seq"]
    assert l.__dict__ == s.__dict__

def test_map():
    from pydna.dseq import Dseq
    from pydna.dseqrecord import Dseqrecord
    from pydna.readers import read
    from pydna.utils import eq

    from Bio.Seq import Seq
    from Bio.SeqRecord import SeqRecord as Srec
    from Bio.SeqIO import read as abiread

    traces = []

    import glob

    for name in glob.glob("*.ab1"):
        traces.append(abiread(name, "abi"))

    for t in traces:
        d = Dseqrecord(t.seq)

        if "ITVFFKEYPYDVPDYAIEGIFHAT" in d:
            tag = "tat cca tat gac gtt cca gac tat gca".replace(" ", "")
            trc = "ata ggt ata ctg caa ggt ctg ata cgt"[::-1].replace(" ", "")

            s = Dseqrecord(Dseq(tag, trc, 0))
            sl = s.find_aa("YPYDVPDYA")
            assert str(s[sl].seq.translate()) == "YPYDVPDYA"
            assert "YPYDVPDYA" in s

            tag = "AAA tat cca tat gac gtt cca gac tat gca".replace(" ", "")
            trc = "    ata ggt ata ctg caa ggt ctg ata cgt"[::-1].replace(" ", "")

            s = Dseqrecord(Dseq(tag, trc, 0))
            sl = s.find_aa("YPYDVPDYA")
            assert str(s[sl].seq.translate()) == "YPYDVPDYA"
            assert "YPYDVPDYA" in s

            tag = "    tat cca tat gac gtt cca gac tat gca".replace(" ", "")
            trc = "AAA ata ggt ata ctg caa ggt ctg ata cgt"[::-1].replace(" ", "")

            s = Dseqrecord(Dseq(tag, trc, 0))
            sl = s.find_aa("YPYDVPDYA")
            assert str(s[sl].seq.translate()) == "YPYDVPDYA"
            assert "YPYDVPDYA" in s

            tag = "    tat cca tat gac gtt cca gac tat gca".replace(" ", "")
            trc = "AAA ata ggt ata ctg caa ggt ctg ata cgt"[::-1].replace(" ", "")

            s = Dseqrecord(Dseq(tag, trc, 0))
            sl = s.find_aa("YPYDVPDYA")
            assert str(s[sl].seq.translate()) == "YPYDVPDYA"

            tag = "tat cca tat gac gtt cca gac tat gca".replace(" ", "")
            trc = "ata ggt ata ctg caa ggt ctg ata cgt"[::-1].replace(" ", "")

            tag, trc = trc, tag

            s = Dseqrecord(Dseq(tag, trc, 0))
            sl = s.rc().find_aa("YPYDVPDYA")

            assert str(s.rc()[sl].seq.translate()) == "YPYDVPDYA"
            assert "YPYDVPDYA" in s.rc()

            tag = "aaa tat cca tat gac gtt cca gac tat gca".replace(" ", "")
            trc = "ttt ata ggt ata ctg caa ggt ctg ata cgt"[::-1].replace(" ", "")

            s = Dseqrecord(Dseq(tag, trc, 0, circular=True))
            sl = s.find_aa("YPYDVPDYA")
            assert str(s[sl].seq.translate()) == "YPYDVPDYA"
            assert "YPYDVPDYA" in s

def test_assemble_YEp24PGK_XK():
    """
    test YEp24PGK_XK
    """
    import pytest
    import sys

    from pydna.readers import read
    from pydna.utils import eq
    from pydna.amplify import pcr

    """ test YEp24PGK_XK"""
    p1 = read("primer1.txt", ds=False)
    p3 = read("primer3.txt", ds=False)
    XKS1 = read("XKS1_orf.txt")
    YEp24PGK = read("YEp24PGK.txt")

    PCR_prod = pcr(p1, p3, XKS1)

    from Bio.Restriction import BamHI

    stuffer1, insert, stuffer2 = PCR_prod.cut(BamHI)

    from Bio.Restriction import BglII

    YEp24PGK_BglII = YEp24PGK.cut(BglII)[0]

    YEp24PGK_XK = YEp24PGK_BglII + insert

    YEp24PGK_XK = YEp24PGK_XK.looped()

    YEp24PGK_XK = YEp24PGK_XK.synced("gaattctgaaccagtcctaaaacgagtaaataggaccggcaattc")  # YEp24PGK)

    assert YEp24PGK_XK.useguid() == "HRVpCEKWcFsKhw_W-25ednUfldI"
    assert YEp24PGK_XK.cseguid() == "t9fs_9UvEuD-Ankyy8XEr1hD5DQ"

    YEp24PGK_XK_correct = read("YEp24PGK_XK_manually_assembled.txt")
    assert YEp24PGK_XK_correct.cseguid() == "t9fs_9UvEuD-Ankyy8XEr1hD5DQ"
    assert eq(YEp24PGK_XK, YEp24PGK_XK_correct)

if __name__ == "__main__":
    args = [
        __file__,
        "--cov=pydna",
        "--cov-append",
        "--cov-report=html:../htmlcov",
        "--cov-report=xml",
        "--capture=no",
        "--durations=10",
        "--import-mode=importlib",
        "--nbval",
        "--current-env",
        "--doctest-modules",
        "--capture=no",
        "-vvv",
    ]
    pytest.main(args)


import pathlib
from glob import glob

import setuptools

# The directory containing this file
HERE = pathlib.Path(__file__).parent

# The text of the README file
README = (HERE / "README.md").read_text()

setuptools.setup(
    name="empty-trash",
    version="0.2.3",
    url="https://github.com/edina/nb_empty_trash",
    author="ian Stuart",
    description="Simple Jupyter extension to show the size of the trash folder, and a button the empty it.",
    long_description=README,
    long_description_content_type="text/markdown",
    license="BSD",
    classifiers=[
        "License :: OSI Approved :: BSD License",
        "Programming Language :: Python :: 3",
    ],
    packages=setuptools.find_packages(),
    install_requires=["notebook>=6.0.0", "prometheus_client", "psutil>=5.6.0"],
    extras_require={
        "dev": [
            "autopep8",
            "pytest",
            "flake8",
            "pytest-cov>=2.6.1",
            "mock",
            "black",
            "tornado",
            "traitlets",
            "notebook",
            "prometheus_client",
            "pre-commit",
            "psutil>=5.6.0",
        ]
    },
    data_files=[
        ("share/jupyter/nbextensions/empty_trash", ["empty_trash/static/trash.js"]),
        (
            "etc/jupyter/jupyter_notebook_config.d",
            ["jupyter-config/nbconfig/notebook.d/empty_trash.json"],
        ),
        (
            "etc/jupyter/nbconfig/notebook.d",
            ["jupyter-config/jupyter_notebook_config.d/empty_trash.json"],
        ),
    ],
    zip_safe=False,
    include_package_data=True,
)


try:
    from gi.repository import Gtk
    from gi.repository import Gdk
    import re
except:
    print ("couldn't load depencies")

class TextEditor( Gtk.TextView ):
    """TextEditor encapsulates management of TextBuffer and TextIter for
    common functionality, such as cut, copy, paste, undo, redo, and
    highlighting of text.

    """

    def __init__(self):
        """Create a TextEditor

        """

        Gtk.TextView.__init__(self)
        self.undo_max = None
        self._highlight_strings = []
        found_tag = Gtk.TextTag(name="highlight")
        found_tag.set_property("background","yellow")
        self.get_buffer().get_tag_table().add(found_tag)

        self.insert_event = self.get_buffer().connect("insert-text",self._on_insert)
        self.delete_event = self.get_buffer().connect("delete-range",self._on_delete)
        self.change_event = self.get_buffer().connect("changed",self._on_text_changed)
        self._auto_bullet = None
        self.auto_bullets = False
        display = self.get_display()
        self.clipboard = Gtk.Clipboard.get_for_display(display, Gdk.SELECTION_CLIPBOARD)

        self.fflines = 0

        self.undos = []
        self.redos = []

    @property
    def text(self):
        """text - a string specifying all the text currently
        in the TextEditor's buffer.

        This property is read/write.

        """
        start_iter = self.get_buffer().get_iter_at_offset(0)
        end_iter =  self.get_buffer().get_iter_at_offset(-1)
        return self.get_buffer().get_text(start_iter,end_iter, False)

    @text.setter
    def text(self, text):
        self.get_buffer().set_text(text)

    def append(self, text):
        """append: appends text to the end of the textbuffer.

        arguments:
        text - a string to add to the buffer. The text will be the
        last text in the buffer. The insertion cursor will not be moved.

        """

        end_iter =  self.get_buffer().get_iter_at_offset(-1)
        self.get_buffer().insert(end_iter, text)

    def prepend(self, text):
        """prepend: appends text to the start of the textbuffer.

        arguments:
        text - a string to add to the buffer. The text will be the
        first text in the buffer. The insertion cursor will not be moved.

        """

        start_iter =  self.get_buffer().get_iter_at_offset(0)
        self.get_buffer().insert(start_iter, text)
        insert_iter = self.get_buffer().get_iter_at_offset(len(text)-1)
        self.get_buffer().place_cursor(insert_iter)

    def cursor_to_end(self):
        """cursor_to_end: moves the insertion curson to the last position
        in the buffer.

        """

        end_iter = self.get_buffer().get_iter_at_offset(-1)
        self.get_buffer().place_cursor(end_iter)

    def cursor_to_start(self):
        """cursor_to_start: moves the insertion curson to the first position
        in the buffer.

        """

        start_iter = self.get_buffer().get_iter_at_offset(0)
        self.get_buffer().place_cursor(start_iter)

    def add_highlight(self, text):
        """add_highlight: add string to be highlighted when entered in the
        buffer.

        arguments:
        text - a string to be highlighted

        """

        if text not in self._highlight_strings:
            self._highlight_strings.append(text)
        self._highlight()

    def clear_highlight(self, text):
        """clear_highlight: stops a string from being highlighted in the
        buffer.

        arguments:
        text - the string to stop highlighting. If the string is not currently
        being highlighted, the function will be ignored.

        """

        if text in self._highlight_strings:
            del(self._highlight_strings[text])
        self._highlight()

    def clear_all_highlights(self):
        """clear_all_highlight: stops highlighting all strings in the buffer.
        The TextEditor will forget about all strings specified for highlighting.
        If no strings are currently set for highlighting, the function will be
        ignored.

        """

        self._highlight_strings = []

        self._highlight()

    def _highlight(self):
        """_highlight: internal method to trigger highlighting.
        Do not call directly.

        """

        start_iter = self.get_buffer().get_iter_at_offset(0)
        end_iter =  self.get_buffer().get_iter_at_offset(-1)
        text = self.get_buffer().get_text(start_iter,end_iter, False)
        self.get_buffer().remove_tag_by_name('highlight', start_iter, end_iter)
        for s in self._highlight_strings:
            hits = [match.start() for match in re.finditer(re.escape(s), text)]
            for hit in hits:
                start_iter = self.get_buffer().get_iter_at_offset(hit)
                end_iter = self.get_buffer().get_iter_at_offset(hit + len(s))
                self.get_buffer().apply_tag_by_name("highlight",start_iter,end_iter)

    def cut(self, widget=None, data=None):
        """cut: cut currently selected text and put it on the clipboard.
        This function can be called as a function, or assigned as a signal
        handler.

        """

        self.get_buffer().cut_clipboard(self.clipboard, True)

    def copy(self, widget=None, data=None):
        """copy: copy currently selected text to the clipboard.
        This function can be called as a function, or assigned as a signal
        handler.

        """

        self.get_buffer().copy_clipboard(self.clipboard)

    def paste(self, widget=None, data=None):
        """paste: Insert any text currently on the clipboard into the
        buffer.
        This function can be called as a function, or assigned as a signal
        handler.

        """

        self.get_buffer().paste_clipboard(self.clipboard,None,True)

    def undo(self, widget=None, data=None):
        """undo: revert (undo) the last action.
        This function can be called as a function, or assigned as a signal
        handler.

        """

        if len(self.undos) == 0:
            return

        self.get_buffer().disconnect(self.delete_event)
        self.get_buffer().disconnect(self.insert_event)

        undo = self.undos[-1]
        redo = self._do_action(undo)
        self.redos.append(redo)
        del(self.undos[-1])

        self.insert_event = self.get_buffer().connect("insert-text",self._on_insert)
        self.delete_event = self.get_buffer().connect("delete-range",self._on_delete)
        self._highlight()

    def _do_action(self, action):
        if action["action"] == "delete":
            offset = action["offset"] + self.fflines
            start_iter = self.get_buffer().get_iter_at_offset(offset)
            end_iter =  self.get_buffer().get_iter_at_offset(offset + len(action["text"]))
            self.get_buffer().delete(start_iter, end_iter)
            action["action"] = "insert"

        elif action["action"] == "insert":
            offset = action["offset"] + self.fflines
            start_iter = self.get_buffer().get_iter_at_offset(offset)
            self.get_buffer().insert(start_iter, action["text"])
            action["action"] = "delete"

        return action

    def redo(self, widget=None, data=None):
        """redo: revert (undo) the last revert (undo) action.
        This function can be called as a function, or assigned as a signal
        handler.

        """

        if len(self.redos) == 0:
            return

        self.get_buffer().disconnect(self.delete_event)
        self.get_buffer().disconnect(self.insert_event)

        redo = self.redos[-1]
        undo = self._do_action(redo)
        self.undos.append(undo)
        del(self.redos[-1])

        self.insert_event = self.get_buffer().connect("insert-text",self._on_insert)
        self.delete_event = self.get_buffer().connect("delete-range",self._on_delete)

        self._highlight()

    def _on_text_changed(self, buff):
        if self._auto_bullet is not None:
            self.get_buffer().disconnect(self.change_event)
            buff.insert_at_cursor(self._auto_bullet)
            self._auto_bullet = None
            self.change_event = self.get_buffer().connect("changed",self._on_text_changed)

    def _on_insert(self, text_buffer, iter, text, length, data=None):
        """_on_insert: internal function to handle programatically inserted
        text. Do not call directly.

        """

        self._highlight()
        offset = iter.get_offset() - self.fflines
        cmd = {"action":"delete","offset": offset,"text":text}
        self._add_undo(cmd)
        self.redos = []
        if text == "\n" and self.auto_bullets:
            cur_line = iter.get_line()
            prev_line_iter = self.get_buffer().get_iter_at_line(cur_line)
            pl_offset = prev_line_iter.get_offset()
            pl_text = self.get_buffer().get_text(prev_line_iter, iter, False)
            if pl_text.strip().find("*") == 0:
                ws = ""
                if not pl_text.startswith("*"):
                    ws = (pl_text.split("*")[0])
                self._auto_bullet = ws + "* "

    def _on_delete(self, text_buffer, start_iter, end_iter, data=None):
        """_on_insert: internal function to handle delete
        text. Do not call directly.

        """
        self._highlight()
        text = self.get_buffer().get_text(start_iter,end_iter, False)
        offset = start_iter.get_offset() - self.fflines
        cmd = {"action":"insert","offset": offset,"text":text}
        self._add_undo(cmd)

    def _add_undo(self, cmd):
        """internal function to capture current state to add to undo stack.
        Do not call directly.

        """

        #delete the oldest undo if undo maximum is in effect
        if self.undo_max is not None and len(self.undos) >= self.undo_max:
            del(self.undos[0])
        self.undos.append(cmd)

class TestWindow(Gtk.Window):
    """For testing and demonstrating AsycnTaskProgressBox.

    """
    def __init__(self):
        #create a window a VBox to hold the controls
        Gtk.Window.__init__(self)
        self.set_title("TextEditor Test Window")
        windowbox = Gtk.VBox(False, 2)
        windowbox.show()
        self.add(windowbox)
        self.editor = TextEditor()
        self.editor.show()
        windowbox.pack_end(self.editor, True, True, 0)
        self.set_size_request(200,200)
        self.show()
        self.maximize()

        self.connect("destroy", Gtk.main_quit)
        self.editor.text = "this is some inserted text"
        self.editor.append("\nLine 3")
        self.editor.prepend("Line1\n")
        self.editor.cursor_to_end()
        self.editor.cursor_to_start()
        self.editor.add_highlight("his")
        self.editor.clear_all_highlights()
        self.editor.add_highlight("some")
        self.editor.undo_max = 100
        self.editor.auto_bullets = True
        cut_button = Gtk.Button("Cut")
        cut_button.connect("clicked",self.editor.cut)
        cut_button.show()
        windowbox.pack_start(cut_button, False, False, 0)

        copy_button = Gtk.Button("Copy")
        copy_button.connect("clicked",self.editor.copy)
        copy_button.show()
        windowbox.pack_start(copy_button, False, False, 0)

        paste_button = Gtk.Button("Paste")
        paste_button.connect("clicked",self.editor.paste)
        paste_button.show()
        windowbox.pack_start(paste_button, False, False, 0)

        undo_button = Gtk.Button("Undo")
        undo_button.connect("clicked",self.editor.undo)
        undo_button.show()
        windowbox.pack_start(undo_button, False, False, 0)

        redo_button = Gtk.Button("Redo")
        redo_button.connect("clicked",self.editor.redo)
        redo_button.show()
        windowbox.pack_start(redo_button, False, False, 0)

        print (self.editor.text)

if __name__== "__main__":
    test = TestWindow()
    Gtk.main()


#!/usr/bin/env python
# coding: utf-8

# In[121]:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import xarray as xr
# if you get errors to import these packages , you will need to install them in Anaconda
# in anaconda command prompt (windows) or terminal (mac/linux)
# Activate your environment, then type:
# conda install xarray

# In[122]:

## load Moab, Utah temperature data
# load excel data as pandas dataframe
df=pd.read_excel(r'C:\Users\Lenovo\Documents\QuantitativeTechniques\MOAB_1893_2020_MeanMonthlyTemp.xlsx')
data=df.to_numpy()
TMP_MOAB_JUL = data[:,7]
TMP_MOAB_JUL
#years = data[0:,0]

# first replace the missing data ("None") with np.nan
missing_index= (TMP_MOAB_JUL == -999) # first identify the missing data
TMP_MOAB_JUL[missing_index] = np.nan
TMP_MOAB_JUL_degC=(TMP_MOAB_JUL-32)*(5/9)
TMP_MOAB_JUL_degC

#TMP_MOAB_year=np.nanmean(TMP_MOAB_month,1)
# unit conversion deg F ->deg C
#TMP_MOAB_year_degC=(TMP_MOAB_year-32)*(5/9)
#year=df.Year.to_numpy()
#TMP_MOAB_year_degC

# In[123]:

# Download the most recent NCEP renalysis monthly data from the link below:
# https://psl.noaa.gov/data/gridded/data.ncep.reanalysis.derived.surface.html
# read NCEP reanalysis monthly data (in netcdf format)
# if you get an error in this step, most likely the netcdf4 package is missing in your environment
# to install netcdf4 package, type "conda install netcdf4" in command line
ds=xr.open_dataset('./air.mon.mean.nc')
# show a summary of the dataset
ds

# In[124]:

# extract surface air temperature data for 1893 to 2020
TMP_NCEP=ds.air.sel(time=slice("1948-01-01","2020-12-31"))

# In[125]:

# Moab, Utah lat lon
# 38.5733° N, 109.5498° W
# note that the longitude range in NCEP data is 0 - 360 deg

# select data for the grid cell Moab located
TMP_MOAB_NCEP=TMP_NCEP.sel(lat=38.5733,lon=360-109.5498,method="nearest")

# calculate annual mean temperature for Moab
TMP_MOAB_NCEP_year=TMP_MOAB_NCEP.groupby('time.year').mean('time')

# calculate climatological mean temperature for MOAB
TMP_MOAB_NCEP_month=TMP_MOAB_NCEP.groupby('time.month').mean('time')

# July temperature for Moab
mon=7;
TMP_MOAB_NCEP_JUL=TMP_MOAB_NCEP.sel(time=TMP_MOAB_NCEP['time.month']==mon)
TMP_MOAB_NCEP_JUL

# In[126]:

## make a figure that fits into a single column
fig=plt.figure(figsize=(3.5, 2.5)) # define figure size, unit: inches
ax=fig.add_axes([0.1, 0.15, 0.8, 0.8])
cmap=plt.get_cmap('Paired')
plt.plot(year,TMP_MOAB_JUL_degC,label='Measured (Moab site)',color=cmap(0))
plt.plot(TMP_MOAB_NCEP_year.year,TMP_MOAB_NCEP_JUL,label='NCEP (regional)',color=cmap(1))
plt.xlabel('Year')
plt.ylabel('Annual mean T ($^\circ$C)')
## add figure legend
plt.legend()

## save figure as .PDF and .PNG
## put save commands before plt.show()
fn='Fig_MOAB_temp'
plt.savefig('./fig/' +fn+ '.pdf',bbox_inches="tight")
plt.savefig('./fig/'+fn+'.png',bbox_inches="tight")
plt.show()

# In[127]:

import matplotlib.path as mpath
import xarray as xr
import cartopy.crs as ccrs

# if you get errors to import these packages , you will need to install them in Anaconda
# in anaconda command prompt (windows) or terminal (mac/linux)
# Activate your environment, then type:
# conda install cartopy xarray

# In[128]:

ds=xr.open_dataset('./air.mon.mean.nc')

# extract surface air temperature data for 1948 to 2020
TMP_NCEP1=ds.air.sel(time=slice("1950-01-01","1960-12-01"))
TMP_NCEP2=ds.air.sel(time=slice("2010-01-01","2020-12-01"))

# Select JULY data for every year and average
TMP_NCEP_JUL1=TMP_NCEP1.sel(time=TMP_NCEP1['time.month']==7).mean(dim='time')
TMP_NCEP_JUL2=TMP_NCEP2.sel(time=TMP_NCEP2['time.month']==7).mean(dim='time')

# X=np.append(TMP_NCEP_mean.lon.values,360)
# Y=np.append(TMP_NCEP_mean.lat.values,-90)
X=ds.lon.values
Y=ds.lat.values

# grid boundaries
Xb=np.arange(-2.5/2, 360,2.5)
Yb=np.arange(90-2.5/2, -90,-2.5)
Yb=np.append(90,Yb)
Yb=np.append(Yb,-90)

# In[129]:

fig=plt.figure(figsize=(8, 5))
ax11 = fig.add_axes([0.42, 0.7,0.3,0.25],projection=ccrs.PlateCarree(central_longitude=-150))
ax11.coastlines()
cf=ax11.pcolormesh(Xb,Yb,TMP_NCEP_JUL1.values, transform=ccrs.PlateCarree(),vmin=-75,vmax=45,cmap='plasma',rasterized=True)
ax11.set_title('July, 1950-1960')
###
####
ax21 = fig.add_axes([0.42, 0.4,0.3,0.25],projection=ccrs.PlateCarree(central_longitude=-150))
ax21.coastlines()
cf=ax21.pcolormesh(Xb,Yb,TMP_NCEP_JUL2.values, transform=ccrs.PlateCarree(),vmin=-75,vmax=45,cmap='plasma',rasterized=True)
ax21.set_title('July, 2010-2020')

# add color bar
ax_cb = fig.add_axes([0.73, 0.41,0.0125,0.53])
fig.colorbar(cf, cax=ax_cb, label='T ($^\circ$C)')

#calculate difference
delta_TMP_JUL=TMP_NCEP_JUL2-TMP_NCEP_JUL1

ax12 = fig.add_axes([0.42, 0.1,0.3,0.25],projection=ccrs.PlateCarree(central_longitude=-150))
ax12.coastlines()
cf=ax12.pcolormesh(Xb,Yb,delta_TMP_JUL.values, transform=ccrs.PlateCarree(),vmin=-15,vmax=15,cmap='RdBu_r',rasterized=True)
ax12.set_title('July, Difference')

ax_cb2 = fig.add_axes([0.73, 0.1,0.0125,0.25])
fig.colorbar(cf, cax=ax_cb2, label='$\Delta$T ($^\circ$C)')

fn='Fig_Jan_July_temp_diff_rasterized'
plt.savefig('./fig/'+fn+'.pdf',bbox_inches="tight",dpi=300)
plt.savefig('./fig/'+fn+'.png',bbox_inches="tight",dpi=300)

plt.show()

# In[ ]:

# In[ ]:


"""
Optical Spectrum Analyser interface for data acquisition card.
"""
import numpy as np
import ctypes
import PyDAQmx
from PyDAQmx.DAQmxFunctions import DAQError
from wand.common import with_log

__all__ = [
    'OSATask',
    'set_frequency',
    'channel_setup',
]

# Value for choosing no options for DAQmx functions
NO_OPTIONS = 0

# Approx collection frequency
_FREQUENCY = 10

def set_frequency(frequency):
    global _FREQUENCY
    _FREQUENCY = frequency

# Downsampling (reduces number of data points, not frequency)
DWNSMP = 10

# Parameters for data acquisition
SAMPLES = 16000
RATE = 1.25e6
TIMEOUT = 0.1
MIN_V = -1.0
MAX_V = 1.0

def channel_setup(config):
    """Set up the channels to be used in the DAQ card"""
    global AI_BLUE, AI_RED, TRIG_BLUE, TRIG_RED
    AI_BLUE = config['blue']['input'].encode()
    AI_RED = config['red']['input'].encode()
    TRIG_BLUE = config['blue']['trigger'].encode()
    TRIG_RED = config['red']['trigger'].encode()

@with_log
class OSATask(PyDAQmx.Task):
    """
    Task object for collecting data from the Optical Spectrum Analyser
    """

    def __init__(self, loop, queue, channel):
        """
        Set up the Task in the DAQ card ready for use
        """
        super().__init__()

        self._log.debug(
            "Creating Task object for channel: {}".format(channel.name))
        self.loop = loop
        self.queue = queue
        self.channel = channel

        # Blue/Red lasers require using different etalons, so have different
        # analog inputs to the DAQ card
        if self.channel.blue:
            AI = AI_BLUE
            TRIG = TRIG_BLUE
        else:
            AI = AI_RED
            TRIG = TRIG_RED

        # We want to measure voltage in range MIN_V to MAX_V, with a finite
        # number of samples taken at RATE, triggering collection on the
        # falling edge of the trigger channel
        self.CreateAIVoltageChan(
            AI, '', PyDAQmx.DAQmx_Val_NRSE, MIN_V, MAX_V,
            PyDAQmx.DAQmx_Val_Volts, None)
        self.CfgSampClkTiming(
            '', RATE, PyDAQmx.DAQmx_Val_Rising,
            PyDAQmx.DAQmx_Val_FiniteSamps, SAMPLES)
        self.CfgDigEdgeStartTrig(TRIG, PyDAQmx.DAQmx_Val_Falling)

        # We have to manualy reset the trigger by restarting the task -
        # committing the task to the DAQ card minimises the time taken to
        # restart
        self.TaskControl(PyDAQmx.DAQmx_Val_Task_Commit)

        # Register callbacks
        self.AutoRegisterEveryNSamplesEvent(
            PyDAQmx.DAQmx_Val_Acquired_Into_Buffer, SAMPLES, NO_OPTIONS)
        self.AutoRegisterDoneEvent(NO_OPTIONS)

    def EveryNCallback(self):
        """
        Called when the DAQ has data, also resets the trigger
        """
        data = np.zeros(SAMPLES)
        _read = np.int32()
        try:
            self.ReadAnalogF64(
                SAMPLES, TIMEOUT, PyDAQmx.DAQmx_Val_GroupByScanNumber, data,
                SAMPLES, ctypes.byref(np.ctypeslib.as_ctypes(_read)), None)
        except Exception as e:
            self._log.error("Read Error: {}".format(e))

        # Perform downsampling
        data.shape = int(SAMPLES/DWNSMP), DWNSMP
        data = data.mean(axis=1)

        # Multiply by 10000 and cast to int to truncate data
        scale = 1e4
        data = np.multiply(data, scale).astype(int)
        d = {'source': 'osa', 'channel': self.channel.name,
             'data': data.tolist(), 'scale': scale}

        if not self.loop.is_closed():
            self.loop.create_task(self.queue.put(d))

            # Restart task so that we have continuous acquisition
            self.RestartTask()

        # Required
        return 0

    def StopTask(self):
        """Wraps StopTask to catch exceptions that we don't care about"""
        try:
            super().StopTask()
        except DAQError:
            pass

    def RestartTask(self):
        self.StopTask()
        self.loop.call_later((1.0/_FREQUENCY), self._start)

    def _start(self):
        """Wraps to catch exceptions, but note that this isn't public"""
        try:
            self.StartTask()
        except DAQError:
            # Task specified is invalid i.e. we've switched away
            pass

    def DoneCallback(self, status):
        self._log.debug("Done: Status: {}".format(status))

        # Required
        return 0


from flask import abort, jsonify

from ospfm import db, helpers
from ospfm.core import models
from ospfm.transaction import models as transaction
from ospfm.objects import Object

class Currency(Object):

    def __own_currency(self, isocode):
        return models.Currency.query.filter(
            db.and_(
                models.Currency.isocode == isocode,
                db.or_(
                    models.Currency.owner_username == self.username,
                    models.Currency.owner_username == None,
                )
            )
        )

    def list(self):
        currencies = models.Currency.query.filter(
            db.or_(
                models.Currency.owner_username == self.username,
                models.Currency.owner_username == None,
            )
        )
        return [c.as_dict() for c in currencies]

    def create(self):
        # With user-defined currencies, isocode=symbol
        symbol = self.args['symbol']

        currency_exists = self.__own_currency(symbol).all()
        if currency_exists:
            self.badrequest("A currency with this symbol already exists")
        c = models.Currency(
                owner_username = self.username,
                isocode = symbol,
                symbol = symbol,
                name = self.args['name'],
                rate = self.args['rate']
        )
        db.session.add(c)
        db.session.commit()
        return c.as_dict()

    def read(self, isocode):
        currency = self.__own_currency(isocode).first()
        if currency:
            return currency.as_dict(with_rate=True)
        else:
            self.notfound('This currency does not exist')

    def update(self, isocode):
        currency = self.__own_currency(isocode).first()
        if not currency:
            self.notfound('Nonexistent currency cannot be modified')
        if not currency.owner_username:
            self.forbidden('Globally defined currencies cannot be modified')

        if 'symbol' in self.args:
            # With user-defined currencies, isocode=symbol
            newsymbol = self.args['symbol']
            testcurrency = self.__own_currency(newsymbol).first()
            if not testcurrency:
                currency.isocode = newsymbol
                currency.symbol = newsymbol
        if 'name' in self.args:
            currency.name = self.args['name']
        if 'rate' in self.args:
            currency.rate = self.args['rate']
            self.add_to_response('totalbalance')
        db.session.commit()
        return currency.as_dict()

    def delete(self, isocode):
        currency = self.__own_currency(isocode).first()
        if not currency:
            self.notfound('Nonexistent currency cannot be deleted')
        if not currency.owner_username:
            self.forbidden('Globally defined currencies cannot be deleted')
        # Only delete the currency if it is not in use
        if transaction.Account.query.filter(
                transaction.Account.currency == currency
           ).count() or \
           transaction.Category.query.filter(
                transaction.Category.currency == currency
           ).count() or \
           transaction.Transaction.query.filter(
                transaction.Account.currency == currency
           ).count():
                # TODO: Make this error explicit
                self.badrequest("This currency is still in use")
        db.session.delete(currency)
        db.session.commit()

    def http_rate(self, fromisocode, toisocode):
        response = helpers.rate(self.username, fromisocode, toisocode)
        if response:
            return jsonify(
                        status=200,
                        response=response
                )
        else:
            self.badrequest("Rate cannot be calculated")