import os
import tempfile
import unittest

import ddt

from chirp import chirp_common
from chirp import directory
from chirp.drivers import generic_csv

CHIRP_CSV_LEGACY = (
    """Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
1,FRS 1,462.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
2,FRS 2,462.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
""")
CHIRP_CSV_MINIMAL = (
    """Location,Frequency
1,146.520
2,446.000
""")
CHIRP_CSV_MODERN = (
    """Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE
0,Nat Simplex,146.520000,,0.600000,TSQL,88.5,88.5,023,NN,023,Tone->Tone,FM,5.00,,50W,This is the national calling frequency on 2m,,,,
1,National Simp,446.000000,-,5.000000,DTCS,88.5,88.5,023,NN,023,Tone->Tone,FM,5.00,,5.0W,This is NOT the UHF calling frequency,,,,
""")  # noqa
CHIRP_CSV_MODERN_QUOTED_HEADER = (
    """"Location","Name","Frequency","Duplex","Offset","Tone","rToneFreq","cToneFreq","DtcsCode","DtcsPolarity","RxDtcsCode","CrossMode","Mode","TStep","Skip","Power","Comment","URCALL","RPT1CALL","RPT2CALL","DVCODE"
0,Nat Simplex,146.520000,,0.600000,TSQL,88.5,88.5,023,NN,023,Tone->Tone,FM,5.00,,50W,This is the national calling frequency on 2m,,,,
1,"National Simp",446.000000,-,5.000000,DTCS,88.5,88.5,023,NN,023,Tone->Tone,FM,5.00,,5.0W,This is NOT the UHF calling frequency,,,,
""")  # noqa
CHIRP_TSV = (
    """Location	Name	Frequency	Duplex	Offset	Tone	rToneFreq	cToneFreq	DtcsCode	DtcsPolarity	RxDtcsCode	CrossMode	Mode	TStep	Skip	Power	Comment	URCALL	RPT1CALL	RPT2CALL	DVCODE
8	Mt Scott	147.280000	+	0.600000	TSQL	88.5	167.9	023	NN	023	Tone->Tone	FM	5.00		50W	TayDan Emergency Step #2\t\t\t\t
9	Mt Tabor	145.390000	-	0.600000	TSQL	88.5	100.0	023	NN	023	Tone->Tone	FM	5.00		50W	TayDan Emergency Step #1\t\t\t\t
10	Timberline V	147.120000	+	0.600000	TSQL	103.5	100.0	023	NN	023	Tone->Tone	FM	5.00		50W	TayDan Emergency Step #4\t\t\t\t
11	Timberline U	444.225000	+	5.000000	TSQL	88.5	100.0	023	NN	023	Tone->Tone	FM	5.00		50W	K7RPT Timberline, Linked to VHF side, AC7QE-R echolink\t\t\t\t
12	GovtCamp	443.875000	+	5.000000	TSQL	88.5	103.5	023	NN	023	Tone->Tone	FM	5.00		50W\t\t\t\t
13	GoatMtn	443.700000	+	5.000000	TSQL	88.5	103.5	023	NN	023	Tone->Tone	FM	5.00		50W	SW Corner of Clackamas, Co.\t\t\t\t
14	TyghRdge	147.260000	+	0.600000	TSQL	103.5	82.5	023	NN	023	Tone->Tone	FM	5.00		50W\t\t\t\t
""")  # noqa


class TestCSV(unittest.TestCase):
    def setUp(self):
        super().setUp()
        self.testfn = tempfile.mktemp('.csv', 'chirp-test')

    def tearDown(self):
        super().tearDown()
        try:
            os.remove(self.testfn)
        except FileNotFoundError:
            pass

    def test_parse_legacy(self):
        with open(self.testfn, 'w', encoding='utf-8') as f:
            f.write(CHIRP_CSV_LEGACY)
        csv = generic_csv.CSVRadio(self.testfn)
        mem = csv.get_memory(1)
        self.assertEqual(1, mem.number)
        self.assertEqual('FRS 1', mem.name)
        self.assertEqual(462562500, mem.freq)
        self.assertEqual('', mem.duplex)
        self.assertEqual(5000000, mem.offset)
        self.assertEqual('', mem.tmode)
        self.assertEqual(88.5, mem.rtone)
        self.assertEqual(88.5, mem.ctone)
        self.assertEqual(23, mem.dtcs)
        self.assertEqual('NN', mem.dtcs_polarity)
        self.assertEqual('NFM', mem.mode)
        self.assertEqual(12.5, mem.tuning_step)

    def test_parse_modern(self, output_encoding='utf-8', data=None):
        with open(self.testfn, 'w', encoding=output_encoding) as f:
            f.write(data or CHIRP_CSV_MODERN)
        # Make sure we detect the file
        with open(self.testfn, 'rb') as f:
            self.assertTrue(generic_csv.CSVRadio.match_model(
                f.read(), self.testfn))
        csv = generic_csv.CSVRadio(self.testfn)
        mem = csv.get_memory(1)
        self.assertEqual(1, mem.number)
        self.assertEqual('National Simp', mem.name)
        self.assertEqual(446000000, mem.freq)
        self.assertEqual('-', mem.duplex)
        self.assertEqual(5000000, mem.offset)
        self.assertEqual('DTCS', mem.tmode)
        self.assertEqual(88.5, mem.rtone)
        self.assertEqual(88.5, mem.ctone)
        self.assertEqual(23, mem.dtcs)
        self.assertEqual('NN', mem.dtcs_polarity)
        self.assertEqual('FM', mem.mode)
        self.assertEqual(5.0, mem.tuning_step)
        self.assertEqual('5.0W', str(mem.power))
        self.assertIn('UHF calling', mem.comment)

    def _test_csv_with_comments(self, data, output_encoding='utf-8'):
        lines = list(data.strip().split('\n'))
        lines.insert(0, '# This is a comment')
        lines.insert(0, '# Test file with comments')
        lines.insert(4, '# Test comment in the middle')
        lines.append('# Comment at the end')
        with open(self.testfn, 'w', newline='', encoding=output_encoding) as f:
            f.write('\r\n'.join(lines))
        # Make sure we detect the file
        with open(self.testfn, 'rb') as f:
            self.assertTrue(generic_csv.CSVRadio.match_model(
                f.read(), self.testfn))
        csv = generic_csv.CSVRadio(self.testfn)
        mem = csv.get_memory(0)
        self.assertEqual(146520000, mem.freq)
        mem = csv.get_memory(1)
        self.assertEqual(446000000, mem.freq)
        csv.save(self.testfn)
        with open(self.testfn, 'r') as f:
            read_lines = [x.strip() for x in f.readlines()]
        # Ignore quotes
        self.assertEqual([x.replace('"', '') for x in lines], read_lines)

    def test_csv_with_comments(self):
        self._test_csv_with_comments(CHIRP_CSV_MODERN)

    def test_csv_with_comments_quoted_header(self):
        self._test_csv_with_comments(CHIRP_CSV_MODERN_QUOTED_HEADER)

    def test_parse_modern_quoted_header(self):
        self.test_parse_modern(data=CHIRP_CSV_MODERN_QUOTED_HEADER)

    def test_parse_modern_quoted_header_bom(self):
        self.test_parse_modern(output_encoding='utf-8-sig',
                               data=CHIRP_CSV_MODERN_QUOTED_HEADER)

    def test_parse_modern_bom(self):
        self.test_parse_modern(output_encoding='utf-8-sig')

    def test_parse_modern_bom_with_comments(self):
        self.test_parse_modern(output_encoding='utf-8-sig')

    def test_parse_minimal(self):
        with open(self.testfn, 'w', encoding='utf-8') as f:
            f.write(CHIRP_CSV_MINIMAL)
        csv = generic_csv.CSVRadio(self.testfn)
        mem = csv.get_memory(1)
        self.assertEqual(1, mem.number)
        self.assertEqual(146520000, mem.freq)

    def test_parse_unknown_field(self):
        with open(self.testfn, 'w', encoding='utf-8') as f:
            f.write('Location,Frequency,Color\n')
            f.write('1,146.520,Red\n')
        csv = generic_csv.CSVRadio(self.testfn)
        mem = csv.get_memory(1)
        self.assertEqual(1, mem.number)
        self.assertEqual(146520000, mem.freq)

    def test_parse_unknown_power(self):
        with open(self.testfn, 'w', encoding='utf-8') as f:
            f.write('Location,Frequency,Power\n')
            f.write('0,146.520,L1\n')
            f.write('1,146.520,5W\n')
        csv = generic_csv.CSVRadio(self.testfn)
        mem = csv.get_memory(1)
        self.assertEqual(1, mem.number)
        self.assertEqual(146520000, mem.freq)

    def test_foreign_power(self):
        csv = generic_csv.CSVRadio(None)
        m = chirp_common.Memory()
        m.number = 1
        m.freq = 146520000
        m.power = chirp_common.PowerLevel('Low', watts=17)
        csv.set_memory(m)
        m = csv.get_memory(1)
        self.assertEqual('17W', str(m.power))
        self.assertEqual(int(chirp_common.watts_to_dBm(17)), int(m.power))

    def test_default_power(self):
        m = chirp_common.Memory()
        m.number = 1
        m.freq = 146520000
        r = generic_csv.CSVRadio(None)
        r.set_memory(m)
        m2 = r.get_memory(1)
        self.assertEqual(generic_csv.DEFAULT_POWER_LEVEL, m2.power)

    def _write_and_read(self, mem):
        radio = generic_csv.CSVRadio(None)
        radio.set_memory(mem)
        radio.save(self.testfn)
        radio = generic_csv.CSVRadio(self.testfn)
        return radio.get_memory(mem.number)

    def test_escaped_string_chars(self):
        m = chirp_common.Memory()
        m.freq = 146520000
        m.number = 1
        m.name = 'This is "The one"'
        m.comment = 'Wow, a \nMulti-line comment!'
        m2 = self._write_and_read(m)
        self.assertEqual(m.name, m2.name)
        self.assertEqual(m.comment, m2.comment)

    def test_unicode_comment_chars(self):
        m = chirp_common.Memory()
        m.freq = 146520000
        m.number = 1
        m.name = 'This is "The one"'
        m.comment = b'This is b\xc9\x90d news'.decode()
        m2 = self._write_and_read(m)
        self.assertEqual(m.name, m2.name)
        self.assertEqual(m.comment, m2.comment)

    def test_cross_dtcs(self):
        m = chirp_common.Memory()
        m.freq = 146520000
        m.number = 1
        m.tmode = 'Cross'
        m.cross_mode = 'DTCS->DTCS'
        m.dtcs = 25
        m.rx_dtcs = 73
        m2 = self._write_and_read(m)
        self.assertEqual('Cross', m2.tmode)
        self.assertEqual('DTCS->DTCS', m2.cross_mode)
        self.assertEqual(25, m2.dtcs)
        self.assertEqual(73, m2.rx_dtcs)

    def test_csv_memories_are_private(self):
        m = chirp_common.Memory(name='foo')
        radio = generic_csv.CSVRadio(None)
        radio.set_memory(m)
        # If CSVRadio maintains a reference to m, then this will modify
        # its internal state and the following assertion will fail.
        m.name = 'bar'
        self.assertEqual('foo', radio.get_memory(0).name)


@ddt.ddt
class RTCSV(unittest.TestCase):
    def _test_open(self, sample):
        sample_fn = os.path.join(os.path.dirname(__file__), sample)
        radio = directory.get_radio_by_image(sample_fn)
        self.assertIsInstance(radio, generic_csv.RTCSVRadio)

    @ddt.data('ft3d', 'ftm400', 'ftm500')
    def test_sample_file(self, arg):
        self._test_open('rtcsv_%s.csv' % arg)


class TestTSV(unittest.TestCase):
    def test_parse_tsv(self):
        radio = generic_csv.TSVRadio(None)
        radio.load_from(CHIRP_TSV)
        m = radio.get_memory(8)
        self.assertEqual('Mt Scott', m.name)
