import sunspec2.spreadsheet as spreadsheet
import pytest
import csv
import copy
import json


def test_idx():
    row = ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor',
           'Units', 'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description', 'Detailed Description']

    assert spreadsheet.idx(row, 'Address Offset') == 0
    with pytest.raises(ValueError):
        del row[0]
        assert spreadsheet.idx(row, 'Address Offset', mandatory=True)


def test_row_is_empty():
    row = [''] * 10
    assert spreadsheet.row_is_empty(row, 0)
    row[0] = 'abc'
    assert not spreadsheet.row_is_empty(row, 0)


def test_find_name():
    points = [
              {
                "name": "Inclx",
                "type": "int32",
                "mandatory": "M",
                "units": "Degrees",
                "sf": -2,
                "label": "X",
                "desc": "X-Axis inclination"
              },
              {
                "name": "Incly",
                "type": "int32",
                "units": "Degrees",
                "sf": -2,
                "label": "Y",
                "desc": "Y-Axis inclination"
              },
              {
                "name": "Inclz",
                "type": "int32",
                "units": "Degrees",
                "sf": -2,
                "label": "Z",
                "desc": "Z-Axis inclination"
              }
            ]
    assert spreadsheet.find_name(points, 'abc') is None
    assert spreadsheet.find_name(points, 'Incly') == points[1]


def test_from_spreadsheet():
    model_spreadsheet = [
        ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor', 'Units',
         'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description', 'Detailed Description', 'Standards'],
        ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model',
         'Include to support orientation measurements', '', ''],
        [0, '', 'ID', 304, '', 'uint16', '', '', '', '', 'M', 'S', 'Model ID', 'Model identifier', '', ''],
        [1, '', 'L', '', '', 'uint16', '', '', '', '', 'M', 'S', 'Model Length', 'Model length', '', ''],
        ['', '', 'inclinometer.incl', '', 0, 'group', '', '', '', '', '', '', '', '', '', ''],
        ['', 0, 'Inclx', '', '', 'int32', '', -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination', '', ''],
        ['', 2, 'Incly', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination', '', ''],
        ['', 4, 'Inclz', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination', '', '']
    ]
    model_def = {
        'group':
            {
                'name': 'inclinometer', 'type': 'group', 'label': 'Inclinometer Model',
                'desc': 'Include to support orientation measurements', 'points':
                [
                    {'name': 'ID', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S',
                     'label': 'Model ID',
                     'desc': 'Model identifier', 'value': 304, 'standards': []},
                    {'name': 'L', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S',
                     'label': 'Model Length',
                     'desc': 'Model length', 'standards': []}
                ],
                'groups': [
                    {'name': 'incl', 'type': 'group', 'count': 0, 'points': [
                        {'name': 'Inclx', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees',
                         'mandatory': 'M', 'label': 'X',
                         'desc': 'X-Axis inclination', 'standards': []},
                        {'name': 'Incly', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees',
                         'label': 'Y',
                         'desc': 'Y-Axis inclination', 'standards': []},
                        {'name': 'Inclz', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees',
                         'label': 'Z',
                         'desc': 'Z-Axis inclination', 'standards': []}]}
                ]
            },
        'id': 304
    }

    spreadsheet_data = spreadsheet.from_spreadsheet(model_spreadsheet)

    assert spreadsheet_data == model_def


def test_to_spreadsheet():
    model_spreadsheet = [
        ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor', 'Units',
         'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description', 'Detailed Description', 'Standards'],
        ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model',
         'Include to support orientation measurements', '', ''],
        [0, '', 'ID', 304, '', 'uint16', 1, '', '', '', 'M', 'S', 'Model ID', 'Model identifier', '', ''],
        [1, '', 'L', '', '', 'uint16', 1, '', '', '', 'M', 'S', 'Model Length', 'Model length', '', ''],
        ['', '', 'inclinometer.incl', '', 0, 'group', '', '', '', '', '', '', '', '', '', ''],
        ['', 0, 'Inclx', '', '', 'int32', 2, -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination', '', ''],
        ['', 2, 'Incly', '', '', 'int32', 2, -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination', '', ''],
        ['', 4, 'Inclz', '', '', 'int32', 2, -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination', '', '']
    ]

    model_def = {
      "id": 304,
      "group": {
        "name": "inclinometer",
        "type": "group",
        "points": [
          {
            "name": "ID",
            "value": 304,
            "desc": "Model identifier",
            "label": "Model ID",
            "mandatory": "M",
            "static": "S",
            "type": "uint16"
          },
          {
            "name": "L",
            "desc": "Model length",
            "label": "Model Length",
            "mandatory": "M",
            "static": "S",
            "type": "uint16"
          }
        ],
        "groups": [
          {
            "name": "incl",
            "type": "group",
            "count": 0,
            "points": [
              {
                "name": "Inclx",
                "type": "int32",
                "mandatory": "M",
                "units": "Degrees",
                "sf": -2,
                "label": "X",
                "desc": "X-Axis inclination"
              },
              {
                "name": "Incly",
                "type": "int32",
                "units": "Degrees",
                "sf": -2,
                "label": "Y",
                "desc": "Y-Axis inclination"
              },
              {
                "name": "Inclz",
                "type": "int32",
                "units": "Degrees",
                "sf": -2,
                "label": "Z",
                "desc": "Z-Axis inclination"
              }
            ]
          }
        ],
        "label": "Inclinometer Model",
        "desc": "Include to support orientation measurements"
      }
    }
    assert spreadsheet.to_spreadsheet(model_def) == model_spreadsheet


def test_to_spreadsheet_group():
    model_def = {
                    "group": {
                        "desc": "DER capacity model.",
                        "label": "DER Capacity",
                        "name": "DERCapacity",
                        "points": [
                            {
                                "access": "R",
                                "desc": "DER capacity model id.",
                                "label": "DER Capacity Model ID",
                                "mandatory": "M",
                                "name": "ID",
                                "static": "S",
                                "type": "uint16",
                                "value": 702
                            },
                            {
                                "access": "R",
                                "desc": "DER capacity name  model length.",
                                "label": "DER Capacity Model Length",
                                "mandatory": "M",
                                "name": "L",
                                "static": "S",
                                "type": "uint16"
                            },
                            {
                                "access": "R",
                                "comments": [
                                    "Nameplate Ratings - Specifies capacity ratings"
                                ],
                                "desc": "Maximum active power rating at unity power factor in watts.",
                                "label": "Active Power Max Rating",
                                "mandatory": "O",
                                "name": "WMaxRtg",
                                "sf": "W_SF",
                                "type": "uint16",
                                "units": "W",
                                "symbols": [
                                    {
                                        "name": "CAT_A",
                                        "value": 1
                                    },
                                    {
                                        "name": "CAT_B",
                                        "value": 2
                                    }
                                ]
                            }
                        ],
                        "type": "group"
                    },
                    "id": 702
                }
    ss = []
    spreadsheet.to_spreadsheet_group(ss, model_def['group'], has_notes=False)
    assert ss == [
        ['', '', 'DERCapacity', '', '', 'group', '', '', '', '', '', '', 'DER Capacity', 'DER capacity model.', '', ''],
        ['', 0, 'ID', 702, '', 'uint16', 1, '', '', '', 'M', 'S', 'DER Capacity Model ID',
         'DER capacity model id.', '', ''],
        ['', 1, 'L', '', '', 'uint16', 1, '', '', '', 'M', 'S', 'DER Capacity Model Length',
         'DER capacity name  model length.', '', ''],
        ['Nameplate Ratings - Specifies capacity ratings', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''],
        ['', 2, 'WMaxRtg', '', '', 'uint16', 1, 'W_SF', 'W', '', '', '', 'Active Power Max Rating',
         'Maximum active power rating at unity power factor in watts.', '', ''],
        ['', '', 'CAT_A', 1, '', '', '', '', '', '', '', '', '', '', '', ''],
        ['', '', 'CAT_B', 2, '', '', '', '', '', '', '', '', '', '', '', '']]


def test_to_spreadsheet_point():
    point = {
                "access": "R",
                "desc": "Abnormal operating performance category as specified in IEEE 1547-2018.",
                "label": "Abnormal Operating Category",
                "mandatory": "O",
                "name": "AbnOpCatRtg",
                "symbols": [
                    {
                        "name": "CAT_1",
                        "value": 1
                    },
                    {
                        "name": "CAT_2",
                        "value": 2
                    },
                    {
                        "name": "CAT_3",
                        "value": 3
                    }
                ],
                "type": "enum16"
            }
    ss = []
    assert spreadsheet.to_spreadsheet_point(ss, point, has_notes=False) == 1

    missing_name_p = copy.deepcopy(point)
    del missing_name_p['name']
    with pytest.raises(Exception) as exc1:
        spreadsheet.to_spreadsheet_point(ss, missing_name_p, has_notes=False)
    assert 'Point missing name attribute' in str(exc1.value)

    missing_type_p = copy.deepcopy(point)
    del missing_type_p['type']
    with pytest.raises(Exception) as exc2:
        spreadsheet.to_spreadsheet_point(ss, missing_type_p, has_notes=False)
    assert 'Point AbnOpCatRtg missing type' in str(exc2.value)

    unk_p_type = copy.deepcopy(point)
    unk_p_type['type'] = 'abc'
    with pytest.raises(Exception) as exc3:
        spreadsheet.to_spreadsheet_point(ss, unk_p_type, has_notes=False)
    assert 'Unknown point type' in str(exc3.value)

    p_size_not_int = copy.deepcopy(point)
    p_size_not_int['type'] = 'string'
    p_size_not_int['size'] = 'abc'
    with pytest.raises(Exception) as exc4:
        spreadsheet.to_spreadsheet_point(ss, p_size_not_int, has_notes=False)
    assert 'Point size is for point AbnOpCatRtg not an integer value' in str(exc4.value)


def test_to_spreadsheet_symbol():
    symbol = {"name": "MAX_W", "value": 0}
    ss = []
    spreadsheet.to_spreadsheet_symbol(ss, symbol, has_notes=False)
    assert ss[0][2] == 'MAX_W' and ss[0][3] == 0

    ss = []
    del symbol['value']
    with pytest.raises(Exception) as exc1:
        spreadsheet.to_spreadsheet_symbol(ss, symbol, has_notes=False)
    assert 'Symbol MAX_W missing value' in str(exc1.value)

    ss = []
    del symbol['name']
    with pytest.raises(Exception) as exc2:
        spreadsheet.to_spreadsheet_symbol(ss, symbol, has_notes=False)
    assert 'Symbol missing name attribute' in str(exc2.value)


def test_to_spreadsheet_comment():
    ss = []
    spreadsheet.to_spreadsheet_comment(ss, 'Scaling Factors', has_notes=False)
    assert ss[0][0] == 'Scaling Factors'


def test_spreadsheet_equal():
    spreadsheet_smdx_304 = [
        ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor', 'Units',
         'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description', 'Detailed Description'],
        ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model',
         'Include to support orientation measurements', ''],
        ['', '', 'ID', 304, '', 'uint16', '', '', '', '', 'M', 'S', 'Model ID', 'Model identifier', ''],
        ['', '', 'L', '', '', 'uint16', '', '', '', '', 'M', 'S', 'Model Length', 'Model length', ''],
        ['', '', 'inclinometer.incl', '', 0, 'group', '', '', '', '', '', '', '', '', ''],
        ['', '', 'Inclx', '', '', 'int32', '', -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination', ''],
        ['', '', 'Incly', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination', ''],
        ['', '', 'Inclz', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination', '']
    ]
    ss_copy = copy.deepcopy(spreadsheet_smdx_304)

    assert spreadsheet.spreadsheet_equal(spreadsheet_smdx_304, ss_copy)

    with pytest.raises(Exception) as exc1:
        ss_copy[0][0] = 'abc'
        spreadsheet.spreadsheet_equal(spreadsheet_smdx_304, ss_copy)
    assert 'Line 1 different' in str(exc1.value)

    with pytest.raises(Exception) as exc2:
        del ss_copy[0]
        spreadsheet.spreadsheet_equal(spreadsheet_smdx_304, ss_copy)
    assert 'Different length' in str(exc2.value)


def test_from_csv():
    model_def = {
        'group': {
            'name': 'inclinometer', 'type': 'group', 'label': 'Inclinometer Model',
            'desc': 'Include to support orientation measurements',
            'points': [
                {'name': 'ID', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S', 'label': 'Model ID',
                 'desc': 'Model identifier', 'value': 304, 'standards': []},
                {'name': 'L', 'type': 'uint16', 'size': 1, 'mandatory': 'M', 'static': 'S', 'label': 'Model Length',
                 'desc': 'Model length', 'standards': []}
            ],
            'groups': [
                {'name': 'incl', 'type': 'group', 'count': 0,
                 'points': [
                     {'name': 'Inclx', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'mandatory': 'M',
                      'label': 'X',
                      'desc': 'X-Axis inclination', 'standards': []},
                     {'name': 'Incly', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'label': 'Y',
                      'desc': 'Y-Axis inclination', 'standards': []},
                     {'name': 'Inclz', 'type': 'int32', 'size': 2, 'sf': -2, 'units': 'Degrees', 'label': 'Z',
                      'desc': 'Z-Axis inclination', 'standards': []}
                 ]
                 }
            ]
        },
        'id': 304
    }

    csv_data = spreadsheet.from_csv('./sunspec2/tests/test_data/smdx_304.csv')

    assert model_def == csv_data


def test_to_csv(tmp_path):
    model_def = {
      "id": 304,
      "group": {
        "name": "inclinometer",
        "type": "group",
        "points": [
          {
            "name": "ID",
            "value": 304,
            "desc": "Model identifier",
            "label": "Model ID",
            "mandatory": "M",
            "static": "S",
            "type": "uint16"
          },
          {
            "name": "L",
            "desc": "Model length",
            "label": "Model Length",
            "mandatory": "M",
            "static": "S",
            "type": "uint16"
          }
        ],
        "groups": [
          {
            "name": "incl",
            "type": "group",
            "count": 0,
            "points": [
              {
                "name": "Inclx",
                "type": "int32",
                "mandatory": "M",
                "units": "Degrees",
                "sf": -2,
                "label": "X",
                "desc": "X-Axis inclination"
              },
              {
                "name": "Incly",
                "type": "int32",
                "units": "Degrees",
                "sf": -2,
                "label": "Y",
                "desc": "Y-Axis inclination"
              },
              {
                "name": "Inclz",
                "type": "int32",
                "units": "Degrees",
                "sf": -2,
                "label": "Z",
                "desc": "Z-Axis inclination"
              }
            ]
          }
        ],
        "label": "Inclinometer Model",
        "desc": "Include to support orientation measurements"
      }
    }
    ss = spreadsheet.to_spreadsheet(model_def)
    spreadsheet.to_csv(model_def, filename=tmp_path / 'smdx_304.csv')

    same_data = True
    row_num = 0
    idx = 0
    with open(tmp_path / 'smdx_304.csv') as csvfile:
        csvreader = csv.reader(csvfile)
        for row in csvreader:
            idx = 0
            for i in row:
                if str(ss[row_num][idx]) != str(i):
                    same_data = False
                idx += 1
            row_num += 1
    assert same_data


def test_spreadsheet_from_csv():
    spreadsheet_smdx_304 = [
        ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor', 'Units',
         'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description', 'Detailed Description', 'Standards'],
        ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model',
         'Include to support orientation measurements', '', ''],
        ['', '', 'ID', 304, '', 'uint16', '', '', '', '', 'M', 'S', 'Model ID', 'Model identifier', '', ''],
        ['', '', 'L', '', '', 'uint16', '', '', '', '', 'M', 'S', 'Model Length', 'Model length', '', ''],
        ['', '', 'inclinometer.incl', '', 0, 'group', '', '', '', '', '', '', '', '', '', ''],
        ['', '', 'Inclx', '', '', 'int32', '', -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination', '', ''],
        ['', '', 'Incly', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination', '', ''],
        ['', '', 'Inclz', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination', '', '']
    ]

    counter = 0
    for row in spreadsheet.spreadsheet_from_csv('./sunspec2/tests/test_data/smdx_304.csv'):
        same = True
        counter2 = 0
        for i in row:
            if i != spreadsheet_smdx_304[counter][counter2]:
                same = False
            counter2 += 1
        counter += 1
    assert same


def test_spreadsheet_to_csv(tmp_path):
    spreadsheet_smdx_304 = [
        ['Address Offset', 'Group Offset', 'Name', 'Value', 'Count', 'Type', 'Size', 'Scale Factor', 'Units',
         'RW Access (RW)', 'Mandatory (M)', 'Static (S)', 'Label', 'Description', 'Detailed Description'],
        ['', '', 'inclinometer', '', '', 'group', '', '', '', '', '', '', 'Inclinometer Model',
         'Include to support orientation measurements', ''],
        [0, '', 'ID', 304, '', 'uint16', '', '', '', '', 'M', 'S', 'Model ID', 'Model identifier', ''],
        [1, '', 'L', '', '', 'uint16', '', '', '', '', 'M', 'S', 'Model Length', 'Model length', ''],
        ['', '', 'inclinometer.incl', '', 0, 'group', '', '', '', '', '', '', '', '', ''],
        ['', 0, 'Inclx', '', '', 'int32', '', -2, 'Degrees', '', 'M', '', 'X', 'X-Axis inclination', ''],
        ['', 2, 'Incly', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Y', 'Y-Axis inclination', ''],
        ['', 4, 'Inclz', '', '', 'int32', '', -2, 'Degrees', '', '', '', 'Z', 'Z-Axis inclination', '']
    ]
    spreadsheet.spreadsheet_to_csv(spreadsheet_smdx_304, filename=tmp_path / 'smdx_304.csv')

    same_data = True
    rowNum = 0
    idx = 0
    with open(tmp_path / 'smdx_304.csv') as csvfile:
        csvreader = csv.reader(csvfile)
        for row in csvreader:
            idx = 0
            for i in row:
                if str(spreadsheet_smdx_304[rowNum][idx]) != str(i):
                    same_data = False
                idx += 1
            rowNum += 1
    assert same_data


